null

Как создать свой первый Liferay портлет, используя maven?

В данной заметке расскажу о том, как собрать свой первый Liferay портлет с помощью maven'а. Параллельно погрузимся в код архетипа портлета и узнаем, почему, создание пустого портлета из архетипа не является тривиальной задачей. 

Уже не раз я становился свидетелем того, как при написании "Hello, world" портлета у человека возникают трудности и непонимания того, что происходит и почему ничего не работает.

Сразу же обозначу, что мы будем делать и что хотим получить на выходе.

  1. Создадим мавеновский проект из архетипа написанного разработчиками платформы Liferay
  2. Произведем сборку приложения

Работать с мавеном будем через терминал. Можете использовать возможности IDE, которая вам больше нравится. Сам же сделаю всё универсальным способом, используя терминал.

Давайте создадим структуру проекта. В Liferay написали множество maven архетипов, чтобы создать шаблон проекта под их платформу в несколько команд и дальше начать работать уже по существу.

Здесь прошу всех сударей проследовать к терминалу и запустить следующий код:

1
mvn archetype:generate

 

Если всё пошло по плану, то maven сообщит вам о работе в интерактивном режиме. Выведет список существуюших архетипов и предложит выбрать один из них или же воспользоваться фильтром для поиска необходимого. Выглядит это следующим образом:

1
2
3
4
5
1747: remote -> us.fatehi:schemacrawler-archetype-maven-project (-)
1748: remote -> us.fatehi:schemacrawler-archetype-plugin-command (-)
1749: remote -> us.fatehi:schemacrawler-archetype-plugin-dbconnector (-)
1750: remote -> us.fatehi:schemacrawler-archetype-plugin-lint (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 909:


Список там не маленький, поэтому воспользуемся фильтром и введем в него 'liferay'.

Потребуется нам архетип 'com.liferay.maven.archetypes:liferay-portlet-archetype' . После выбора архетипа необходимо указать версию портала. Я остановился на версии '6.2.5'. Сейчас уже имеется архетип с версией '7.0.0-m2', но на текущий момент портал и стандартные портлеты содержат множество багов, да и отсутствия части библиотек с соответствующей версией сулит множество дополнительных проблем при сборке из-за ручного подбора версий для зависимостей.
Далее прописываем группу, имя, версию и пакет проекта. У меня получилось следующее:

После выполнения этих шагов maven создаст структуру проекта. Если всё прошло успешно, то он сообщит об этом сообщением "BUILD SUCCESS".

Теперь попробуем произвести сборку приложения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mvn package
 
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.version' for com.liferay.portal:portal-service:jar must be a valid version but is '${liferay.version}'. @ line 55, column 13
[ERROR] 'dependencies.dependency.version' for com.liferay.portal:util-bridges:jar must be a valid version but is '${liferay.version}'. @ line 61, column 13
[ERROR] 'dependencies.dependency.version' for com.liferay.portal:util-taglib:jar must be a valid version but is '${liferay.version}'. @ line 67, column 13
[ERROR] 'dependencies.dependency.version' for com.liferay.portal:util-java:jar must be a valid version but is '${liferay.version}'. @ line 73, column 13
[ERROR] 'build.plugins.plugin.version' for com.liferay.maven.plugins:liferay-maven-plugin must be a valid version but is '${liferay.maven.plugin.version}'. @ line 15, column 14
 @
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]  
[ERROR]   The project com.bleizard:test:1.0-SNAPSHOT (/some/path/test/pom.xml) has 5 errors
[ERROR]     'dependencies.dependency.version' for com.liferay.portal:portal-service:jar must be a valid version but is '${liferay.version}'. @ line 55, column 13
[ERROR]     'dependencies.dependency.version' for com.liferay.portal:util-bridges:jar must be a valid version but is '${liferay.version}'. @ line 61, column 13
[ERROR]     'dependencies.dependency.version' for com.liferay.portal:util-taglib:jar must be a valid version but is '${liferay.version}'. @ line 67, column 13
[ERROR]     'dependencies.dependency.version' for com.liferay.portal:util-java:jar must be a valid version but is '${liferay.version}'. @ line 73, column 13
[ERROR]     'build.plugins.plugin.version' for com.liferay.maven.plugins:liferay-maven-plugin must be a valid version but is '${liferay.maven.plugin.version}'. @ line 15, column 14
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException 
 
