- PVSM.RU - https://www.pvsm.ru -

Пишем плагин для 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 или свой архетип [1] наваять, то от первого минуса не так-то просто избавить.

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

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

, в котором указываем тип сборки «maven-plugin». Так же нам понадобятся зависимости:
1) org.apache.maven.plugin-tools:maven-plugin-annotations [2] для возможности указания Mojo классов не через JavaDoc, а с помощью аннотаций
2) org.twdata.maven:mojo-executor [3] для возможности запуска других плагинов из нашего.
Пока зависимостей достаточно — пора приступать собственно к реализации самого Mojo класса.
commit [4]

Шаг 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 [4]

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

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

<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 [7]
Кратко:

  1. «requiresDependencyResolution = ResolutionScope.TEST» требуется [8] для получения списка зависимостей — без этого плагин 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 [9]

Шаг 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-проекта и выноса часто используемого «наружу» закончен (заключительный [9] commit для этого поста). Впереди ещё оптимизация кода, добавление параметров и многое другое, но это уже совсем другая история, которую когда-нибудь поведаю хабражителям.

Автор: Borz

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/plugins/50420

Ссылки в тексте:

[1] свой архетип: http://habrahabr.ru/post/111408/

[2] org.apache.maven.plugin-tools:maven-plugin-annotations: http://maven.apache.org/plugin-tools/maven-plugin-annotations/

[3] org.twdata.maven:mojo-executor: https://github.com/TimMoore/mojo-executor

[4] commit: https://github.com/BorzdeG/info.alenkov.tools.maven.tomcat7-ewar-plugin/commit/c3eafc1d0f325f9b1f4e4ed645aef65b4aab09fe

[5] потребуется: http://maven.apache.org/plugin-tools/maven-plugin-plugin/examples/using-annotations.html

[6] commit: https://github.com/BorzdeG/info.alenkov.tools.maven.tomcat7-ewar-plugin/commit/9a3c863194cbc6efd89fff2f8c97db1c3588d4fd

[7] commit: https://github.com/BorzdeG/info.alenkov.tools.maven.tomcat7-ewar-plugin/commit/67c58366472b0b064e17c2e39c37619dda090840

[8] требуется: http://maven.apache.org/developers/mojo-api-specification.html

[9] commit: https://github.com/BorzdeG/info.alenkov.tools.maven.tomcat7-ewar-plugin/commit/5e71f9668336122a85be763a44c43f8c24d49f8a

[10] Источник: http://habrahabr.ru/post/205118/