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.