3

I’m trying to use JAXB 2.2.11 in an osgi environment (Liferay DXP). I am having issues creating a JAXBContext. Based on some other sources found while researching like this and this, I have determined that in an osgi container I need to provide the correct classloader for JAXB to instantiate the context. So I have code like this:

ClassLoader cl package.with.jaxb.objects.ObjectFactory.class.getClassLoader(); JAXBContext jc = JAXBContext.newInstance("package.with.jaxb.objects ", cl);

This code causes a null pointer exception with the following stack trace:

Caused by: java.lang.NullPointerException
    at javax.xml.bind.ContextFinder.handleClassCastException(ContextFinder.java:129)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:201)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:146)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:371)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:446)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:409)

Looking at the source for ContextFinder I can see that context must be null on line 129:

throw handleClassCastException(context.getClass(), JAXBContext.class);

I thought perhaps the problem was that my module has a dependency on jaxb-api 2.2.11 but the jaxb-impl classes are provided by rt.jar at runtime and are probably newer than 2.2.11 because Liferay DXP runs on JDK 1.8. To get around this issue, I have tried including jaxb-impl.jar 2.2.11 as a dependency in my osgi module, thinking then the jaxb-api & jaxb-impl versions would match. After that, trying to create a JAXBContent using the same code as above results in the following error:

ClassCastException: attempting to cast jar:file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar!/javax/xml/bind/JAXBContext.class to bundleresource://623.fwk616113009:13/javax/xml/bind/JAXBContext.class. Please make sure that you are specifying the proper ClassLoader.

By the looks of this message, the JAXBContext that is getting instantiated is from the version of JAXBContext that is loaded via rt.jar. This is very confusing to me because I would expect the version of JAXBContext loaded by my module’s classloader to be used since I’ve included jaxb-impl.jar in my module and I’ve specified my module’s classloader is the one to be used in my call to JAXBContext.newInstance. Can anyone shed some light on how I can get jaxb 2.2.11 to work in an osgi container?

*Please note that I can’t upgrade the version of jaxb-api used by my module because the JAXB code is actually in a 3rd party jar that requires jaxb 2.2.11 (I have just eliminated the 3rd party jar from the equation for now by writing some test JAXB code).

clav
  • 4,221
  • 30
  • 43

3 Answers3

5

After extensive research I found the following solution. Since it seemed like passing the bundle class loader as suggested by the accepted answer in this post had to be correct, I followed the path of figuring out why I was getting a NullPointerException when I tried that. After carefully looking over the source code for jaxb-api to follow the stack trace of the NullPointerException, I could see that the jaxb-api code does things like

classLoader.loadClass("com.sun.xml.internal.bind.v2.ContextFactory")

where classLoader is my bundle's class loader (since that's what I passed in) and ContextFactory is actually a class in jaxb-impl which is loaded by the bootstrap class loader. This is where the problem lies because my bundle's classloader isn't going to be able to see classes loaded by the bootstrap class loader. This threw me for a while because I'm not used to how class loaders work in osgi. I incorrectly was thinking the classes loaded by the bootstrap class loader would be visible because I'm used to web app class loading where there is delegation. In osgi class loaders are completely isolated from each other, things are only visible if they are exported. To get around the issue I found some helpful posts talking about similiar issues. It turns out there is a concept called boot delegation in osgi where you can specify a list of classes/packages to always be loaded via the bootstrap classloader. So the end result is two steps:

1) Switch the thread's class loader to your bundle class loader before calling the code to get the JAXBContext:

ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

try {           
    // ObjectFactory here is in the same package as my classes to be marshalled
    ClassLoader objectFactoryClassLoader = ObjectFactory.class.getClassLoader();            
    Thread.currentThread().setContextClassLoader(objectFactoryClassLoader);
    // JAXB code goes here
} finally {
    Thread.currentThread().setContextClassLoader(currentClassLoader);
}

2) Specify packages to be loaded using the boot delegation mechanism. This list needs to include the transitive dependencies of the classes you need loaded. In my case, I'm using Liferay so the list is specific to Liferay and it goes in portal-ext.properties configuration file. Luckily I found this post where someone had done most of the work for me:

module.framework.properties.org.osgi.framework.bootdelegation=\
  __redirected,\
  com.liferay.aspectj,\
  com.liferay.aspectj.*,\
  com.liferay.portal.servlet.delegate,\
  com.liferay.portal.servlet.delegate*,\
  com.sun.ccpp,\
  com.sun.ccpp.*,\
  com.sun.crypto.*,\
  com.sun.image.*,\
  com.sun.jmx.*,\
  com.sun.jna,\
  com.sun.jndi.*,\
  com.sun.mail.*,\
  com.sun.management.*,\
  com.sun.media.*,\
  com.sun.msv.*,\
  com.sun.org.*,\
  com.sun.syndication,\
  com.sun.tools.*,\
  com.sun.xml.*,\
  com.yourkit.*,\
  org.eclipse.persistence.internal.jaxb,\
  org.eclipse.persistence.internal.jaxb.*,\
  javax.xml.*,\
  sun.*

Helpful links:

Why can't JAXB find my jaxb.index when running inside Apache Felix?

What is the difference between bootdelegation and DynamicImport-Package in osgi

https://web.liferay.com/web/user.26526/blog/-/blogs/liferay-dxp-and-weblogic-

https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/bundle-classloading-flow

http://apache-felix.18485.x6.nabble.com/Classloading-for-JAXB-td4834670.html

clav
  • 4,221
  • 30
  • 43
1

Here is the workaround that worked for me using JDK 11, Liferay DXP/7.2, OSGI, with a sample Jax-RS web service created from Dev Studio. The error I was getting was as follows when trying to access the web service:

JAXBException occurred : Implementation of JAXB-API has not been found on module path or classpath.. com.sun.xml.internal.bind.v2.ContextFactory cannot be found by org.apache.aries.jax.rs.whiteboard_1.0.4.

What worked for me was to define the context factory at the system level to override the predefined context factory. Add the following system variable to your system

javax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory

For example you can add this in your setenv.sh/bat file in Tomcat, or in eclipse you can access your server Launch Configuration, Arguments tab, under VM arguments

-Djavax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory

This worked without adding any extra libraries since Liferay already has those libraries included.

How does this work? Refer to the javadoc for JaxBContext and read the Discovery of JAXB implementation Section. Using the /META-INF/services/javax.xml.bind.JAXBContext file did not work for me.

I hope this helps someone. One final note for DXP users, if you get a permission denied on your service then you need to read about Service Access Policies

Carlos
  • 21
  • 2
0

The best place to see how it should be done is Apache Karaf. It doesn't install any JAXB-API bundle - instead it uses org.apache.servicemix.specs.jaxb-api-2.2-2.7.0.jar inside lib/endorsed directory.

This way you won't use JAXB-API provided by rt.jar.

For implementation - it's best to use ServiceMix version of JAXB bundles:

  • org.apache.servicemix.bundles:org.apache.servicemix.bundles.jaxb-impl:2.2.11_1
  • org.apache.servicemix.bundles:org.apache.servicemix.bundles.jaxb-xjc:2.2.11_1
Grzegorz Grzybek
  • 6,152
  • 3
  • 29
  • 42
  • Thank you for answering Grzegorz. Unfortunately, as I mentioned at the end of my post I don't actually have control over how the JAXB code is written because it's in a 3rd party library I'm using called STIX (https://github.com/STIXProject/java-stix) and the STIX lib is using jaxb-api. – clav Oct 04 '17 at 12:41