Maven сообщает о том, что в файле 'pom.xml' мы используем свойства <strong>'${liferay.version}'</strong> и <strong>'${liferay.maven.plugin.version}'</strong>, о значениях которых он ни слухом, ни духом.

Чтобы избавиться от этой проблемы необходимо добавить в файл pom.xml внутрь блока <project> код, который задаст значения эти свойствам

1
2
3
4
<properties>
   <liferay.version>6.2.5</liferay.version>
   <liferay.maven.plugin.version>6.2.5</liferay.maven.plugin.version>
</properties>

 

Достаточно странно, что при создании проекта нас просят выбрать версию архетипа, которая должна быть завязана на версии портала, но не проставляют это значения в соответствуюшие свойства.  Человек, который был вынужден открыть 'pom.xml' и внести парочку свойств попутно замечает, что в блоке <plugin> есть блок с конфигурацией, внутри которой также задаются значение параметров свойствами, которые не объявлены. Если прочитать названия свойств, то не трудно понять, что это конфигурация связана с автоматическим развретыванием приложения на портале. Многие, в том числе и я, удаляют этот блок за ненадобностью, т.к. будут пользоваться порталом на удаленной машине.

http://risovach.ru/upload/2013/05/mem/mne-kazhetsya-ili-frai-futurama_18798565_orig_.jpg

В этот момент начинается веселая картина. Если человек между добавлением двух свойств с версиями портала, плагина и удаление блока конфигурации не производил попытку сборки, то при попытке maven сообщит ему следующее.

1
2
3
4
5
6
7
8
9
10
11
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.762 s
[INFO] Finished at: 2017-01-23T00:24:55+03:00
[INFO] Final Memory: 10M/211M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.liferay.maven.plugins:liferay-maven-plugin:6.2.5:build-css (default) on project test: null: MojoExecutionException: NullPointerException -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]

 

Если же просто добавить парочку свойств и ничего более не делать, то портлет успешно собирается. Но зачем тогда рассматривать вышеописанную ситуацию?
За последний год наблюдал уже вопрос от пяти человек, которые попадали в такую ситуацию. Если первое сообщение об  ошибки сборки для человека слабо понимающего, что из себя представляет maven, несло хоть какую-то полезную информацию, то здесь причины проблемы не очевидны. Как правило всё заканчивается удалением из pom выполнения 'build-css'.

http://m.memegen.com/q0vdjb.jpg

Подход 'удаления конечности' при возникновении проблемы с ней, почти всегда выззывает ощущение 'творящейся на глазах магии', но сегодня мы отправимся закулисы и посмотрим на то, как на самым выглядит, кажущаясь магией ошибка.

Из сообщения от мавена видно, что по каким-то причинам у нас не выполняет цель 'build-css' и где-то внутри неё выбрасывается NPE. Посмотрим где это происходит, запустив maven в отладочном режиме, используя ключик -e.
 

1
mvn package -e

 

Результат следующий:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ERROR] Failed to execute goal com.liferay.maven.plugins:liferay-maven-plugin:6.2.5:build-css (default) on project test: null: MojoExecutionException: NullPointerException -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal com.liferay.maven.plugins:liferay-maven-plugin:6.2.5:build-css (default) on project test: null
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:212)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:116)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:80)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:307)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:193)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:106)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:863)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:288)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:199)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
Caused by: org.apache.maven.plugin.MojoExecutionException
    at com.liferay.maven.plugins.AbstractToolsLiferayMojo.execute(AbstractToolsLiferayMojo.java:92)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:207)
    ... 20 more
