Monday, May 21, 2012

ServiceMix/CXF Development in a Heterogeneous JVM Environment

When developing ServiceMix deployed CXF applications for a heterogeneous JVM environment,  there are some known steps that need to be taken to avoid incompatibilities between the JVM providers.  This typically involves overriding the JVMs implementation of JAXP with a constant and stable version of Xerces using the JVMs endorsed directory capabilities.  If you are in a large enterprise or distributed environment this can lead to project overhead though when you start adding up all the developer, test and production environments.  I faced this very issue and with the use of Maven was able to reduce this overhead to something much more manageable.  Below are those steps and hopefully they will help you all out.

First we need to establish what version of Xerces that will be used by our projects.  My recommendation is to use the version of Xerces that is defined by the version of ServiceMix you will be using.  Then make the version a defined property in your parent so we can change it in one place and it will be picked up by all our projects.

Next we have to establish a consistent location for our endorsed development directory to place our chosen version of Xerces.  If you have a large project with many modules and sub-modules this can be problematic.  To overcome this I used the Maven Dependency Plugin to copy the necessary libraries into a common location for each project.  The definition for this plugin is below:


The keys to the configuration above are ensuring the plugin runs during the process-resources phase and setting the version and the outputDirectory plugin configuration elements.  Setting the version allows you to control what version of the dependency is used otherwise you are leaving it up to the plugin to determine it which can be inconsistent.  I also overrode the default location of the output directory setting it to ${project.build.directory}/endorsed for readability.  This gives me a consistent endorsed location at a project level which will become important when we start to compile and test our projects.  Finally, I typically define this plugin it in my parent as a managed plugin and then reference in the build plugins on a project by project basis.  Otherwise this plugin will execute even for POM projects which is really unnecessary overhead.

The next step is to tell the compiler to use our endorsed directory as shown below:



We do this by setting the compiler plugins compilerArgument configuration parameter to -Djava.endorsed.dirs=${project.build.directory}/endorsed.  Defining this ensures all our projects will use the locally created endorsed directory when compiling our main and test code.

The next part of our Maven configuration involves setting up the Maven CXF Codegen Plugin to deal with a known issue with the IBM JVM.  We need to pass in our Xerces implementation as a dependency to the plugin as shown below:


The known issue involves the IBM JVM throwing the following error:
org.apache.xerces.impl.dv.DVFactoryException: DTD factory class org.apache.xerces.impl.dv.dtd.DTDDVFactoryImpl does not extend from DTDDVFactory
Typically we see this configuration as a IBM JDK profile in other Maven projects.  Given that we are making our all our projects use a consistent version of Xerces, this can now be the default.  We no longer have to worry about creating a separate profile for the various JVMs.

Now on to the the test phase of our projects.  Given that we are compiling against an endorsed library, we need to provide that same endorsed directory to phases in our project that execute our code base.  As such we need to make the endorsed directory available to our Maven Surefire (test phase) and Failsafe (integration test phase) Plugins. The example of how to configure the Maven Surefire Plugin is below (it is the same for both plugins):


The first property in the argLine configuration element is the system property to override the default endorsed JVM directory.  We have to go a step further with an executable though and tell the JVM which implementation of the JAXP factories we want the JVM to use.  This is done by setting the following system properties in the argLine configuration element also:
  • DocumentBuilderFactory
    • -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
  • DatatypeFactory
    • -Djavax.xml.datatype.DatatypeFactory=org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl
  • SAXParserFactory
    • -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl
Gotcha warning: Do not wrap the argLine configuration property as some versions of the plugin do not support line wrapping and blow up the plugin.

Finally we need to make sure our version of ServiceMix is using the endorsed version of Xerces and the factories we want to override.  Some older versions of ServiceMix do not define an endorsed directory so check to make sure one is available, it has the version of Xerces we are using and is referenced in the ServiceMix startup scripts.  With that, ServiceMix does not override the JAXP factories though so this is something we need to take care of ourselves.  This is accomplished by exporting the JAVA_OPTS environment variable with the system property factory overrides we specified above for our unit and integration tests.

Once all the steps above are finished, you should be able to build and run your CXF codebase in ServiceMix against any current JVM.

2 comments: