JAR Service Provider Abstraction

The well-designed class loader isolation is one of the prominent features of OSGi. Unfortunately, this introduces a problem for the JAR Service Provider mechanism found in the JAR specification. This mechanism relies on the hierarchical organization of class loaders of traditional Java applications. This won’t work in OSGi as the plug-in host won’t see (and won’t know about) and plug-ins also present in an OSGi-enabled system.

I’ve started looking into this topic because I needed to make Apache FOP work in an OSGi environment. The Apache XML Graphics project (Batik & FOP) make extensive use of the JAR Service Provider mechanism. For example, every output format in FOP is actually a plug-in which is detected via this mechanism. The are many more extension points like that. The goal for me was to find a solution that would work in plain Java environments and in OSGi environments. Unfortunately, it was quickly apparent that this won’t be possible without changes in the products of the Apache XML Graphics project. A number of blog entries found on the net describe the problems and try to find solutions. They helped me a lot when looking for a solution that I was happy with.

The result of my efforts is presented here and published under the Apache License v2.0.

Download page

Please read the README.txt file contained in the distribution. It explains all the details.

Usage Example

The following code example shows how to use the package.

public class ExampleServiceClassConsumer {

    //volatile used because we do copy/replace rather than use synchronization
    private volatile Collection providers = new java.util.ArrayList();

    public ExampleServiceClassConsumer() {
        ServiceProviderListener listener = new ServiceProviderListener() {

            public void providerAdded(Class clazz) {
                register(clazz);
            }

            public void providerRemoved(Class clazz) {
                unregister(clazz);
            }

        };
        ServiceTracker tracker = Services.getInstance().getServiceTracker(
                ImageReaderSpi.class);
        tracker.addServiceProviderListener(listener);
    }

    private void register(Class provider) {
        //Use copy and replace to avoid synchronization
        Collection newList = new java.util.ArrayList(this.providers);
        newList.add(provider);
        this.providers = newList;
        System.out.println(provider.getName() + " added.");
    }

    private void unregister(Class provider) {
        //Use copy and replace to avoid synchronization
        Collection newList = new java.util.ArrayList(this.providers);
        if (newList.remove(provider)) {
            this.providers = newList;
            System.out.println(provider.getName() + " removed.");
        }
    }
} 

This class creates a ServiceProviderListener which gets notifications when a service provider class is added or removed (becoming unavailable). In the second step a ServiceTracker (not the OSGi variant!) is acquired for listening for ImageReaderSpi implementation classes in this case. Once you add the service listener to the service tracker you’ll get notifications on any available service providers.

Please note the requirement for thread-safety of the listener code. Any thread may call the listener methods at any time.

Some notes

For performance reasons, the OSGi extender built into the package only inspects bundles that have the following manifest header:

JAR-Service-Provider: true

That requires any bundle that has service providers that should be detected by this package to carry the above header entry. This restriction can be disabled by setting the following system property: -Dch.jm.util.services.osgi.checkAllBundles=true

Apache Aries offers a similar solution with SPI Fly. However, this solution is based on the class java.util.Services which is only available since Java 6. My solution also works with Java 5. I’m working on a successor to the above which uses parts of SPI Fly but offers more dynamics (the listener) and Java 5 compatibility. This actually already works in production but I haven’t been able to find time, yet, to publish it. Stay tuned.