• Articles
• Articles about computing
• Articles about software development
• Apache integration software
Creating OSGI bundles and services from the ground up using
Apache Karaf
This article describes the fundamental principles of the OSGi development
model, using Apache Karaf or ServiceMix as the container. No software
tools other than a text editor and Java JDK are used — my intention
is to explain in detail how OSGi bundles are packaged, and how they interact
with their container and one another. Of course most development of
this kind is done with sophisticated IDE tools, but these just tend to
obscure the low-level details.
The example I will use is a simple one, but a step up from "Hello, World" — it shows how one bundle can be exposed as a service with a well-defined
API, and that service consumed by another, independent bundle.
About OSGi and its container concept
This section briefly describes the OSGi component model, and how to
obtain and install the Apache Karaf or ServiceMix OSGi containers.
The OSGi concept
The OSGi development model for Java is based on loosely-coupled software
components called bundles, deployed in a lightweight container.
Each bundle consists of one or
more Java packages, at least one of which contains an Activator,
which forms the link between the bundle and the container.
The OSGi container is lightweight in the sense that it offers very few
services, and has little runtime overhead. Developers familiar with J2EE
will already have experience with servlet and EJB containers; the OSGi
container is conceptually simpler, except that it does not attempt
to offer the same range of business-oriented services that J2EE does.
What the OSGi container does offer, however, is a strict dependency
management and versioning system. Each bundle that uses a service
from a different bundle must declare that it has a dependency, and
the container will not allow a bundle to start until the dependency
is satisfied.
OSGi bundles are kept segregated by the use of independent classloaders;
bundles can only interact by well-defined mechanisms exposed by the
container. The simplest of these, and the one that will be used in
this example, is for one of the bundles to expose a service that the
other imports. The service is defined by a Java interface, which has to
be known to both the importing and the exporting bundle.
OSGi containers
Many software products are now based on the OSGi model; this may, or may not,
be apparent in use. A number of IDE tools, for example, use OSGi internally
to provide a plug-in/extension mechanism. The Apache project maintains
a general-purpose OSGi container called Felix; Felix can be used
programatically, but for experimentation it is probably easiest to
use one of the two Apache projects based on Felix — Karaf or ServiceMix.
Both of these projects provide an interactive shell through which
OSGi modules and services can be installed and managed. The main
difference between the two is that ServiceMix bundles a bunch of other
integration components — ActiveMQ message broker, the Camel routing
engine, and some other things. In this example, I have shown Karaf
being used, but it works with ServiceMix as well (with some changes
to the logging configuration as, by default, you won't see any output).
Obtaining and setting up Karaf
Karaf may be obtained from its
download page. The download bundles are platform-specific because Karaf uses
native-code libraries to provide line editing functions that do not
exist in pure Java.
Installation of Karaf is trivial — just unpack the distribution bundle
into any convenient directory. You should then be able to start the
Karaf shell by running
$ /path/to/karaf/bin/karaf
Naturally, you'll need to replace the /path/to part with the
actual installation directory in this command (and in all other commands
in this article).
With luck you'll see a welcome banner and a prompt:
karaf@root>
The name 'root' here does not indicate the OS root user, but the Karaf
user; different users can be defined with specific security privileges over
the container, but I'll just be using the 'root' user in this example.
Important Karaf shell commands
The Karaf shell exposes a large number of management commands, but only
a few are required for these article. I'll describe them here, just so
they're all in one place, rather than scattered around the text
osgi:list
Lists the installed bundles with their IDs and status. The ID will be
used in many other commands to manage the bundle. The status will
usually be one of Installed, Active, and Resolved. Be aware that
Installed can be an unhealthy status — if you've attempted to
start a bundle, but there are missing dependencies, then it will be
shown as Installed. If you successfully start a bundle and then stop
it, its status is Resolved. There are interim status codes — Starting, Stopping, etc — but their appearance for any length of time
usually indicates some sort of fault.
osgi:install file:/path/to/my/bundle.jar
Installs the bundle whose JAR file is at the specified location.
By default, installation does not attempt to start the bundle, or
to resolve dependencies. If the installation succeeds, Karaf will
report the ID of the new bundle.
osgi:start [id]
Attempts to start the bundle. That is, dependencies are resolved
(if possible), and the bundle activator's start()
method is called (more on activators later).
osgi:stop [id]
Attempts to stop the bundle. The bundle activator's start()
method is called.
headers [id]
Shows details from the bundle's manifest (more on manifests below).
In particular, it shows which packages are imported and exported, and
failed dependencies are highlighted. headers is very
helpful in diagnosing problems where the bundle won't start because of
missing dependencies (as is find-class , described below).
osgi:uninstall [id]
Stops and uninstalls the specified bundle. In general, this does not
stop or uninstall any bundles that depend on the one being uninstalled,
although they might well fail to function, and will probably not restart
once stopped themselves.
find-class [class_name]
Attempts to find the bundle that exposes a class with the specified name.
This can be useful if you're seeing NoClassDefFound messages
in the log, because you haven't imported the necessary packages in
your bundle.
There are other important commands concerned with 'feature' management; a
feature is a set of bundles that provide a particular facility. We
won't need to install any new features in this example but it is possible,
for example, to configure support for the Camel routing engine by
installing it as a feature.
Anatomy of a bundle
At its simplest, a bundle consists of some Java packages and a
manifest, bundled into a JAR. The manifest is provided in the
usual META-INF/MANIFEST.MF file that most Java developers will know about.
OSGi defines a few extra manifest entries specific to bundles, most
particularly entries that define dependencies between bundles.
You can take any Java library in JAR form and make it 'OSGi-ready'
simply by adding the relevant OSGi entries to its manifest. However,
this will not allow the container to manage the bundle beyond installing
and uninstalling it.
The essential OSGi headers, taken from the 'Tick' bundle that will be
presented later, are:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGi test: tick
Bundle-SymbolicName: net.kevinboone.OSGiTest.tick;singleton:=true
Bundle-Version: 1.0.2
In practice, the first two headers are boilerplate, and will appear
at the start of every bundle manifest. The two names and the version
identify this specific bundle; it is possible to install the same
bundle more than once in the container, if the bundle versions
are different. Bundles can, and probably should, import other bundles
by their specific version numbers if multiple versions will be deployed.
For the container to be able to start and stop the bundle, the developer
must provide as a minimum an activator. The activator is
a Java class that implements the BundleActivator interface
provided by the OSGi framework. This interface has only two methods:
start() and stop() , and it probably isn't
difficult to guess when they get called. These methods can do pretty
much anything, with the proviso that they don't block — if they take
a long time to complete, they can interfere with the Karaf shell getting
on with the next task. For long-running, service-based tasks, the
start() method can start a new thread, as it does
in the example in this article.
The container is made aware of the bundle activator by the following
entry in the manifest:
Bundle-Activator: net.kevinboone.osgitest.tick.Activator
Introducing the example
The example in this article consists of two bundles: Tick and Tock.
The Tick bundle starts a background thread that wakes up every
five seconds. It then calls a method tick() on every
class that has registered an interest in it. A class registers
an interest by getting a reference to the TickService interface,
and calling its addListener() method, passing a
class that implements the TickListener interface.
So the Tock bundle is a consumer of the service exposed by the
Tick bundle.
An overview of the two bundles is shown in the UML diagram below.
Each bundle in this example consists of one Java package, although
bundles need not be so limited in practice. As discussed above, each
bundle has a activator that implements the BundleActivator
interface, so the container can start or stop it.
Building, deploying, and starting the Tick service
Setting up the workspace
The Tick service is a bundle that consists of two interfaces, one
class, and a manifest. The directory layout I have chosen for the
source of this bundle (and the corresponding Tock bundle) looks
like this:
src
kevinboone
osgitest
tick
Activator.java
TickService.java
TickListener.java
tock
Activator.java
etc
MANIFEST_tick.MF # Manifest for the Tick bundle
MANIFEST_tock.MF # Manifest for the Tock bundle
target
tick
classes # compiled classes for the Tick bundle will go here
tock
classes # compiled classes for the Tock bundle will go here
The complete source, with the described directory structure, is provided
in the download bundle at the end of this article.
Coding the bundle
Here is the source of the Tick activator (and service implementation, as
I've put these in the same class). I hope that with the comments
it's reasonably clear what's going on. The important points to
note are that the activator class implements the interface
TickService , and provides the corresponding
addListener and removeListener methods; and
that it provides the essential start() and stop()
methods that will be called by the container.
package net.kevinboone.osgitest.tick;
import org.osgi.framework.*;
import java.util.*;
public class Activator implements BundleActivator, TickService
{
private boolean stop = false;
private List<TickListener> listeners = new Vector<TickListener>();
addListener() implements the method in the service interface
TickService.
@Override
public void addListener (TickListener listener)
{
listeners.add (listener);
}
removeListener() implements the method in the service interface
TickService.
@Override
public void removeListener (TickListener listener)
{
listeners.remove (listener);
}
start() method gets called when running osgi:start from the console
public void start(BundleContext bundleContext) throws Exception
{
System.out.println ("Tick bundle started");
stop = false;
new Thread (new Runnable()
{
public void run()
{
while (!stop)
{
try
{
Thread.sleep(5000);
System.out.println ("Tick!");
for (TickListener listener : listeners)
listener.tick();
}
catch (Exception e){}
}
}
}).start();
bundleContext.registerService(TickService.class.getName(),
this, new Properties());
}
stop() method gets called when running osgi:start from the console
public void stop(BundleContext bundleContext) throws Exception
{
System.out.println("Tick bundle stopped");
stop = true;
}
}
This section of code deserves special mention:
bundleContext.registerService(TickService.class.getName(),
this, new Properties());
Here this class registers itself ('this ') as the provider
of a service whose interface is TickService . Note that
we're passing the interface by name, not by class. This is because
the provider and the consumer of the service will likely be in
different bundles, and be served by different class loaders. The
framework will have to construct the interface reflectively from
its name.
Coding the interfaces
There are two interfaces — an interface to the service's API
(TickService ) and an interface that must be implemented
by any consumer of the service that wants to listen for tick events
(TickListener ). These interfaces are trivially simple,
but shown here just for completeness.
TickService :
package net.kevinboone.osgitest.tick;
public interface TickService
{
public void addListener (TickListener listener);
public void removeListener (TickListener listener);
}
TickListener :
package net.kevinboone.osgitest.tick;
public interface TickListener
{
The tick() method is called on every registered listener every time
the Tick service ticks
public void tick();
}
Writing the manifest
Most of the manifest for the Tick service has already been presented,
but here is the whole thing.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGi test: tick
Bundle-SymbolicName: net.kevinboone.OSGiTest.tick;singleton:=true
Bundle-Version: 1.0.2
Bundle-Activator: net.kevinboone.osgitest.tick.Activator
Bundle-Vendor: Kevin Boone
Bundle-Localization: plugin
Import-Package: org.osgi.framework
Export-Package: net.kevinboone.osgitest.tick
The important entries not discussed so far are Import-Package
and Export-Package . These control which bundles we will
see, and which bits of this bundle other bundles will see. In this
case I'm exporting the whole package. We need to import
the framework package, to have access to interfaces like
BundleActivator . It's vital to understand that the
class search path seen at runtime in the container will not look much
like that seen at compile time. It's perfectly possible — common even — for a bundle to compile perfectly well, and then fail at start time
because classes that were available to the compiler are not available
in the container. This is why Impot-Package is so important:
apart from the basic JVM classes (java.lang , etc), every
class required by an OSGi bundle must be imported from somewhere.
If you're not using clever build tools or an IDE, it can be quite difficult
to work out which bundle exports a particular class. This is where the
Karaf find-class and headers commands come in
handy, along with a bit of trial-and-error.
Building the Tick bundle
To build the bundle at the command line:
$ javac -d target/tick/classes -classpath /path/to/karaf/lib/karaf.jar \\
src/net/kevinboone/osgitest/tick/*.java
$ jar cfm osgitest_tick.jar etc/MANIFEST_tick.MF -C target/tick/classes .
The reference to karaf.jar provides the java compiler with
definitions of the OSGi framework classes that the source uses.
The jar command bundles up the classes and provides the
specified manifest.
The final JAR file, osgitest_tick.jar should
have a structure like this:
$ jar tf osgitest_tick.jar
META-INF/MANIFEST.MF
net/kevinboone/osgitest/tick/TickListener.class
net/kevinboone/osgitest/tick/TickService.class
net/kevinboone/osgitest/tick/Activator$1.class
net/kevinboone/osgitest/tick/Activator.class
The $1 class here corresponds to the anonymous inner
class used to create the implementation of the background thread
in the Activator class.
Deploying the Tick bundle
Now all we need to do is start Karaf, and run at the console:
karaf@root> osgi:install file:/path/to/osgitest_tick.jar
Bundle ID: 102
karaf@root> osgi:start 102
Tick
Tick
...
Because the the bundle uses System.out.println for its
output, then by default this output will just be seen in the
Karaf console. Yes, it does mess up the look of the console a bit, but
it's easier to see what's going on than by writing to a log file.
Note that the bundle ID — 102 in this case — will most likely
be different from this; what matters is that you start the bundle
whose ID is given by the shell.
As written, the Tick service ticks every five seconds. To stop it,
you can just run
karaf@root> osgi:stop 102
This calls the bundle's stop() method, which sets the
stop flag, which causes the background thread to stop. Note that
stopping the bundle does not unload it from memory, or force its
threads to stop running, although uninstalling it does.
It's perfectly possible for a badly-behaved bundle to ignore the
stop() call and carry on regardless.
Building, deploying, and starting the Tock bundle
The Tock service is a consumer of ticks. That is, it locates
the Tick service, and calls its addListener method
with itself as a reference. Thereafter, the tick()
method will get called every five seconds, and the Tock bundle will
output a message.
Here is the complete source of the Tock bundle. Again, I hope that with
the comments it's reasonably clear how it works.
package net.kevinboone.osgitest.tock;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import net.kevinboone.osgitest.tick.*;
public class Activator implements BundleActivator, TickListener
{
start() method gets called when running osgi:start from the console
public void start(BundleContext bundleContext) throws Exception
{
System.out.println("Tock bundle started");
ServiceTracker tracker = new ServiceTracker
(bundleContext, TickService.class.getName(), null);
tracker.open();
TickService tick = (TickService) tracker.getService();
tracker.close ();
if (tick != null)
tick.addListener (this);
else
throw new Exception
("Can't start tock bundle, as tick service is not running");
}
stop() method gets called when running osgi:start from the console
public void stop(BundleContext bundleContext) throws Exception
{
System.out.println("Tock bundle stopped");
ServiceTracker tracker = new ServiceTracker
(bundleContext, TickService.class.getName(), null);
tracker.open();
TickService tick = (TickService) tracker.getService();
tracker.close ();
if (tick != null)
tick.removeListener (this);
}
tick() implements the tick() method in TickListener.
@Override
public void tick()
{
System.out.println ("tock");
}
}
Note that this class uses a ServiceTracker to get
a reference to the interface exposed by the Tick service. If the
Tick service is installed but not started then the interface
will not be found. In that case, the Tock service must fail by
throwing an exception; otherwise there is no way for the container,
or the administrator, to know that initialization failed.
Here is the manifest for the Tock service:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGi test: tock
Bundle-SymbolicName: net.kevinboone.OSGiTest.tock
Bundle-Version: 1.0.2
Bundle-Activator: net.kevinboone.osgitest.tock.Activator
Bundle-Vendor: Kevin Boone
Bundle-Localization: plugin
Import-Package: org.osgi.framework, org.osgi.util.tracker, net.kevinboone.osgitest.tick
Notice that we import the package of the Tick service, and also
org.osgi.util.tracker — this latter is required for the
bundle to be able to load the ServiceTracker class at
runtime.
Building the Tock service
We can easily build this service at the command line also:
javac -d target/tock/classes -classpath /path/to/karaf/lib/karaf.jar \\
src/net/kevinboone/osgitest/tock/*.java \\
src/net/kevinboone/osgitest/tick/Tick*.java
jar cfm osgitest_tock.jar etc/MANIFEST_tock.MF -C target/tock/classes .
Notice that we must include in the javac invocation
some reference to the interfaces defined by the Tick service. We can
do that by specifying the source (as here), or by adding the location
of the compiled classes to the classpath. However, including the
source means that compiled versions of these classes will be added
written to the target directory, and thus included automatically
in the JAR. This avoid one extra file-copy step; in practice, in a real
application your files are less like to be set out in such a way as to
allow tricks like this to work.
Deploying the Tock bundle
The procedure is the same as for the Tick bundle, which should be
started by this point.
karaf@root> osgi:install file:/path/to/osgitest_tock.jar
Bundle ID: 103
karaf@root> osgi:start 103
Tick
Tock
Tick
Tock
...
Now every time you see a tick from the Tick service, you should also
see a tock from the Tock bundle. If you stop the Tick service, you
should see both ticks and tocks stop.
Notice that the bundles are sufficiently loosely coupled that you can
completely uninstall the Tick service without damaging the Tock
service. The Tock service won't get any more ticks, but it won't crash
or spit out errors — at least until you try to stop and start it again,
at which point it will realize that there is no Tick service for it to
consume from.
You should also find that if you uninstall Tick and Tock, and then
try to install Tock alone, it should fail — the dependency on
Tick listed in the manifest cannot be satisfied.
Summary
This has been a fairly low-level exposition of the OSGi framework and how
to use it. In practice, in a substantial project you're likely to use
an IDE or a build/dependency tool like Maven to manage bundles. But, as
you can see, the OSGi architecture does not depend on any of these things;
and, I hope, working at this low level makes it clearer what the tools
are doing.
Downloads
Source code bundle
|