Writing a plugin for Maven

  • Tutorial
I have a profile on some maven projects that copies shared libraries and then restarts the Tomcat server.
Maven profile
deploy-depsmaven-dependency-pluginpackagecopy-dependenciestrueисключаем некоторые группы, попадающие в war-архивorg.codehaus.mojoexec-maven-plugin05-stop-tomcatpackageexec-ssh-4-agent-i${putty.key}${ssh.user}@${ssh.host}${tomcat.dir.root}/bin/shutdown.shplink10-clean-shared-jarspackageexec-ssh-4-agent-i${putty.key}${ssh.user}@${ssh.host}rm-Rf${tomcat.dir.shared}/*.jarplink15-upload-shared-jarspackageexec-scp-4-agent-i${putty.key}${project.build.directory}/dependency/compile/*.jar${ssh.user}@${ssh.host}:${tomcat.lib.shared}/pscp20-start-tomcatpackageexec-ssh-4-agent-i"${putty.key}"${ssh.user}@${ssh.host}bin/startup.shplink

stepping aside, I’ll tell you why this profile
In some projects, the Nginx + Tomcat bundle is used. For this bundle, the following is implemented:
  1. For all static content, a directory outside of webapps is used. Nginx "looks" into this directory and gives it via the web path "/ static / *"
  2. All shared java libraries (rarely changed) are loaded into the $ {catalina.home} / shared directory, and the variable “shared.loader” is configured for this in Tomcat in the conf / catalina.properties file
  3. Each Tomcat instance has its own system user.
  4. Keys are used for access via SSH and each developer has his own

Accordingly, loading static content and shared libraries are separate profiles. Everything else is collected in a war archive and installed through the standard Tomcat web-manager.
And in order not to produce configurations, PAgent is used, into which we have already added the private keys we need. They are also used to connect via Putty.

The profile in pom.xml lies to itself, it doesn’t seem to bite, it even plows slowly for the benefit of the programmer, but it just has a couple of “minuses” in it - it takes up a lot of space when pom.xml is deployed and you have to insert it into new projects.
And if you can get rid of the second minus by writing a template to your favorite_IDE or build your archetype , then it is not so easy to get rid of the first minus.

Isn't it that simple exactly? can we wrap this profile as a plugin for Maven? No sooner said than done.

Step 1. Create a draft project for the maven plugin


, in which we indicate the type of assembly "maven-plugin". We will also need dependencies:
1) org.apache.maven.plugin-tools:maven-plugin-annotationsfor the possibility of specifying Mojo classes not through JavaDoc, but using annotations
2) org.twdata.maven:mojo-executorfor the possibility of launching other plugins from ours.
While there are enough dependencies, it's time to start actually implementing the Mojo class itself.
commit

Step 2. Writing a Mojo Class


Class blank
@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

We need to generate mojo tags from annotations ( commit ):
Class blank
maven-plugin-pluginhelp-goalhelpmojomojo-descriptordescriptortrue


Add dependency copying
It was
maven-dependency-pluginpackagecopy-dependenciestrueисключаем некоторые группы, попадающие в war-архив

has become
@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
Briefly:
  1. “RequiresDependencyResolution = ResolutionScope.TEST” is required to get a list of dependencies - without this, the maven-dependency-plugin will not copy them
  2. “ThreadSafe = true” indicates that this Mojo can be run in a separate thread - it is self-sufficient
  3. the static executeMojo method allows you to fulfill any goal for any available plugin with a description of the environment configuration. In this case, the environment remains the same (_pluginEnv variable)

Add a method to stop the Tomcat server
It was
org.codehaus.mojoexec-maven-plugin05-stop-tomcatpackageexec-ssh-4-agent-i${putty.key}${ssh.user}@${ssh.host}${tomcat.dir.root}/bin/shutdown.shplink

has become
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));
	}
}


Add the remaining methods.

By analogy with the previous paragraph, we add methods for remotely cleaning the tomcat.lib.shared directory, copying new libraries into it, and then starting the Tomcat server.
commit

Step 3. Install the plugin in the repository and edit the configuration of the Maven project


Installing the plugin in the local repository is carried out by a simple command “mvn clean install”

And we edit the project configuration:
It was
deploy-depsmaven-dependency-pluginpackagecopy-dependenciestrueисключаем некоторые группы, попадающие в war-архивorg.codehaus.mojoexec-maven-plugin05-stop-tomcatpackageexec-ssh-4-agent-i${putty.key}${ssh.user}@${ssh.host}${tomcat.dir.root}/bin/shutdown.shplink10-clean-shared-jarspackageexec-ssh-4-agent-i${putty.key}${ssh.user}@${ssh.host}rm-Rf${tomcat.dir.shared}/*.jarplink15-upload-shared-jarspackageexec-scp-4-agent-i${putty.key}${project.build.directory}/dependency/compile/*.jar${ssh.user}@${ssh.host}:${tomcat.lib.shared}/pscp20-start-tomcatpackageexec-ssh-4-agent-i"${putty.key}"${ssh.user}@${ssh.host}bin/startup.shplink

has become
deploy-depsinfo.alenkov.tools.maventomcat7-ewar-pluginprocess-sourcesdeploy-deps

The description of the maven project can be
mvn clean process-sources tomcat7-ewar:deploy-deps

further simplified by deleting the deploy-deps profile and calling goal directly: This completes the process of improving the readability of the maven project and moving the frequently used out (the final commit for this post) - the maven project was reduced by 106 lines. There is still code optimization ahead, the addition of parameters and much more, but this is a completely different story that I will someday tell the hawkers.

UPD: Published the plugin in his repository . I plan to develop it further, because if there are suggestions / comments, then I accept them on github .
UPD: Updated the plugin to version 1.1
UPD: Updated the plugin to version 2.0 - got rid of Putty

Also popular now: