September 16, 2014

How To Hide Dependency Libraries in an OSB Callout JAR

How to place dependency libraries into a JAR and hide them from the callout dialog.

Sometimes we have to use OSB’s Java callouts. On many occasions the callout Java code requires the use of external libraries (dependency JARs).

Here comes the problem: how to deploy these dependency JARs?

Should we deploy the dependencies as JAR resources right into the OSB project?

Or should we package them into the callout JAR itself?

I found a relatively simple solution that may be the best of both worlds.

Packaging Inside a JAR… Not That Easy.

For my JSON-to-XML-for-OSB library, I needed to use Argo JSON JAR.

However, I did not want my library’s users to deploy the Argo JAR separately because:

  • It is easy to overlook this requirement.

  • It adds an extra step for the deployment team, which increases the risk of defects.

  • It creates a greater risk for conflicts with Weblogic-level JARs if any application already uses a different version of Argo.

My choice was to package Argo right inside of my JAR.

In my build script I have unpacked the Argo JAR into the build directory, and then packaged it into my own JAR along with my own classes. Easy, eh?

But when I tried to use the JAR in the Callout step, instead of the single class I intended to expose, I saw dozens of classes.

HiddenClassesBefore

OSB found every single class with public static methods (which are used a lot in Argo) and populated the dialog with them.

My small Convertor class is lost in this list.

Solution: Nested Classloader

How to hide Argo classes?

At first I considered updating the Argo code to get rid of the public static methods, i.e., move them into a package-visible area. The change, though, proved to be too big, and I would have had to do the same work every time I needed to update Argo in my project.

This was not a strategically sound approach.

Then I realized that if the Argo classes do not look like classes, then OSB won’t pick them up. I could, for instance, rename them as *.bin.

Then, of course, JVM itself won’t be able to pick them up, too… by default. I can load them as classes if I install my own context classloader.

A Design is Born

And so the following design was drafted:

  1. OSB calls a Convertor’s method toXML() or toJSON().

  2. The Convertor class installs my custom classloader InnerClassLoader for the current thread.

  3. Convertor then calls ConvertorInner, which does all the actual work.

  4. When any Argo classes are needed, InnerClassLoader loads them as a resource with a *.bin extension and defines them as classes.

  5. Upon exit, the Convertor class removes my classloader from the current thread.

And look at the Callout dialog now!

HiddenClassesAfter

The InnerClassLoader, though, is still shown. Unfortunately, the ClassLoader API has a few public static methods, and I do not see an easy way to get rid of them.

I can live with it.

I have also included a simple hint for those who are trying to import the wrong class :)

HiddenClassesAfterHint

Performance Considerations

Re-loading and re-defining Argo classes on every request or on every new thread would have a large negative impact on performance.

To avoid this impact, only a single instance of the InnerClassLoader is used, and it is shared between all threads.

class InnerClassLoader extends ClassLoader {
    private static InnerClassLoader icl;

    public InnerClassLoader(ClassLoader parent) {
        super(parent);
    }

    static void install() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if( cl instanceof InnerClassLoader ) return;

        if( icl == null ) icl = new InnerClassLoader(cl);
        Thread.currentThread().setContextClassLoader(icl);
    }

To protect against race conditions, the loadClass() method is synchronized, and the first thing it does is to check if the class was already loaded in another thread.

Conclusion

There are a few gotchas. For instance, the call from Convertor to ConvertoInner cannot be static, otherwise JVM will try to resolve ConvertorInner dependencies, which include Argo classes. Since my classloader is not installed yet, it ends with NoClassDefFound. I have to use reflection.

But otherwise it was a pretty simple implementation:

  • One entry class.

  • One classloader.

  • One inner class.

  • Dependency classes renamed as *.bin.

Feel free to review the details at OSB-to-XML-for-OSB public repository.

Vladimir Dyuzhev, author of GenericParallel

About Me

My name is Vladimir Dyuzhev, and I'm the author of GenericParallel, an OSB proxy service for making parallel calls effortlessly and MockMotor, a powerful mock server.

I'm building SOA enterprise systems for clients large and small for almost 20 years. Most of that time I've been working with BEA (later Oracle) Weblogic platform, including OSB and other SOA systems.

Feel free to contact me if you have a SOA project to design and implement. See my profile on LinkedIn.

I live in Toronto, Ontario, Canada.  canada   Email me at info@genericparallel.com