JAX-RS applications in OSGi – whiteboard style

There are various ways to publish a JAX-RS application when running inside OSGi. Some use Blueprint or Spring, and I’m sure there are other methods. I’d like to show how you can publish JAX-RS applications using the whiteboard pattern.

The nice thing about JAX-RS is that it offers the abstract “Application” class. So, why not just put your application in the OSGi service registry? That’s very simple and you don’t need to know anything about the JAX-RS implementation. Let someone else worry about how to get the JAX-RS application into the web container at the right place. Here’s how your Application subclass could look like:

 

public class ContentRepositoryApplication extends Application {

    private static final Set<Class<?>> CLASSES
        = new java.util.HashSet<Class<?>>();

    static {
        CLASSES.add(RootResource.class);
        CLASSES.add(DocumentMetadataResource.class);
        CLASSES.add(DocumentResource.class);
        CLASSES.add(RepositoryResource.class);
        CLASSES.add(URIMessageBodyWriter.class);
    }

    @Override
    public Set<Class<?>> getClasses() {
        return CLASSES;
    }
}

 

I’d like to demonstrate how to do the publishing part with Jersey (the reference implementation). It’s particularly easy with this one. Actually, I’ve tried and failed with both Apache CXF and Apache Wink (Incubating), probably mostly for lack of in-depth knowledge on how to build a servlet instance from an Application instance.

What we need is simply a ServiceTracker that tracks Application services in the OSGi registry. Whenever a new Application becomes available, build a servlet for it and register that with the OSGi registry. A whiteboard-capable web container can then pick up the servlet and expose it. The code is quite straight-forward:

 

package ch.jm.rest.jersey;

import java.util.Dictionary;
import java.util.Hashtable;

import javax.servlet.Servlet;
import javax.ws.rs.core.Application;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

import com.sun.jersey.spi.container.servlet.ServletContainer;

/**
 * Bundle activator for the Jersey-based JAX-RS Application deployer. It tracks all JAX-RS
 * {@link Application} services and publishes a servlet for each of them. The "alias" service
 * property is propagated to the servlet service so you can control the endpoint for the JAX-RS
 * service. This follows the whiteboard pattern. Similarly, we publish servlet services
 * in whiteboard style.
 */
public class Activator implements BundleActivator {

    private Log log = LogFactory.getLog(Activator.class);

    private BundleContext context;
    private ServiceTracker tracker;

    /** {@inheritDoc} */
    public void start(BundleContext context) throws Exception {
        this.context = context;

        //Track all JAX-RS Applications
        this.tracker = new ServiceTracker(context,
            Application.class.getName(), new Customizer());
        this.tracker.open();
    }

    /** {@inheritDoc} */
    public void stop(BundleContext context) throws Exception {
        this.tracker.close();
        this.tracker = null;
        this.context = null;
    }

    private class Customizer implements ServiceTrackerCustomizer {

        private Dictionary createProps(ServiceReference reference) {
            String alias = reference.getProperty("alias").toString();
            if (log.isDebugEnabled()) {
                log.debug("Alias: " + alias);
            }

            Dictionary props = new Hashtable();
            props.put("alias", alias);
            return props;
        }

        /** {@inheritDoc} */
        public Object addingService(ServiceReference reference) {
            Application app = (Application)context.getService(reference);
            if (log.isDebugEnabled()) {
                log.debug("Adding JAX-RS application: " + app);
            }

            //For each JAX-RS Application, create a servlet wrapping that Application instance
            ServletContainer servlet = new ServletContainer(app);

            Bundle sourceBundle = reference.getBundle();
            BundleContext sourceContext = sourceBundle.getBundleContext();
            ServiceRegistration reg = sourceContext.registerService(
                    Servlet.class.getName(), servlet, createProps(reference));

            return reg;
        }

        /** {@inheritDoc} */
        public void modifiedService(ServiceReference reference, Object service) {
            ServiceRegistration reg = (ServiceRegistration)service;
            if (log.isDebugEnabled()) {
                log.debug("Modifying JAX-RS application: " + reg);
            }
            reg.setProperties(createProps(reference));
        }

        /** {@inheritDoc} */
        public void removedService(ServiceReference reference, Object service) {
            ServiceRegistration reg = (ServiceRegistration)service;
            if (log.isDebugEnabled()) {
                log.debug("Removing JAX-RS application: " + reg);
            }
            reg.unregister();
            context.ungetService(reference);
        }

    }

}

 

Please note how the servlet service is published under the BundleContext of the bundle that published the Application service. That’s not really necessary but keeps things in their context.

That’s all I put in that deployer bundle, just this BundleActivator. That already does the whole job in the Jersey case.

This approach may not work for everyone. Like with the whiteboard approach for servlets, there may be some restrictions of what you can do. But so far it has served me well.

One catch I ran into was multipart support. The old story. I was forced to introduce a hard dependency on Jersey’s multipart support in one of my JAX-RS applications. Unfortunately, Jersey Core 1.5 exports the JAX-RS API directly instead of in a separate bundle, and hasn’t assigned versions to exported packages. That would make it difficult to use its multipart support with Apache CXF (it properly imports the JAX-RS API with a version range). Maybe I should look at doing it the other way around (use CXF’s multipart with Jersey). But to deploy the huge CXF package including dependencies just for getting multipart support (as long as I don’t manage to create a JAX-RS servlet) is probably not the best of ideas. BTW, the problem with CXF is that I didn’t find a way to instantiate/configure the servlet without having to rely on web-inf.xml for the initialization parameters. Probably just lack of knowledge. If you have a suggestion, I’m all ears.

I think it would be cool if all JAX-RS implementations that strive to be OSGi-compatible implemented such a whiteboard support for JAX-RS Applications. All you have to do as a user is register your Application object with the OSGi registry. Ideally, you don’t have a dependency on any JAX-RS implementation. I’m still hoping for that multipart support in the next JAX-RS API version.

Tagged with: , , ,
Posted in Java, OSGi
2 comments on “JAX-RS applications in OSGi – whiteboard style
  1. jbr says:

    nice post,
    but I wonder if the reg.setProperties() in modifiedService() and the reg.unregister() in removedService() are correct.
    I think that the ‘reg’ object should be the one that was returned from the register call in the addingService() and not the one passed to these methods…

  2. Jeremias Märki says:

    Please take another look. It is like you say it should be. The “service” parameter is cast to the ServiceRegistration that was created in “addingService(). Then, it is modified or unregistered.