Monday, May 26, 2008

Java EE specs in OSGi

For ServiceMix 4, I've been working on making sure the Java EE specifications can be used in OSGi. The first step was to release OSGi versions of the various specifications by just adding the needed manifest entries to make them usable in OSGi. This was done inside the Geronimo project (on which I am a committer). This means that since a few months, most of the Java EE specification jars are available as OSGi bundles (you can grab those from maven 2 public repositories.

However, this is not always sufficient. Some of these specifications (mostly JAXB, SAAJ, JAX-WS and Stax) do not work well in OSGi. The mean reason is that the implementation is discovered using different mechanisms by the API. The most commonly used one is to find a file on the classpath in the META-INF/services directory and find the main entry point class of the implementation. Unfortunately, to make this work in OSGi, the client bundle (the one using one of these APIs) has to have the required file in its classpath, which means the inability to use one provided by the runtime in which the bundle is deployed and that it can not be switched without changing and re-compiling the bundle. Another way would be to add a Require-Bundle OSGi manifest so that the classpath of the implementation becomes part of the client bundle, but this also ties the client bundle to the implementation used.

The solution came to me after a chat with Dan Kulp: an OSGi specific discovery mechanism can be easily plugged into these spec jars. It consists in two small classes shared amongst these spec jars: an OSGi bundle activator and another class not tied to the OSGi api that maintain a list of available implementations. The final step if to rewrite the factory of those jars to look inside this map before performing the usual lookup.

This means that in a non OSGi environment, the jar behaves as usual, but when deployed into an OSGi runtime such as ServiceMix Kernel, the spec bundle will be able to locate dynamically the implementation to use. Therefore, the client bundle using the spec jar is now free of any requirements.

Nice work! You are addressing a real problem for many of the factory patterns in Java wrt OSGi/strict modularity.

If you can change the spec JAR, then I think there is an even nicer possibility. Instead of prescanning the bundles, you can just also look in the OSGi service registry when the factory is used. An independent bundle will then do the scanning of the implementation jars and register implementations under their factory interface. This scanning bundle is shared between ALL different META-INF/services users. If all spec starts to scan all bundles, this might become expensive.

Also, this would allow implementation JARs to just register their implementations directly. Which opens all kind of nice possibilities like remote implementations, etc.

I agree with you. This was discussed on the Geronimo list a while back, http://www.nabble.com/Geronimo-specs-jars-in-OSGi-td16726438s134.html.

There, Guillaume pointed out the added benefit of running multiple versions, e.g. jaxb 2.0 / 2.1, concurrently. The problem is with legacy code that would not have the requisite service registration code.

I think the best way of finding an implementation would be that the implementation factory simply registers itself as an osgi service. So we do not need any scanning. You simply look up which services offer the given spec (interface).

But I also think that in many cases it is perfectly in order that you simply package jars like an XML Parser with each bundle that needs it. I do not think that OSGI services have to be used for fine granular things. They are much more useful for modularizing your business aplication than for basic libs.

I know that's been close to a year after you posted this in your blog, but there is a bug in the implementation that reads from "META-INF/services/*" as it is not able to handle comments in the file, should trim the input and move to the next line if (once these ops were executed) the line has a space or tab or is zero length.Of course, the best would be to use ServiceLoader, but I do know that that is not an option as the requirement is just too high.