Caused by: java.lang.NullPointerException
    at java.util.regex.Matcher.getTextLength(Matcher.java:1234)
    at java.util.regex.Matcher.reset(Matcher.java:308)
    at java.util.regex.Matcher.<init>(Matcher.java:228)
    at java.util.regex.Pattern.matcher(Pattern.java:1088)
    at com.liferay.maven.plugins.AbstractToolsLiferayMojo.getPortalMajorVersion(AbstractToolsLiferayMojo.java:249)
    at com.liferay.maven.plugins.AbstractToolsLiferayMojo.execute(AbstractToolsLiferayMojo.java:76)
    ... 22 more

Можно увидеть, что NPE появляется в классах стандартной библиотеки Java, которая вызывается из класса  AbstractToolsLiferayMojo метода execute. Этот класс является частью плагина Liferay и лежит в открытом доступе на github.

Вот строка вызова библиотечной функции:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    public Matcher matcher(CharSequence input) {
 
        if (!compiled) {
 
            synchronized(this) {
 
                if (!compiled)
 
                    compile();
 
            }
 
        }
 
        Matcher m = new Matcher(this, input);
 
        return m;
}
 
Matcher(Pattern parent, CharSequence text) {
 
        this.parentPattern = parent;
 
        this.text = text;
 
 
        // Allocate state storage
 
        int parentGroupCount = Math.max(parent.capturingGroupCount, 10);
 
        groups = new int[parentGroupCount * 2];
 
        locals = new int[parent.localCount];
 
 
        // Put fields into initial states
 
        reset();
 
    }
public Matcher reset() {
 
        first = -1;
 
        last = 0;
 
        oldLast = -1;
 
        for(int i=0; i<groups.length; i++)
 
            groups[i] = -1;
 
        for(int i=0; i<locals.length; i++)
 
            locals[i] = -1;
 
        lastAppendPosition = 0;
 
        from = 0;
 
        to = getTextLength();
 
        return this;
 
    }
    int getTextLength() {
 
        return text.length();
 
    }

 

NPE произошло в методе getTextLength. Единственное что могло его вызвать, что поле text равно null. Оно проставляется в конструкторе класса Matcher и передаётся вторым аргументом. В методе, который вызывает конструктор второй аргумент приходит из вызываемой функции. Теперь понятно в плагине Liferay был передан в метод matcher null в качестве аргумента.

Вот код класса AbstarctToolsLiferayMojo где это происходит:

1
2
3
4
5
6
7
8
9
10
11
protected float getPortalMajorVersion() {
    float majorVersion = 0;
 
    Matcher matcher = _majorVersionPattern.matcher(liferayVersion);
 
    if (matcher.find()) {
        majorVersion = GetterUtil.getFloat(matcher.group(1));
    }
 
    return majorVersion;
}

 

Здесь liferayVersion - поле класса родителя AbstarctLiferayMojo, которое объявлено следующим образом:

1
2
3
4
/**
 * @parameter expression="${liferayVersion}"
 */
protected String liferayVersion;

Значение ему проставляется через @parameter, которая берет значения свойства '$liferayVersion' из pom.xml.

Дойдя до этого, я подумал, что здесь простая опечатка. Впервый раз нас просили задать свойства '$liferay.version', через точку, а здесь же оно было написано camalCase'ом. Если добавить помимо  'liferay.version' ещё и 'liferayVersion', то сборка завершится успешно. Но, как оказалось, при создании архетипа в том самом блоке конфигурации помимо свойст для автоматического развертывания есть ещё и такая строка:

1
<liferayVersion>${liferay.version}</liferayVersion>

 

В итоге, ознакомившись с вышеизложенным у вас не возникнет проблем при попадании в такую ситуацию и вы будете понимать, для чего нужны те или иные действия по решению этой проблемы. К сожалению, понимание этого вызывает уйму вопросов к людям, писавшим этот плагин. Что заставило их не проставлять версии автоматически и зачему использовать дополнительное свойство для дополнительных целей сборки?


http://s00.yaplakal.com/pics/pics_original/6/2/9/7540926.jpg