This came to bug me while creating a maven plugin the other day in which the plugin needed to read a specific classpath resource from the project it was running in. Since maven plugins run in their own class loader, I wasn't going to be able to access project classpath resources.
I might have been able to add the @requiresDependencyResolution metadata annotation to resolve the problem, but we really didn't want to box ourselves in to needing an enclosing project to run the plug-in.
The Maven Exec Plugin gave me an idea.
The Maven Exec Plugin is the maven-y way to run command-line java through a maven goal. It can either be run within the maven process as a separate thread or as a separate process. Either way, it has the same challenge of propagating the enclosing project's classpath on to a separate and distinct classpath context.
In the in-maven-process case, what they do is create their own classloader, and then run the invocation of the main method inside a separate thread, setting that threads context classloader along the way:
private void executeWithClassLoader(Runnable runnable, ClassLoader classLoader) throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( runnable.getClass().getName() );
Thread bootstrapThread = new Thread( threadGroup, runnable, runnable.getClass().getName() + ".run()" );
bootstrapThread.setContextClassLoader(classLoader);
bootstrapThread.start();
try {
bootstrapThread.join();
} catch ( InterruptedException e ) {
Thread.currentThread().interrupt(); // good practice if don't throw
getLog().warn( "interrupted while joining against thread " + bootstrapThread, e ); // not expected!
}
synchronized ( threadGroup )
{
if ( threadGroup.uncaughtException != null )
{
throw new MojoExecutionException( "An exception occured while executing the Java class. "
+ threadGroup.uncaughtException.getMessage(),
threadGroup.uncaughtException );
}
}
}
The Runnable that is passed in is the section of code where you actually need access to the project's classpath. The ClassLoader is created like this:
URL outputDirectory = new File( project.getBuild().getOutputDirectory() ).toURI().toURL();
ClassLoader classLoader = new URLClassLoader( new URL[] { outputDirectory } );
this.executeWithClassLoader(runnable, classLoader);
Of course, there may be other directories or artifacts that you need to add for your runnable to function correctly, but that is the basic idea.
For more ideas, check out http://svn.codehaus.org/mojo/tags/exec-maven-plugin-1.2/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java

0 comments:
Post a Comment