...

вторник, 10 декабря 2013 г.

Пишем плагин для Maven

Есть у меня на некоторых maven-проектах профиль, с помощью которого производится копирование shared-библиотек с последующим перезапуском сервера Tomcat.

Maven profile


<profile>
<id>deploy-deps</id>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<useSubDirectoryPerScope>true</useSubDirectoryPerScope>
<excludeGroupIds>исключаем некоторые группы, попадающие в war-архив</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>05-stop-tomcat</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>${tomcat.dir.root}/bin/shutdown.sh</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
<execution>
<id>10-clean-shared-jars</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>rm</argument>
<argument>-Rf</argument>
<argument>${tomcat.dir.shared}/*.jar</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
<execution>
<id>15-upload-shared-jars</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-scp</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${project.build.directory}/dependency/compile/*.jar</argument>
<argument>${ssh.user}@${ssh.host}:${tomcat.lib.shared}/</argument>
</arguments>
<executable>pscp</executable>
</configuration>
</execution>
<execution>
<id>20-start-tomcat</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>"${putty.key}"</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>bin/startup.sh</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>





отходя в сторону, поведаю для чего сей профиль
В части проектов используется связка Nginx+Tomcat. Для данной связки реализовано следующее:


  1. Для всего статичного контента используется некий каталог за пределами webapps. В этот каталог «смотрит» Nginx и отдаёт по web-пути "/static/*"

  2. Все shared java-библиотеки (редко изменяемые) грузятся в каталог ${catalina.home}/shared, и в Tomcat в файле conf/catalina.properties настроена для этого переменная «shared.loader»

  3. Для каждого инстанса Tomcat создан свой системный пользователь

  4. Для доступа по SSH используются ключи и у каждого разработчика он свой




Соответственно, загрузка статичного контента и shared-библиотек это отдельные профили. Всё остальное собирается в war-архив и устанавливается через стандартный web-manager Tomcat-а.

А чтобы не плодить конфигураций, используется PAgent, в который уже и добавленые нужные нам private keys. Они же используются для подключения через Putty




Лежит себе профиль в pom.xml, не кусается вроде бы, даже пашет потихоньку на благо программера, но вот только есть в нём пара «минусов» — занимает много места при развёрнутом pom.xml да ещё и в новые проекты приходится вставлять.

И если от второго минуса можно избавиться написав шаблон в *любимой IDE* или свой архетип наваять, то от первого минуса не так-то просто избавить.

Точно ли не так просто? может «обернём» этот профиль в виде плагина для Maven? Сказано, сделано.



Шаг 1. Создаём заготовку проекта для плагина maven




, в котором указываем тип сборки «maven-plugin». Так же нам понадобятся зависимости:

1) org.apache.maven.plugin-tools:maven-plugin-annotations для возможности указания Mojo классов не через JavaDoc, а с помощью аннотаций

2) org.twdata.maven:mojo-executor для возможности запуска других плагинов из нашего.

Пока зависимостей достаточно — пора приступать собственно к реализации самого Mojo класса.

commit

Шаг 2. Пишем Mojo-класс




Заготовка класса


@Mojo(name = "deploy-deps", defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true)
public class DeployDepsMojo extends AbstractMojo {

@Component
protected MavenProject mavenProject;
@Component
protected MavenSession mavenSession;
@Component
protected BuildPluginManager pluginManager;
protected MojoExecutor.ExecutionEnvironment _pluginEnv;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
_pluginEnv = executionEnvironment(mavenProject, mavenSession, pluginManager);
}
}







commit

Нам потребуется генерация mojo тегов из аннотаций (commit):


Заготовка класса


<build>
<!-- ... -->
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<executions>
<execution>
<id>help-goal</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
<execution>
<id>mojo-descriptor</id>
<goals>
<goal>descriptor</goal>
</goals>
</execution>
</executions>
<configuration>
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
</plugin>
</plugins>
</build>







Добавляем копирование зависимостей

было


<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<useSubDirectoryPerScope>true</useSubDirectoryPerScope>
<excludeGroupIds>исключаем некоторые группы, попадающие в war-архив</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>





стало


@Mojo(name = "deploy-deps",
requiresDependencyResolution = ResolutionScope.TEST,
defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true)
public class DeployDepsMojo extends AbstractMojo {

// ...

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
_pluginEnv = executionEnvironment(mavenProject, mavenSession, pluginManager);
copyDependencies();
}

private void copyDependencies() throws MojoExecutionException {
// TODO expects corrections https://github.com/TimMoore/mojo-executor/issues/18
Plugin pluginDependency = plugin("org.apache.maven.plugins", "maven-dependency-plugin", "2.8");

final Xpp3Dom cfg = configuration(element(name("useSubDirectoryPerScope"), "true"));

executeMojo(pluginDependency, goal("copy-dependencies"), cfg, _pluginEnv);
}
}





commit

Кратко:


  1. «requiresDependencyResolution = ResolutionScope.TEST» требуется для получения списка зависимостей — без этого плагин maven-dependency-plugin не произведёт их копирование

  2. «threadSafe = true» указывает на то, что данный Mojo можно запускать в отдельном потоке — он самодостаточен

  3. статический метод executeMojo позволяет выполнить любой goal для любого доступного плагина с описанием конфигурации окружения. В данном случае окружение остаётся тем же (переменная _pluginEnv)




Добавляем метод для остановки сервера Tomcat

было


<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>05-stop-tomcat</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>${tomcat.dir.root}/bin/shutdown.sh</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
<!-- ... -->
</executions>
</plugin>





стало


public class DeployDepsMojo extends AbstractMojo {
public static final String PLG_EXEC_CFG_ARGUMENTS = "arguments";
public static final Xpp3Dom PLG_EXEC_CFG_EXEC_PLINK = element(name("executable"), "plink").toDom();
public static final String PLG_EXEC_GOAL_EXEC = goal("exec");
public static final String PLG_EXEC_PROTOCOL_SSH = "-ssh";

// ...

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
_pluginEnv = executionEnvironment(mavenProject, mavenSession, pluginManager);
_pluginExec = plugin("org.codehaus.mojo", "exec-maven-plugin", "1.2.1");
copyDependencies();
tomcatShutdown();
}

private void tomcatShutdown() throws MojoExecutionException {
Xpp3Dom cfg = getBaseConfigExec(PLG_EXEC_PROTOCOL_SSH);
final Xpp3Dom arguments = cfg.getChild(PLG_EXEC_CFG_ARGUMENTS);
arguments.addChild(element(name("argument"), "${ssh.user}@${ssh.host}").toDom());
arguments.addChild(element(name("argument"), "bin/shutdown.sh").toDom());
cfg.addChild(PLG_EXEC_CFG_EXEC_PLINK);

executeMojo(_pluginExec, PLG_EXEC_GOAL_EXEC, cfg, _pluginEnv);
}

private Xpp3Dom getBaseConfigExec(String protocol) {
final Element el0 = element(name("argument"), protocol);
final Element el1 = element(name("argument"), "-4");
final Element el2 = element(name("argument"), "-agent");
final Element el3 = element(name("argument"), "-i");
final Element el4 = element(name("argument"), "${putty.key}");
return configuration(element(name(PLG_EXEC_CFG_ARGUMENTS), el0, el1, el2, el3, el4));
}
}







Добавляем оставшиеся методы



По аналогии с предыдущим пунктом, добавляем методы для удалённой очистки каталога tomcat.lib.shared, копирования в него новых библиотек и последующего запуска сервера Tomcat.

commit

Шаг 3. Устанавливаем плагин в репозитарий и правим конфигурацию Maven-проекта




Установка плагина в локальный репозитарий выполняется простой командой «mvn clean install»

И правим конфигурацию проекта:


было


<profile>
<id>deploy-deps</id>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<useSubDirectoryPerScope>true</useSubDirectoryPerScope>
<excludeGroupIds>исключаем некоторые группы, попадающие в war-архив</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>05-stop-tomcat</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>${tomcat.dir.root}/bin/shutdown.sh</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
<execution>
<id>10-clean-shared-jars</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>rm</argument>
<argument>-Rf</argument>
<argument>${tomcat.dir.shared}/*.jar</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
<execution>
<id>15-upload-shared-jars</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-scp</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>${putty.key}</argument>
<argument>${project.build.directory}/dependency/compile/*.jar</argument>
<argument>${ssh.user}@${ssh.host}:${tomcat.lib.shared}/</argument>
</arguments>
<executable>pscp</executable>
</configuration>
</execution>
<execution>
<id>20-start-tomcat</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-ssh</argument>
<argument>-4</argument>
<argument>-agent</argument>
<argument>-i</argument>
<argument>"${putty.key}"</argument>
<argument>${ssh.user}@${ssh.host}</argument>
<argument>bin/startup.sh</argument>
</arguments>
<executable>plink</executable>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>





стало


<profile>
<id>deploy-deps</id>
<build>
<plugins>
<plugin>
<groupId>info.alenkov.tools.maven</groupId>
<artifactId>tomcat7-ewar-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>deploy-deps</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>







На этом процесс улучшения читабельности maven-проекта и выноса часто используемого «наружу» закончен. Впереди ещё оптимизация кода, добавление параметров и многое другое, но это уже совсем другая история, которую когда-нибудь поведаю хабражителям.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий