Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development • Apache integration software

Implementing an Apache Karaf route in an OSGi Java bundle from the ground up

This article describes how to install and manage an Apache Camel route as an OSGi bundle within the Apache Karaf container. Like all the articles in this series, it proceeds from first principles, and uses no sophisticated software tools or IDEs. However, it builds on the principles described in my articles on Creating OSGi bundles and Creating a Camel route, and I assume that the reader is familiar with the material in those articles. In particular, I won't describe again what an OSGi bundle is, nor how to obtain and set up Karaf, nor the fundamental principles of Camel routing in Java — the other articles deal with these topics. Here I assume that Camel is installed, and Karaf is installed and running.

In this article I use the same file-copy Camel route as in my Camel article, but with some extra processing, just to demonstrate an additional feature of Camel. What we'll end up is a bundle that can be deployed on Karaf, and which will copy files from one directory to another, inserting a timestamp at the top of each one. Not a very impressive feat, of course, but it does demonstrate Karaf/Camel integration in a self-contained way.

The bundle created in this article should work with Apache ServiceMix as well as Karaf; in fact, it should be easier to set up, as ServiceMix has the necessary Camel bits pre-installed. However, at the time I wrote this, the latest stable version of ServiceMix had Camel components that were to early to have all the necessary OSGi support, so installing on ServiceMix would have meant removing a heap of stuff and installing newer versions. This situation might well have improved by the time you read this.

Setting up Karaf with Camel support

At the time of writing, setting up Karaf so that we can use the recommended form of CamelContext (see below) is a bit fiddly. We start by installing the basic Camel support as follows:
karaf@root> features:chooseurl camel 2.12.1
karaf@root> features:install camel

Be aware that installing camel can take a while, and you don't get a lot of progress indication. Next we need to install support for Camel OSGi integration, but (so far as I can tell) this can't be installed as feature; we need to get it explicitly from the respository. In fact, before we can do even that, we need to install the eventadmin bundle that the OSGi support depends on, and that needs to be done manually. Download the bundle org.apache.felix.eventadmin-1.3.2.jar from here, and install it using osgi:install:

karaf@root> osgi:install file:/path/to/org.apache.felix.eventadmin-1.3.2.jar
Now we can install OSGi support from the repository:
karaf@root> osgi:install -s mvn:org.apache.camel/camel-core-osgi/2.12.1

[Update, May 2014: it seems now to be possible to install Camel OSGi support from the repository: osgi:install -s mvn:org.apache.camel/camel-osgi/1.5.0. So you shouldn't need to download the above-mentioned bits separately.]

If Camel support is properly installed, the output of osgi:list should show at least the following bundles, and all in the Active state (you might have to start them manually with osgi:start):

[  54] [Active     ] [            ] [   80] camel-core (2.12.1)
[  55] [Active     ] [            ] [   80] camel-core-osgi (2.12.1)
[  66] [Active     ] [            ] [   80] Apache Felix EventAdmin (1.3.2)
In practice you might see additional modules, but they aren't necessary for this example. The bundle camel-karaf-commands is useful for managing Camel routes defined in, for example, XML files, but it isn't necessary (or even functional) for Java routes.

Building the Camel route bundle

In this section I'll describe how to implement a Camel route in Java, inside an OSGi bundle. The bundle requires only two source files: one Java class and a manifest. The Java class will play the role of the bundle activator (that is, it will provide the start() and stop() methods for management by the container), and the route processor — what little processing is involved. In a real application these two disparate tasks would probably be better separated into different classes.

Setting up the workspace

I'm assuming that there is a directory structure like this:
src
  net
    kevinboome
      karafcameltest
        Activator.java   # This is the Java source for the bundle

target
  classes                # Compiled code will go here

etc
  MANIFEST.MF            # This is the bundle manifest
The source code bundle is available from the Download section at the end of this article, and will create the necessary directory structure when unpacked.

Coding the route

The source code for the Camel route is not very different from the simple file copier in my Camel article. I hope that, with comments, it's reasonably self-explanatory.
/*
Simple Camel file copier which adds file the current date and
time to the top of each file copied. It is intended to be used in
an OSGi bundle.

(c)2013 Kevin Boone
*/
package net.kevinboone.karafcameltest;

import java.util.*;
import org.osgi.framework.*;
import org.osgi.framework.*;
import org.apache.camel.*;
import org.apache.camel.core.osgi.*;
import org.apache.camel.impl.*;
import org.apache.camel.builder.*;

public class Activator implements BundleActivator
  {
  private CamelContext camelContext = null;
  public void start(BundleContext bundleContext) throws Exception
    {
    System.out.println ("File copier started");
    // In a stand-alone application we would usally get a CamelContext
    //  like thos:
    //  camelContext = new DefaultCamelContext();
    // But in a bundle, we need to use OsgiDefaultCamelContext,
    //  so the container can manage the camel context as it manages
    //  the bundle. 
    camelContext = new OsgiDefaultCamelContext(bundleContext);
    camelContext.addRoutes(new RouteBuilder()
      {
      public void configure()
        {
        // Define the process that will be carried out on the file
        //  before it is copied to the new directory
        // We're using an anonymous inner class here, that implements
        //  the Camel Processor interface
        Processor myProcessor = new Processor()
          {
          public void process (Exchange exchange)
            {
            System.out.println ("Processing file: " + exchange);
            // Get the file contents as a Message object
            Message in = exchange.getIn();
            // Append the timestamp. Note that we must tell the
            //  getBody() method the format in which we want the data,
            //  but we don't need to do this with setBody(), as we're passing
            //  a String object to it
            in.setBody (new Date().toString() + "\n\n"
              + in.getBody(String.class));
            }
          };

        // Set up the route — from the input directory, through the
        //  process defined above, to the output directory.
        from ("file:/tmp/in?noop=true")
          .process (myProcessor)
            .to ("file:/tmp/out");
        }
      });

    // Start the Camel operation when the bundle is started
    camelContext.start();
    }

  public void stop(BundleContext bundleContext) throws Exception
    {
    System.out.println("File copier stopped");
    // Stop the Camel operation when the bundle is stopped
    camelContext.stop ();
    }
  }
The bundle will copy any files that arrive in /tmp/in to /tmp/out, after prepending a timestamp. Of course you're free to change these directories to ones that are more suitable for your system. The directories will be created at runtime if they do not already exist.

Writing the manifest

Here is the complete manifest for this bundle.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Kevin's Karaf Camel Test 
Bundle-SymbolicName: net.kevinboone.KarafCamelTest
Bundle-Version: 1.0.3
Bundle-Activator: net.kevinboone.karafcameltest.Activator
Bundle-Vendor: Kevin Boone 
Bundle-Localization: plugin
Import-Package: org.osgi.framework, 
 org.apache.camel, org.apache.camel.impl, org.apache.camel.builder, 
 org.apache.camel.builder.xml, org.apache.camel.model, 
 org.apache.camel.component, org.apache.camel.core.osgi
The Camel support bundle in Karaf is divided into a number of different Java packages, each of which must be included individually. If you don't have a clever IDE tool to tell you which imports you need, you can proceed with a mixture of find-class in the Karaf console, intuition, Google, and luck. The last import org.apache.camel.core.osgi is perhaps the least obvious; this package provides the OsgiDefaultCamelContext class.

Building the bundle

We need to compile the code and then build a JAR file including the manifest. Here's how to do this at the command line:
$ javac -d target/classes -classpath /path/to/karaf/lib/karaf.jar:\
  /path/to/camel/lib/camel-core-2.12.1.jar:\
  /path/to/camel/lib/camel-core-osgi-2.12.1.jar\  
  src/net/kevinboone/karafcameltest/*.java 

$ jar cfm karafcameltest.jar etc/MANIFEST.MF  -C target/classes . 
Note that the first command is intended to be one long line. As always, you'll need to replace the /path/to prefixes with the actual locations of Karaf and Camel on your system. The JAR camel-core-osgi is needed specifically to provide the OsgiDefaultCamelContext class.

All being well, you should end up with a bundle JAR called karafkameltest.jar in the current directory.

Testing the bundle

Now it's just a case of installing the bundle in Karaf, and starting it.
karaf@root> osgi:install file:/path/to/karafcameltest/karafcameltest.jar
Bundle ID: 74
karaf@root> start 74
File copier started
Then try copying some files into the in directory, and see what happens. The Bundle produces output to System.out each time it processes a file, and this output should appear on the console. In case of problems, check for errors in the log at data/log/karaf.log.

As with any other bundle, you can stop the route bundle using osgi:stop.

Summary

I hope it is clear from this example that there's not a lot more to building a Camel route into an OSGi bundle than there is to running the Camel routing engine on its own. We do have to take care to start and stop the route when the bundle is started and stopped, but that's about all there is to it.

Downloads

Source code bundle

Copyright © 1994-2013 Kevin Boone. Updated May 02 2014