4.23.2012

OSGi, Jersey and Jetty

This is a mini how-to on setting up Jersey (JAX-RS) with Jetty running in OSGi. I wouldn't necessarily say that this the recommended setup, and I encourage comments for better ways of doing what I've done.

First off if you're not familiar with Jersey and JAX-RS, I'd recommend looking at it. It's a framework for developing RESTful applications using annotations and other methods to simplify deploying said applications. What's nice about this is you get to forget about all the boiler-plate code and just develop. Another cool feature is that if you write your code properly, then you can test it without the need of doing a full integration test (though, it's still a good idea to have those).

Ok, first step, make sure your web-app's bundle is setup correctly for Jetty, put this in your manifest:

Web-ContextPath: /bfry

The bfry being the root path of the web-app. When Jetty starts up, you should see some logs about the web-app being loaded. For this to work, in addition to the standard Jetty dependencies, I use these:


            <dependency>
            <groupId>org.eclipse.jetty.osgi</groupId>
            <artifactId>jetty-osgi-boot</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.osgi</groupId>
            <artifactId>jetty-httpservice</artifactId>
            <version>${jetty.version}</version>
        </dependency>

With that, your standard web-app should be loaded with your OSGi startup. Hooking up Jersey with OSGi takes a little more work. Your WEB-INF/web.xml file should be read by Jetty with the above config. This configuration will install Jersey:


   <servlet>
        <servlet-name>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>benjaminfry.api.BenjaminFryApplication</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

What this says is that the BenjaminFryApplication (which I'll show you the code for below), should be the basis for the Jersey Container. There may be other (and perhaps better) ways to do this, but this works well for me. The Application is installed at /bfry/*, so all requests to this location will go through Jersey. Here's the code for the Application:


package benjaminfry.api;


import org.apache.log4j.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;


import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import java.util.Collections;
import java.util.Set;


/**
 * This is the Jersey Application implementation for passing back references to the Jersey Resource handlers.
 *  In this example there are two resources, one is the LocationResource which implements the Location app.
 *  The other is the OSGiResource with is wired up with the OSGiService
 *  so that it has access to OSGi system. The LocationResource does not, so I won't show that one.
 */
public class BenjaminFryApplication extends Application {
    private static final Logger logger = Logger.getLogger(BenjaminFryApplication.class);


    private final Set<Class<?>> resourceClasses;
    private final Set<Object> resourceSingletons;


    /**
     * Constructor for the Application. This wires up all the Resources and stores the final map for retrieval
     *  at any point.
     * @param sc ServletContext is automatically passed in from the Jersey framework, this is used to retrieve the OSGi
     *           BundleContext which is specified by Jetty through its OSGi support.
     */
    public BenjaminFryApplication(@Context ServletContext sc) {
        try {
            BundleContext bundleContext = (BundleContext)sc.getAttribute("osgi-bundlecontext");
            if (bundleContext == null) throw new IllegalStateException("osgi-bundlecontext not registered");


            ServiceReference<OSGiResource> ref = bundleContext.getServiceReference(OSGiResource.class);
            if (ref == null) throw new IllegalStateException(OSGiResource.class.getSimpleName() + " not registered");
            OSGiResource osgiResource = bundleContext.getService(ref);


            resourceSingletons = Collections.<Object>singleton(meanResource);
            resourceClasses = Collections.<Class<?>>singleton(LocationResource.class);
        } catch (Exception e) {
            logger.error(e);
            throw new RuntimeException(e);
        }
    }


    /**
     * @return per request Jersey Resources which are used to create objects for handling the request.
     */
    @Override
    public Set<Class<?>> getClasses() {
        return resourceClasses;
    }


    /**
     * @return singleton Jersey Resources, these are pre-constructed objects, each one needs to be idempotent and
     *  thread safe.
     */
    @Override
    public Set<Object> getSingletons() {
        return resourceSingletons;
    }
}

In the above code there are some things to point out. The Jersey system supports different types of context information being passed in as parameters with the @Context annotation. The ServletContext has an attribute that is set by the Jetty OSGi system with the BundleContext for our OSGi Bundle. With this BundleContext we get a reference to the registered OSGiResource. The method getSingletons() is used by Jersey to get references to the Jersey REST Servlets. Each of these objects must be threadsafe, reentrant, etc.

Here's the code for the Jersey REST Servlet which also, through the use of OSGi Dynamic Services, is registered with the OSGi System:


package benjaminfry.api;


import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.Reference;
import benjaminfry.osgi.pub.OSGiService;
import com.sun.jersey.spi.resource.Singleton;


import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


/**
 * OSGiResource is the Jersey resource for giving access to the OSGi Service.
 *  the path is bfry/osgi/*, anything outside this path will return a 404. This is defined as an
 *  OSGi DynamicServices Component which will instantiate the object and register it with OSGi automatically.
 */
@Component(provide = OSGiResource.class)
@Path("osgi/{myParam}")
@Singleton
public class OSGiResource {
    private volatile OSGiService osgiService;


    /**
     * Only registered the get method for retrieving the mean and variance for the specified httpCode.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN) 
    public String get(@PathParam("myParam") String myParam) {
        if (meanService == null) throw new WebApplicationException(Response.serverError().entity("osgiService is not set").build());


        String myValue = osgiService.getMyValue(myParam);
        return myValue;
    }


    /**
     * OSGi Dynamic Services reference for wiring up the osgiService.
     */
    @Reference
    public void setOSGiService(OSGiService osgiService) {
        this.meanService = meanService;
    }


    public void unsetOSGiService(OSGiService osgiService) {
        this.osgiService = null;
    }
}

This class has Jersey and Bndlib annotations intermingled, and yes it works. The @Component specifies that this application should be registered with OSGi. @Path is a Jersey annotation that specifies the location of this REST servlet, with the addition of the web.xml config from above it means this would be located at /bfry/osgi/{myParam}. The {myParam} portion tells Jersey that myParam is a path parameter (which can be a regex, look at the Jersey docs). The @PathParam tells Jersey which variable of the get() method to pass the path parameter to.

The lifecycle of this object works like this; The BenjaminFryApplication gets a reference to the OSGiResource service, this causes the OSGi environment to initialize the OSGiResource object to be initialized. During initialization, the OSGi Dynamic Services framework links up the field osgiService via the setOSGiService() method. When a request is made, Jersey looks up the path information say related to the request 'http://localhost/bfry/osgi/someParam'. Jersey sees the GET request, and calls the get() method based on the @GET annotation.

There are some cool things here, but the most interesting thing I think is that this "servlet" can actually be unit tested, no need for an integration test, because of the annotations. The unit test merely needs to construct the object, set either a mocked OSGiService implementation or a real implementation, and then call the get() method with whatever data you want to test. No need to start up Jetty at all for your tests! This makes testing the logic of the servlet simpler and the test itself more reliable. I'm not going to show this, it should be pretty obvious.

REST easy.



4.18.2012

OSGi Classpaths and Jersey

OSGi is cool, but it adds a ton of classpath constraints. I'll put up a complete OSGi page about some of the headaches I've run into and the basic setup I use at a later date, but today I want to share how to get rid of a ClassNotFoundException when trying to deploy Jersey.

If you're not familiar with Jersey it's an implementation from Sun/Oracle of the JAX-RS spec. Basically an annotation system in java for deploying rest based web services.

Anyway, this post isn't about that, it's about this:

java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:513)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:429)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:424)
...
at org.eclipse.jetty.osgi.boot.OSGiAppProvider.addContext(OSGiAppProvider.java:232)
at org.eclipse.jetty.osgi.boot.OSGiAppProvider.addContext(OSGiAppProvider.java:214)
...
at org.osgi.util.tracker.BundleTracker$Tracked.customizerAdding(BundleTracker.java:439)
at org.osgi.util.tracker.AbstractTracked.trackAdding(AbstractTracked.java:261)
at org.osgi.util.tracker.AbstractTracked.trackInitial(AbstractTracked.java:184)
at org.osgi.util.tracker.BundleTracker.open(BundleTracker.java:159)
at org.eclipse.jetty.osgi.boot.JettyBootstrapActivator.start(JettyBootstrapActivator.java:118)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl$1.run(BundleContextImpl.java:711)
at java.security.AccessController.doPrivileged(Native Method)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:702)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:683)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:381)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:299)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:291)


I'd been beating my head into the wall all morning when the problem/solution struck me. I'd doubled checked that I had these bundles installed and started (maven dependencies):

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>1.12</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>1.12</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>1.12</version>
        </dependency>

They were right there in the OSGi runtime: "Active". So what was up? The answer lies in how I was referencing the Jersey context in my web.xml:

      <servlet>
        <servlet-name>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.my.Application</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

It turns out this is the only reference to the ServletContainer anywhere in my "code", in the web.xml. I use the maven-bundle-plugin for generating the bundles. The Bnd tool at the core of that plugin automatically collects all the Import-Packages from what's being imported in your Java code.

The classpath in OSGi is partly derived from your Import-Package statement, and since nowhere in my code was I explicitly referencing the ServletContainer, that class was never made available on the classpath.

Solution, changed the configuration of the maven-bundle-plugin to add the package to the Import-Package statement:

<Import-Package>com.sun.jersey.spi.container.servlet,*</Import-Package>

And then Jersey was deployed, and the world was calm.

4.09.2012

Compiling libvirt and continuing use of polkit

This drove me batty for a little bit. I compiled libvirt and I couldn't get polkit to allow a non-priviledged user to use qemu:///system. So here are the notes and why this ended up happening.

First of all, whenever compiling a piece of software, I always use --prefix to make sure I don't clobber any system files managed by whatever OS you use. Personally I did this on Ubuntu.

First download the version of libvirt you want, for my project I wanted to make sure I was compatible with 0.9.4 (the whole reason I couldn't use the version available from apt):

$> wget http://libvirt.org/sources/libvirt-0.9.4.tar.gz

The instructions on libvirts site tell you to do the basic gnu configure steps, i.e. ./configure, make, make install, but this is much too simplistic in my opinion and would potentially clobber system files.

Here are my steps:

$> tar -xzvf libvirt-0.9.4.tar.gz
$> cd libvirt-0.9.4
$> make clean

And my configuration options:

$> ./configure --prefix=/opt/libvirt --with-polkit --with-app-armor --with-capng --with-qemu


For any errors you'll probably need to make sure you install certain dev packages of common things, like libapparmor-dev etc.

$> make
$> sudo make install


Now here are somethings that are screwed up after installation:


# permissions on the directories in the install path are wrong
$> sudo find /opt/libvirt -type d -exec chmod go+rx {} \;


# I want to use actual /var not the one in the /opt/libvirt
$> sudo rm -r /opt/libvirt/var && pushd /opt/libvirt && sudo ln -s /var && popd

Here's the thing with polkit that I had missed and spent many hours trying to figure out, of course it was self imposed. virsh and the Java binding Connect kept giving me this error:

error: Failed to connect to the hypervisor
error: authentication failed: authentication failed


polkit is a cool system, which I didn't know well until I ran into having to get this next thing setup. The purpose of polkit is to allow non-privileged users access to perform certain actions without the need of promoting to root via sudo. This is important if like me you want to use libvirt's Java bindings, you don't really want to run your JVM as root, do you? Anyway, polkit requires some configuration, first is that you need to define the new actions. Normally make install would do this properly, but since we specified a different --prefix, it ended up installing the action definitions in /opt/libvirt/share. To add it to the actual system just add a new link (that way if you ever reinstall, the link will point to the new file, as opposed to having to remember to copy it each time).

$> pushd /usr/share/polkit-1/actions && sudo ln -s /opt/libvirt/share/polkit-1/actions/org.libvirt.unix.policy && popd

Now install the new file that grants non-root users access to perform the libvirt actions

$> cat > 50-libvirt-remote-access.pkla <<END
[libvirt Management Access]
Identity=unix-group:libvirt
Action=org.libvirt.unix.manage
ResultAny=yes
ResultInactive=yes
ResultActive=yes


[libvirt Monitor Access]
Identity=unix-group:users
Action=org.libvirt.unix.monitor
ResultAny=yes
ResultInactive=yes
ResultActive=yes
END


$> sudo cp 50-libvirt-remote-access.pkla /etc/polkit-1/localauthority/50-local.d

Notice that the group for management is libvirt and monitoring is any user on the system. You will need to make sure both that the group exists (see /etc/group and groupadd) and that your user is a member (see id, and usermod).

With all of that setup, now you just need to make sure that your environment is setup, add this to your .bashrc or equivalent:

export LIBVIRT_HOME=/opt/libvirt
export PATH=$LIBVIRT_HOME/bin:$PATH
export LD_LIBRARY_PATH=$LIBVIRT_HOME/lib
export PYTHONPATH=$LIBVIRT_HOME/lib/python2.6/site-packages

Make sure to login or create a new terminal to reload your shell. Other things to note, I didn't mention starting libvirtd, you will need to do this. If you put things in the paths I specified this would be in /opt/libvirt/sbin/libvirtd. You probably will want this to be started automatically. If you need an upstart script, post a comment, and I can post one. Otherwise, you can just modify the one that is installed with the system to point to the proper location for everything.

4.08.2012

Giving back

I've neglected this blog for a long time. I've always been trying to figure out what best to do here and recently it became obvious. I, like many developers, search for answers constantly online. Some of which I find, and others I have to figure out for myself. I'm going to dedicate this blog to the answers that I had to come up with myself. Expect to see future posts in regard to, but definitely not limited to: Java, C, Linux, OSGi, Maven, Ant, Jetty, Servlets, XML/Jaxb, Intellij, REST, etc.

For too long have I only consumed, I now feel a need to give back, and since my son was born, I have all this spare time to do it too ;)

For anything personal, i.e. not tech related, you'll need to look to Facebook or Google+ for that.

Thanks-
-ben