В данной заметке расскажу о том, как собрать свой первый Liferay портлет с помощью maven'а. Параллельно погрузимся в код архетипа портлета и узнаем, почему, создание пустого портлета из архетипа не является тривиальной задачей.
Уже не раз я становился свидетелем того, как при написании "Hello, world" портлета у человека возникают трудности и непонимания того, что происходит и почему ничего не работает.
Сразу же обозначу, что мы будем делать и что хотим получить на выходе.
- Создадим мавеновский проект из архетипа написанного разработчиками платформы Liferay
- Произведем сборку приложения
Работать с мавеном будем через терминал. Можете использовать возможности IDE, которая вам больше нравится. Сам же сделаю всё универсальным способом, используя терминал.
Давайте создадим структуру проекта. В Liferay написали множество maven архетипов, чтобы создать шаблон проекта под их платформу в несколько команд и дальше начать работать уже по существу.
Здесь прошу всех сударей проследовать к терминалу и запустить следующий код:
Если всё пошло по плану, то 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> есть блок с конфигурацией, внутри которой также задаются значение параметров свойствами, которые не объявлены. Если прочитать названия свойств, то не трудно понять, что это конфигурация связана с автоматическим развретыванием приложения на портале. Многие, в том числе и я, удаляют этот блок за ненадобностью, т.к. будут пользоваться порталом на удаленной машине.
В этот момент начинается веселая картина. Если человек между добавлением двух свойств с версиями портала, плагина и удаление блока конфигурации не производил попытку сборки, то при попытке 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'.
Подход 'удаления конечности' при возникновении проблемы с ней, почти всегда выззывает ощущение 'творящейся на глазах магии', но сегодня мы отправимся закулисы и посмотрим на то, как на самым выглядит, кажущаясь магией ошибка.
Из сообщения от мавена видно, что по каким-то причинам у нас не выполняет цель 'build-css' и где-то внутри неё выбрасывается NPE. Посмотрим где это происходит, запустив maven в отладочном режиме, используя ключик -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;
int parentGroupCount = Math.max(parent.capturingGroupCount, 10 );
groups = new int [parentGroupCount * 2 ];
locals = new int [parent.localCount];
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 >
|
В итоге, ознакомившись с вышеизложенным у вас не возникнет проблем при попадании в такую ситуацию и вы будете понимать, для чего нужны те или иные действия по решению этой проблемы. К сожалению, понимание этого вызывает уйму вопросов к людям, писавшим этот плагин. Что заставило их не проставлять версии автоматически и зачему использовать дополнительное свойство для дополнительных целей сборки?