Thursday, 30 October 2008

OSGI, GigaSpaces and Buddy Classloading

I've been doing a reasonable amount of GUI development using Eclipse Rich Client Platform recently. This is a great framework and getting better. In the Eclipse 3.1 dark days I got a bit burned trying to build applications, largely because the development support for RCP within Eclipse was itself a bit flakey. There's nothing more galling than spending half a day trying to diagnose a problem only to find that restarting Eclipse clean sorts it all out. But I digress...

Eclipse RCP and Plugin Architecture

I won't wax too lyrical about the benefits of the Eclipse plugin model. If you are a Java developer you probably already understand the great value of being able to drop in plugins from around the web and rely on Eclipse to run with simultaneous multiple versions of the same jar. At my company, PSJ, we've been developing a number of operations console GUIs that interact with application services and the GigaSpaces implementation of JavaSpaces. The plugin model provides a great basis for us to build small UI plugins that interact with different service components we've developed over the years. Using Spring we're then able to wire together our re-usable plugins and UI pieces with custom components written in the context of an engagement into a customer-specific GUI.

Eclipse's ability to manage multiple versions of classes concurrently is ultimately down to the multiple classloader OSGI mechanism that underpins the plugin architecture. So far so triffic, however if you've ever tried to use plugins with technologies like Spring, JavaSpaces even Log4j, you'll have encountered ClassNotFoundException problems galore. Reading around the web when I first hit this a couple of years ago the recommendations were to hack in and manipulate the classloaders in application code.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
ClassLoader thisClassesLoader = this.getClass().getClassLoader();
// do your stuff here
Not only was this pretty horrible, but it's very difficult to make bombproof as you've got to track down all points where the issue can occur and problematic when using technogies like GigaSpaces that use internal thread pools even for synchronous calls and deliver events to you on their own threads.

In revisiting this topic again recently in some work for a client, I'm grateful to my colleague John Nichol who has shown me the one true way: buddy classloaders. Like many things with Eclipse RCP the documentation of this is vanishingly thin and largely what I call "non-doc" - you know the kind of thing:
"To press button B, click the B button"
Arggh! Anyway after trawling around the web and single-step debugging within the OSGI class resolution code, I have mined the following nuggets of true knowledge.

Why is ClassNotFound in the First Place?

If you are using technologies like Spring or GigaSpaces, they both need access to the classes in your application code - in one case to instantiate instances and wire them together, and in the other to store instances in a sharable in-memory location on the network. If you put the Spring and GigaSpaces jar files inside your plugin you can't easily share instances of objects across plugins. The solution to this is to create independent plugins for Spring, GigaSpaces, Log4j etc and then build dependencies between your higher level plugins and these lower level ones. So far so good, but this is where you can hit ClassNotFoundException. Let's say Spring needed to instantiate an instance of your application class Foo. Well Foo isn't in the Spring plugin and you're screwed. You could also wodge all the application code, Spring, Gigaspaces jars together into one big plugin, but then really you've lost the advantage of component separation you were trying achieve in the first place. You'd also be back to manually manipulating the thread context classloaders. So, you're screwed, right...

Buddy Classloading

Fortunately to get you out of this jam Eclipse has a mechanism called buddy classloading. This lets you add directives to the plugin manifests to selectively delegate classloading to other friendly plugins. Add the following line:
Eclipse-BuddyPolicy: registered
To the lower-level Spring, GigaSpaces plugin manifests. This tells those plugins that they can delegate class-loading to any plugin that registers with them. You also need to add directives to your application-level plugins to perform registration with lines in their manifests like:
Eclipse-RegisterBuddy: org.springframework,com.gigaspaces, org.apache.log4j,org.apache.commons
Going back to our Foo class example earlier, when the Spring plugin tries to instantiate a Foo instance it will fail to resolve the class from its own plugin and will then attempt to resolve from any buddies that it knows about. Foo therefore gets resolved from the application plugin that is registered with the Spring plugin. This happens no matter what thread is attempting to resolve the class and doesn't suffer from the holes that context classloader manipulation suffered from.

Direction of Buddy Registration

One point to make clear here (coz it tripped me up when I was trying to get my feeble brain around it all) is the direction of registration. I had originally thought from reading the non-doc that registration was spring, gigapsaces plugins registering with the application plugin. Actually it's the reverse. Application plugins register with spring, gigaspaces because they want their classes to be accessible to those generic technologies for instantiation purposes. The confusion arises because only the application plugin can specify which generic plugins it wants to register with and the manifest entry Eclipse-RegisterBuddy implies (to me at least) that the list that follows the entry is registering with the current plugin.

No comments: