This may not be too revolutionary, but I’ve spent enough time googling and asking questions on the Maven lists to believe that there isn’t a lot of information out there about topic so I thought I’d document it in a blog. Apologies to the Maven guys if this isn’t the best way of going about this (I believe Jason mentioned something about improvements to the Maven Embedder), but I needed a solution right now.
The situation was that I wanted to be able to decide at runtime which jars to put onto the execution classpath for the Jetty6 Maven Plugin. The decision is based on the runtime environment: if the user is running with < 1.5 JVM, then I need to be able to download and use the JSP 2.0 jars. If, however, the user is running with a 1.5 JVM, then the JSP 2.1 jars should be used (as these mandate JDK 1.5).
Rather than having to hard-code into my plugin a list of jars and their transitive dependencies for each version of JSP, I created one submodule for each JSP variant and listed all dependencies in the module’s pom.xml.
This reduced the problem to downloading and transitively resolving a pom on-the-fly, then getting all of the resolved artifacts on the plugin’s execution classpath.
Runtime Downloading and Transitive Resolution of a pom
I used the Maven tools for manipulating artifacts. You need to put some configuration parameter declarations into your plugin to gather the necessary factories etc from the runtime environment to drive the tools. The ones I used were:
Then, it is a matter of downloading the pom, getting its dependencies and transitively resolving them. Here’s a snippet of the code I used to do the job generically:
Now we can make some environment-based decisions on which pom use to extract the artifacts we want:
Having got the artifacts, now we need to place them on the execution classpath.
Runtime Maven Classpath Manipulation
This is the bit I found really hair-raising. I’m not convinced it’s a bullet-proof solution, but all testing to date seems to indicate its working fine.
Taking the Artifacts we got from the on-the-fly downloaded pom above, we need to put these into a Classloader and also arrange for the existing ContextClassLoader to be it’s parent (so we can resolve classes that are already on the plugin’s classpath).
The first solution that springs to mind is to put the urls of the download jars into a URLClassLoader and make the current ContextClassLoader it’s parent like this:
However, after a lot of experimentation, it seems that this just simply does not work: the parent class loader does not seem able to correctly resolve classes and resource when delegated to from the URLClassLoader. The parent class loader is an instance of a ClassWorlds ClassLoader set up by the plugin execution environment.
Experimenting further, I discovered it is possible to create a new ClassWorlds classloading hierarchy, injecting the jars that we downloaded earlier, and linking the existing (ClassWorlds) classloader as the parent of the new hierarchy. It looks like this:
When used this way, the parent ClassWorlds classloader is able to correctly resolve classes and resources. The Jetty6 Maven Plugin is therefore able to automatically provide the correct JSP version at runtime without necessitating any user configuration.