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

ссылка на оригинал статьи http://habrahabr.ru/post/205118/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *