Thursday, December 4, 2014

Maven Plugin with Java Endorsed

The Challenge

I need to write a maven plugin that in order for it to work it needs endorsed jars loaded into the java JVM (java.endorsed.dirs). An example of this would be to enhance the jaxb facets (see https://github.com/whummer/jaxb-facets).
A simple search on google will bring you to solutions like using the maven-compiler-plugin (for example: http://www.mindbug.org/2009/02/adding-endorsements-to-mavens-plugins.html). This solution will compile your source will the endorsed. What we need is for our plugin that we just wrote, to run in endorsed mode so that I can use within the plugin the new jabx facets features.

Solution

A simple search for which maven plugins support endorsed we will find plugins like surefire and a few others. What they all have in common is that for them to work you need to use the fork flag. This should give us a hint as to the general solution to the problem. For us to use the endorsed in our plugin we need to spawn a new java process and then we can specify the endorsed jvmarg.

Spawn Process

There are a few ways to spawn a new process in java. The main issue is to get the output of the process running (we need both the stdout and the errorout). Maven has a library to help with this: plexus-utils. In this library you have a class called: CommandLineUtils.So for example you can have the following code:
Commandline cl = new Commandline();
cl.setExecutable(path);
cl.createArg().setValue("-Djava.endorsed.dirs=" + endorsedDir);
cl.createArg().setValue("-cp");
cl.createArg().setValue(classpathList);
cl.createArg().setValue(ForkMyPlugin.class.getName());
CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.executeCommandLine(cl, stdout, stderr);
String output = stdout.getOutput();
if (output.length() > 0) {
getLog().info(output);
}
String errOutput = stderr.getOutput();
if (errOutput.length() > 0) {
getLog().error(errOutput);
throw new MojoExecutionException(errOutput);
}

Of course the file ForkMyPlugin needs to have a main method to handle the execution of the plugin.

ClassPath

The next issue that we need to deal with is how to create the classpath for the plugin fork code. At first thought you could inject MavenProject and get from there all the dependencies of the project. This will not work since you will not get the dependencies from the plugin pom.xml – instead you will get the plugins of the calling pom.xml. If you realy wan’t to get the dependencies on the plugin pom.xml you need to use pluginArtifacts (@Parameter(defaultValue="${plugin.artifacts}", readonly=true)).
But a simpler way will be to just pass on the current classpath to the fork class. To do this, you need to load the resources from the current jar:
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF");
From here it should be easy to transfer from the URL to a file location for the classpath (just don’t forget the delimiter for windows is “;” , while for linux it is “:”).

Last but not least is the length of the classpath. There is a limitation on the commandline for how many dependencies you can have. The simple solution is to remove all dependencies that are not used. Especially the maven dependencies should be set to scope – provided.