The Source for Java Technology Collaboration
User: Password:



Kohsuke Kawaguchi

Kohsuke Kawaguchi's Blog

HOW-IT-WORKS: Running JAXB/WS 2.1 tools on JDK 6

Posted by kohsuke on February 06, 2007 at 09:59 AM | Comments (3)

This is a follow up to my previous post.

A reader asked how my "danegrous classloader trick" works, and another reader asked if he can use this technique to run his applications (with JAXB 2.1) on JavaSE 6. I've got a few internal people who had the same question, so I'm going to explain how it works today.

The following picture illustrates the basic design:

When our code gets the control of the execution for the first time, the "surrounding environment" already creates a few classloaders for us. The surrounding environment is Ant when we are invoked as an Ant task, and it's Java launcher (the code that invokes your main method) when we are invoked as a CLI tool.

You can't really assume too much about how those classloaders are set up, but generally speaking it does the following:

  1. It can load JAXB API classes. It could be either 2.0 (like if the user runs Ant on SE6), 2.1, or a mixture.
  2. It can load JAXB RI classes (otherwise our code will never gets executed in the first place)
  3. For all the classes that it can load, it can also locate .class files as resources.

In the above picture, these existing classloaders correspond to the bootstrap classloader and the cloud on the right. It's a cloud because we really shouldn't be assuming too much about the structure of these classloaders. The third assumption is not necessarily true (and that's one of the reasons why this is dangerous), although it's true in a reasonable environment like Ant, Maven, containers, etc. We need to make this assumption for this scheme to work.

The first thing we check is if this classloader can load everything we need correctly. So we first check if it's loading JAXB API 2.1. Doing this requires us to find a class that has been around since 2.0 (or better yet 1.0), with a method newly added in 2.1. We can't just check the existence of a class newly added in 2.1, because that would fail to diagnose the situation correctly if 2.0 API is in the bootstrap but 2.1 API is in other classloaders.

If this check detected that our environment is already loading JAXB API 2.1 for us, then there's nothing else needed. We just proceed with the rest of processing.

But if this check detected that our environment is loading 2.0 API, then the trick starts. First, we need to create a classloader that selectively disables delegation to the parent classloader (see Masking CL above.) Such a classloader allows another copy of JAXB API to be loaded below. This masking classloader needs to hide JAXB API and everything that depends on it (like all the RI code) — for example, if you let the RI loaded by the existing environment, then those classes would end up seeing the JAXB API in the existing environment, not the JAXB API below the masked classloader.

Then we create an URLClassLoader beneath it, by specifying the jar files of the JAXB 2.1 APIs. We are assuming that the JAXB 2.1 API class files are available somewhere in the envronment (like in the application classloader if this is CLI, or some AntClassLoader if this is Ant.) They are just not loaded because 2.0 API in the upper classloader takes precedence. To find out where those 2.1 jars are, we do ClassLoader.getResource("javax/xml/bind/annotation/XmlSeeAlso.class") (XmlSeeAlso is a new class in 2.1.) This likely returns a jar URL like jar:file://path/to/jaxb-api-2.1.jar!javax/xml/bind/annotation/XmlSeeAlso.class, so from there inferring the actual API jar URL is easy (and this is another reason why this is dangerous; URL need not be a jar URL, although they normally are.)

And now that we have a classloader that can load JAXB API 2.1 without interference from the existing environment, we create another classloader (see ParallelWorld CL above) to load the rest of the RI. For this we don't really need to locate where those jar files are. We can simply ask the existing environment to find the class files for us. All this classloader needs to do is to load the classes in this classloader, instead of delegating the loading to the parents. Got it?

At this point the end result is that if we use the parallel world classloader, it can load all 2.1 APIs and RIs correctly. So we finally move the execution to the entry point inside this classloader. Phew.

Can you use this for your app?

The same technique can be used by your application, yes. If you think about it, the JAXB RI is just a bunch of code that relies on the JAXB 2.1 API, so in that sense it's the same as your code.

The tricky part is that this technique ends up loading two sets of classes. The existing environment can most likely load your application classes, and this parallel classloader will load them again, too. Even though they are of exact same definition, as far as JVM is concerned they are different classes.

So passing values between the code that lives outside in the existing environment and the code that lives inside the parallel classloader becomes problematic. In case of XJC, the only thing being passed around is String[] (for CLI) and XML infoset (for Ant tasks), which are both defined outside XJC. So this was a non-issue.

But if types defined in your code are passed around, then you need to set the masking right so that you'll only get one copy of those classes. That means those classes won't be able to have any reference to the JAXB API.

This scheme also relies on the assumption that masking classloader can block the access to the bootstrap classloader, so it's likely to fail in more complicated classloader set up like classworlds or IDE plugins, where classloaders don't form a tree structure.

Anyway, this is how it works. I'm sorry for a lengthy post, but I didn't have time to write a shorter one! The implementation of these classloaders can be seen in istack-commons


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • You know the problem really affected a lot of people when a barely-out-of-highschool punk teaching himself to program (like me) has had to deal with this problem. Because of the inability to easily use and deploy JAX 2.1 on Java 1.6 I have had to drop use of it altogether in order to support both JDK 1.5 and JDK 1.6. I could have built my application twice, once for Java 1.5.x and once for Java 1.6.x but I still have the problem of not having WSIT support and MOTM

    Posted by: benju on September 24, 2007 at 03:41 PM

  • Hi Koshuke, I am experiencing the same problem. I developed my webservices server in NetBeans 5.5 on my localhost (Apache Tomcat 6.0) and i pointed to 2.1 APIs in classpath to the ones that come with NetBeans IDE and its working perfectly fine.
    But when i try to take the war file and put in our server and deploy it which also has Tomcat 6.0, i am getting the same linking error that says 2.0 API is being loaded and it needs 2.1 APIs. Let me tell you what i have done to resolve this in our server.
    1.I put the JAX 2.1 jars in my JAVA_HOME\lib\endorsed folder and put it in my class path.(which is the fix that many people prosposed and even the docs that i downloaded with JAXWS-2.1 APIs suggested)
    2.I have also put the 2.1 jars in my Tomcat\lib folder
    3.I have also set the shared.loader property to point to the location where my 2.1 APIs live (ie Tomcat\lib) But for some reason it just doesnt work. Any help would be greatly appreciated. Sorry for the big post as i just wanted to explain things clearly.
    Thanks, Vivek

    Posted by: vivshan on September 26, 2007 at 08:16 AM

  • If you use netbeans and tomcat, you should look at http://wiki.netbeans.info/wiki/view/FaqEndorsedDirTomcat

    Posted by: ofical2 on November 14, 2007 at 02:36 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds