Implementing an OSGi service on Felix, Maven and Eclipse

Implementing an OSGi service using Maven isn't hard, but there are a bunch of little pieces to pull together the first time through the process.

Let's create a SlingPostProcessor.

A SlingPostProcessor is called by Sling just before POSTed changes are written into the repository. It provides a mechanism for validating what goes into the repository.

Our goal isn't to create just an example, but an example that's ready to use in a larger software development project. This means we're developing in Eclipse and building with Maven.

So to get started, make sure you've got a recent Eclipse and the M2Eclipse plugin installed.

Once you're there, start with File / New Project and the Maven 2 Project Creation Wizard. I'm going to create a simple Java project named "my-sling-validator".

Eclipse dialog where you select the project type

Eclipse dialog showing Maven coordinates

Eclipse dialog showing some configuration details

If everything is working right, you should have a Java project that builds both in Eclipse, and from the command line (via "mvn package"). But it's a Java project, not an OSGi bundle.

The M2Eclipse plugin provides an editor for the pom.xml file (Maven's project object model), but I find it's easier to edit the xml file directly, and you're going to want to become familiar with the format of the pom.xml file since it's a fundamental part of working with Maven, so let's do it there.

First we need to turn our Java project into a project that produces an OSGi bundle. There's a plugin for that - and there's a plugin for producing the service descriptor file that describes the service that our bundle will provide, so let's add both those plugins right now.

Edit the pom.xml file and add, as a child of the <project> node, the following:

[cc]
org.apache.felix
maven-bundle-plugin
2.2.0
true


net.stevex.examples.sling.validator

org.apache.felix
maven-scr-plugin
1.7.0


generate-scr-scrdescriptor

scr



[/cc]

Paste this into your pom.xml file. Save it, and Eclipse should immediately go hunting for the two plugins, downloading them and installing them into your local Maven repository if required. The plugins give your project the ability to produce an OSGi bundle, but to actually get a bundle, you need to change the project packaging from the default value of 'jar' to 'bundle'. (For this simple change you can try using the M2Eclipse pom.xml editor if you like. The value you're changing is:

[cc] ... bundle ... [/cc]

Now if you package your project, you'll get an OSGi bundle. To package, right-click on your project, and on the Maven 2 menu select Package Artifact.

201106200650

Refresh the 'target' folder in your project and you'll find a Jar file. Look at the META-INF/MANIFEST.MF file inside that jar and you'll see it has the bundle properties that identify it as an OSGi bundle.

Manifest-Version: 1.0
Export-Package: net.stevex.examples.sling.validator
Bundle-Version: 1.0.0.SNAPSHOT
Build-Jdk: 1.6.0_24
Built-By: stevex
Tool: Bnd-1.15.0
Bnd-LastModified: 1308567248570
Bundle-Name: my-sling-validator
Bundle-ManifestVersion: 2
Created-By: Apache Maven Bundle Plugin
Bundle-SymbolicName: net.stevex.my-sling-validator

Next, we need to create a class that implements the SlingPostProcessor service. To do this we need some dependencies on the ClassPath so we have access to the interface we need to implement. Edit the pom.xml file again and add some dependencies:

[cc] ..

..

org.apache.felix
org.apache.felix.scr.annotations
1.4.0


org.apache.felix
org.osgi.core
1.4.0


org.apache.sling
org.apache.sling.servlets.post
2.1.0
provided


org.apache.sling
org.apache.sling.api
2.2.0

..

.. [/cc]

Add these dependencies, save, and let Maven download the required dependencies. Once that's done, you can use the Eclipse class wizard to extend the SlingPostProcessor:

Java Class Wizard

Now the next step is exporting this service so the OSGi container will find it.

[cc lang="java"]
@Component(metatype=false, immediate=true)
@Services({
@Service(value=org.apache.sling.servlets.post.SlingPostProcessor.class)
})
public class MyPostProcessor implements SlingPostProcessor {

@Override
public void process(SlingHttpServletRequest arg0, List arg1)
throws Exception {
System.out.println("MyPostProcessor.process called!");
}

}
[/cc]

There are two ways that you can specify the SCR annotations: as JavaDoc comments, or as annotations. I prefer annotations because they seem more like part of the language, even though the comment syntax is shorter and simpler.

Now that the project implements a service, take a look at the jar file generated when you invoke the Maven package step. You'll see it has an OSGI-INF folder with some files that describe the service the jar implements.

At this point, we've got a complete working OSGi bundle that implements the SlingPostProcessor service. Let's see if it works!

If you've got CRX installed on localhost, you should be able to access the Felix (OSGi) console at http://localhost:4502/system/console/bundles (or whatever port number you're running it on). On that page, click the "Install/Update..." link at the top right, browse to your jar file, and upload it. Once it's uploaded you may need to click the Reload button to see it show up in the list but once it does, you can click the little triangle at the left of the name to see details.

Click the "play" button at the right of the list and start the bundle.

To see if the service registration was successful, take a look at the Services tab in the Felix console. You should be able to find your service in the list. And the final test: watch stdout.log and invoke curl to POST some content:

$ curl -F "foo=bar" http://localhost:7402/content/foo

You should see the line we're printing in the process function show up in stdout.log.

(Note that cheesy stdout logging is not something you'd want to use, but it saved me from having to get into how to set up logging).

Notice that we didn't need to register with the SlingPostProcessor? That's because the SlingPostProcessor is listening to the service registry and when it sees a SlingPostProcessor register itself, it gets added to the list of post processors. Stop your bundle and Sling will automatically remove the post processor.

What if something goes wrong, where do you look? Here are some troubleshooting steps.

Make sure the bundle is loading. If you click the "play" button on the Bundles tab for your bundle, and then reload, and it's not showing as Active, then something went wrong starting the bundle. Check the logs (there are a few logs) in the crx-quickstart/logs folder. (A good way to do this is to "tail -f *" in the logs folder, then attempt to start your bundle, and scroll back through all the output to see what went wrong).

Make sure you're importing the classes you need. Our example project doesn't import any packages but a real project would. You'll need to specify these in the configuration/instructions section of the maven-bundle-plugin plugin configuration (by adding an Import-Package next to the Export-Package directive that we did specify).

Did the bundle load but the service isn't showing up on the services tab? Something is wrong with your service registration.

If you need to see more debugging information in the log, go to the the Sling Log Support tab in the Felix console, and click the little configuration wrench next to stderr.log. Change the level from 'info' to 'debug' and you'll get more detailed logs.