This story appeared on JavaWorld at
http://www.javaworld.com/javaworld/jw-03-2008/jw-03-osgi1.html
The Open Services Gateway Initiative (OSGi) defines an architecture for developing and deploying modular applications and libraries. In this first article in a three-part introduction to OSGi, Sunil Patil gets you started with OSGi development concepts and shows you how to build a simple Hello World application using the Eclipse OSGi container implementation, Equinox. He also touches briefly on building service-oriented applications using OSGi and introduces OSGi's ServiceFactory
and ServiceTracker
classes.
The Open Services Gateway Initiative (OSGi) , also known as the Dynamic Module System for Java , defines an architecture for modular application development. OSGi container implementations such as Knopflerfish , Equinox , and Apache Felix allow you to break your application into multiple modules and thus more easily manage cross-dependencies between them.
Similar to the Java Servlet and EJB specifications, the OSGi specification defines two things: a set of services that an OSGi container must implement and a contract between the container and your application. Developing on the OSGi platform means first building your application using OSGi APIs, then deploying it in an OSGi container. From a developer's perspective, OSGi offers the following advantages:
Given that you use servlet containers for building Web applications and EJB containers for building transactional applications, you may be wondering why you need yet another type of container. The short answer is that OSGi containers are intended specifically for developing complex Java applications that you want to break up into modules. I'll expand on that short answer throughout this series.
Work on the OSGi specification was started by the OSGi Alliance in March 1999. Its main goal was to create an open specification for delivering managed services to local networks and devices. The basic idea is that once you add an OSGi Service Platform to a networked device (embedded as well as servers), you should be able to manage the lifecycle of software components in that device from anywhere in the network. Software components can be installed, updated, or removed on the fly without ever having to disrupt the operation of the device.
For years, OSGi technology has flourished in the embedded systems and network devices market. Now, thanks in part to Eclipse, OSGi is emerging as a viable and valuable technology for enterprise development.
In 2003, the Eclipse development team began looking for ways to make Eclipse a more dynamic rich client platform and increase the toolset's modularity. Eventually, the team settled on using the OSGi framework as a runtime component model. Eclipse 3.0, released in June of 2004, was the first version of Eclipse based on OSGi.
Almost all enterprise application servers support or plan to support OSGi . The Spring framework also supports OSGi, via the Spring Dynamic Modules for OSGi Service Platforms project, which provides an infrastructure layer to make it easier to use OSGi in Spring-based Java enterprise application development.
From an enterprise developer's point of view, the OSGi container has such a low footprint that you can easily embed it into an enterprise application. For example, let's say you're developing a complex Web application. You want to break the application into multiple modules: one module for the view layer, another for the DAO layer, and a third module for the data access layer. Using an embedded OSGi container to manage the cross-dependencies of these modules would enable you to update your DAO layer (say from slow DAO to fast DAO) without restarting your application.
As long as your application is compliant with the OSGi specification it should be able to run in any OSGi-compliant container. Currently, there are three popular open source OSGi containers:
In this article we will use Equinox as our OSGi container. See the Resources section for more information about Apache Felix and Knopflerfish .
<!-- -->
In OSGi, software is distributed in the form of a bundle . A bundle consists of Java classes and other resources that deliver functions to device owners, as well as providing services and packages to other bundles. Eclipse offers excellent support for developing OSGi bundles. Not only does it provide wizards for creating OSGi bundles, it also has an embedded Equinox OSGi container that you can use to execute and debug OSGi plugins. Note that every Eclipse plug-in is essentially an OSGi bundle with some additional Eclipse-specific code. Eclipse also allows you to build standard-compliant OSGi bundles without code specific to Eclipse. In this section you'll learn how to develop a Hello World OSGi bundle using the Eclipse IDE.
Follow the steps below to create a Hello World bundle using OSGi and Eclipse.
com.javaworld.sample.HelloWorld
Eclipse will take few seconds to generate template code for the Hello World bundle. It will create two files: Activator.java
and MANIFEST.MF
. We'll take a closer look at them both.
Your Activator.java
file should look as shown in Listing 1.
package com.javaworld.sample.helloworld;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
System.out.println("Hello world");
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World");
}
}
If your bundle needs to be notified at the time of bundle startup or shutdown then you should create a class implementing the BundleActivator
interface. Follow these rules when creating the class:
BundleActivator
class must have a public constructor that takes no parameters. The OSGi framework can create a BundleActivator
object by calling Class.newInstance()
. start()
method of your Activator
class to start the bundle. The bundle can take this opportunity to perform resource initialization such as getting a database connection for future use. The start()
method takes one argument, the BundleContext
object. This object allows bundles to interact with the framework by providing access to OSGi-container-related information. If an exception is thrown for a particular bundle the container will mark that bundle as stopped and will not put it into service. stop()
method of your Activator
class to report that it is shutting down a bundle. You can use this opportunity to perform cleanup tasks such as releasing the database connection. Once your Activator
class is ready you should relay its fully qualified name to the container using your MANIFEST.MF
file.
The MANIFEST.MF
file acts as deployment descriptor for your bundle. The format for this file is the same as that of a normal JAR file, so it consists of a set of headers with values. The OSGi specification defines a set of headers that you can use to describe your bundle to the OSGi container. The MANIFEST.MF file for your Hello World bundle should look as shown in Listing 2.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorld Plug-in
Bundle-SymbolicName: com.javaworld.sample.HelloWorld
Bundle-Version: 1.0.0
Bundle-Activator: com.javaworld.sample.helloworld.Activator
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Import-Package: org.osgi.framework;version="1.3.0"
Let's take a closer look at what each of these headers is used for:
Bundle-ManifestVersion
header tells the OSGi container that this bundle follows the rules of the OSGi specification. A value of
2 means that the bundle is compliant with OSGi specification Release 4; a value of
1 means that it is compliant with Release 3 or earlier.
Bundle-Name
header defines a short, human-readable name for the bundle.
Bundle-SymbolicName
header specifies a unique, non-localizable name for the bundle. This is the name you will use while referring a given bundle from other bundles.
Bundle-Version
header specifies the version of the bundle.
Bundle-Activator
header specifies the name of the optional listener class to be notified of bundle
start and
stop events. In Listing 2 the value of this header is
com.javaworld.sample.helloworld.Activator
.
Bundle-Vendor
header contains a human-readable description of the bundle vendor.
Bundle-Localization
header contains the location in the bundle where localization files can be found. The Hello World bundle doesn't contain any locale-specific files, but the eclipse IDE still generates this header.
Import-Package
header defines imported packages for the bundle. You'll learn more about this when I discuss dependency management, later in the article.
The Hello World bundle is ready, so let's execute it to see the output.
As I mentioned earlier, the Eclipse IDE has an embedded Equinox OSGi container that you can use to execute or debug OSGi bundles. Follow these steps to execute the Hello World bundle:
Hello World Bundle
. com.javaworld.sample.HelloWorld
plugin, which is checked. Under Target Platform, make sure that the checkbox next to the org.eclipse.osgi
plugin is also checked.Your Run dialog should look like the screenshot in Figure 1 (click to enlarge).
<!-- -->
The OSGi console is a command-line interface to the OSGi container. It allows you to do things like start, stop, install bundles, and update or delete bundles. In your Eclipse IDE, click the console view to give focus to that view, then click Enter and you will get an OSGi prompt like the one shown in Figure 2 (click to enlarge).
Here are some of the commonly used OSGi commands that you can use to interact with your OSGi container:
ss
displays a list of installed bundles with the status of each bundle. It will display the bundle ID, short name, and status of the bundle. start <bundleid>
starts a bundle. stop <bundleid>
stops a bundle. update <bundleid>
updates a bundle with a new JAR file. install <bundleURL>
installs a new bundle into the OSGi container. uninstall <bundleid>
uninstalls already installed bundles from the OSGi container. Note that these commands are defined in the OSGi specification so you can use them to interact with any of the OSGi containers.
The OSGi specification allows you to break your application into multiple modules and then manage their dependencies on each other. It does this by adding a bundle scope . By default, none of the classes in a bundle are visible from any other bundle; inside bundles they follow the normal rules of the Java language. So, what do you do if you want to access the classes of one bundle from another bundle? The solution is to export packages from the source bundle and then import them into the target bundle. In this section we'll walk through a sample application that demonstrates this concept.
First, we will create a com.javaworld.sample.HelloService
bundle, which will export a package. Then we will import the package into our com.javaworld.sample.HelloWorld
bundle.
We'll start by creating the com.javaworld.sample.HelloService
bundle and exporting a package from it. Follow these steps to create the bundle and export the package:
com.javaworld.sample.HelloService
bundle by following the same steps used to create the com.javaworld.sample.HelloWorld
bundle in the previous section. HelloService
bundle, create a com.javaworld.sample.service.HelloService.java
interface, as shown in Listing 3. public interface HelloService {
public String sayHello();
}
com.javaworld.sample.service.impl.HelloServiceImpl.java
class implementing the HelloService
interface, as shown in Listing 4. public class HelloServiceImpl implements HelloService{
public String sayHello() {
System.out.println("Inside HelloServiceImple.sayHello()");
return "Say Hello";
}
}
MANIFEST.MF
for the HelloService
package in your Eclipse Manifest editor and go to the Runtime tab. In the Exported Packages section, click Add and select the com.javaworld.sample.service
package. The MANIFEST.MF
file for the HelloService
bundle should look like the one in Listing 5. Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloService Plug-in
Bundle-SymbolicName: com.javaworld.sample.HelloService
Bundle-Version: 1.0.0
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Export-Package: com.javaworld.sample.service
Import-Package: org.osgi.framework;version="1.3.0"
As you can see, the MANIFEST.MF
file for the HelloService
bundle looks very similar to that of the HelloWorld
bundle. The only difference is that this MANIFEST.MF
file has one Export-Package
manifest header, whose value is com.javaworld.simple.service
.
The Export-Package
manifest header informs the OSGi container that classes in the com.javaworld.sample.service
package from the HelloService
bundle can be accessed from outside. Note that in the sample code we have exposed a HelloService
interface but not the HelloServiceImpl
implementation class.
The next step is to update the HelloWorld
bundle to import the com.javaworld.simple.service
package. Here are the steps to import the package:
com.javaworld.sample.HelloWorld
bundle, open the MANIFEST.MF
file in the Plug-in Manifest editor. Now go to the Dependencies tab, and then to Imported Packages . Add com.javaworld.sample.service
as the value of Imported Packages. The MANIFEST.MF
file for your HelloWorld
bundle should look like the one in Listing 6. Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorld Plug-in
Bundle-SymbolicName: com.javaworld.sample.HelloWorld
Bundle-Version: 1.0.0
Bundle-Activator: com.javaworld.sample.helloworld.Activator
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Import-Package: com.javaworld.sample.service, org.osgi.framework;version="1.3.0"
Import-Package
header is a comma-separated list of packages that this bundle wants to import. In the sample code the HelloWorld
bundle imports two packages: com.javaworld.sample.service
and org.osgi.framework
.org.osgi.framework
package contains OSGi framework classes such as BundleContext
and BundleActivator
, which are used by the Activator.java
class of the HelloWorld
bundle. com.javaworld.sample.helloworld.Activator.java
in the Eclipse Java editor. You will notice that you are now able to access the HelloService
interface but not the HelloServiceImpl
class. This is because the HelloService
package exports (and the HelloWorld
package imports) the com.javaworld.sample.service
package. HelloServiceImpl
is an internal class for the HelloService
bundle and no other bundle can access it. <!-- -->
If you try running the sample service package now it will print "Hello World" on your Eclipse console. If you try running Activator.java
to access HelloServiceImpl.java
and compile it with the normal javac
complier, it will compile. If you try to execute this bundle in an OSGi container, however, it will throw an exception.
How is the OSGi container able to hide a few classes from the .jar file while others are visible? The answer is that the OSGi container uses the Java class loader to manage class visibility. The OSGi container creates a different class loader for every bundle. The bundle can therefore access classes from
java.*
packages. HelloWorld
bundle imports the com.javaworld.sample.service
package so it can access classes from that package. The bundle-level scope is a powerful feature allowing you (for instance) to safely change the HelloServiceImpl.java
class without worrying that dependent code might break.
As previously mentioned, the OSGi architecture is a very good candidate for implementing service-oriented applications. It allows bundles to export services that can be consumed by other bundles without knowing anything about the exporting bundle. Taken with the ability to hide the actual service implementation class, OSGi provides a perfect combination for service-oriented applications.
In OSGi, a source bundle registers a POJO (you don't have to implement any interfaces or extend from any superclass) with the OSGi container as a service under one or more interfaces. The target bundle can then ask the OSGi container for services registered under a particular interface. Once the service is found, the target bundle binds with it and can start calling its methods. As with other OSGi concepts, a sample application will make all of this more clear.
In this section we will change the HelloService
bundle so that it exports objects of the HelloServiceImpl.java
class as a service. Here are the steps to set up the sample application:
MANIFEST.MF
for com.javaworld.sample.HelloService
to import the org.osgi.framework
package. com.javaworld.sample.service.impl.HelloServiceActivator.java
as shown in Listing 7. public class HelloServiceActivator implements BundleActivator {
ServiceRegistration helloServiceRegistration;
public void start(BundleContext context) throws Exception {
HelloService helloService = new HelloServiceImpl();
helloServiceRegistration =context.registerService(HelloService.class.getName(), helloService, null);
}
public void stop(BundleContext context) throws Exception {
helloServiceRegistration.unregister();
}
}
BundleContext.registerService()
method to export the service. It takes three parameters:
String
array of interface names and pass it as your first argument. In the sample code we want to export the service under the name of HelloService
interface. HelloServiceImpl
class as the service. Dictionary
object. If more than one bundle exports a service under the same interface name then the target object can use these properties to filter out the service that it is interested in. MANIFEST.MF
file of the HelloService
bundle to declare HelloServiceActivator
as the bundle's activator class. To do that, simply add com.javaworld.sample.service.impl.HelloServiceActivator
as the value of the Bundle-Activator
header in your MANIFEST.MF
file. Now the HelloService
bundle is ready to export objects of the HelloServiceImpl
class. When the OSGi container starts the HelloService
bundle it will pass control to HelloServiceActivator.java
, which will register an object of HelloServiceImpl
as a service. The next step is to create the service consumer.
In this section we will change the HelloWorld
bundle developed in the last section so that it acts as a consumer of the HelloService
service. The main thing you need to do is change the Activator.java
for the HelloWorld
bundle as shown in Listing 8.
public class Activator implements BundleActivator {
ServiceReference helloServiceReference;
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
helloServiceReference= context.getServiceReference(HelloService.class.getName());
HelloService helloService =(HelloService)context.getService(helloServiceReference);
System.out.println(helloService.sayHello());
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
context.ungetService(helloServiceReference);
}
}
The BundleContext.getServiceReference()
method returns a ServiceReference
object for a service registered under the HelloService
interface. If multiple such services exist, the service with the highest ranking (as specified in its Constants.SERVICE_RANKING
property) is returned. Once you have the object of ServiceReference
you can call its BundleContext.getService()
method to get the actual service object.
You can run this sample the same way you would execute any bundle, by clicking Run --> Run Simply make sure that both the HelloWorld
and HelloService
bundles are checked in as plugins. When you start the HelloService
bundle, you will see the message "Inside HelloServiceImple.sayHello()
" printed from the HelloServiceImpl.sayHello()
method.
<!-- -->
In the last section you learned how to use the OSGi framework to create a Java object and register it as service to be consumed by any other bundle. If you look at the HelloServiceActivator.start()
method you will notice that we created an object of the HelloServiceImpl
class in the start method and registered it under the name of HelloService
interface. Thereafter, whenever any bundle asks for the HelloService
service, the OSGi container will return the same object.
This implementation will work for most common use cases. But let's say you want to return a different object of the HelloServiceImpl
class for every consumer bundle. Alternately, let's say your service object needs to open a connection to a database and you don't want to open the connection unless someone really needs the service.
In either case, the solution is to create a class implementing the ServiceFactory
interface and register its object, instead of the actual service object, as a service. Once you do that, your ServiceFactory
class will take control whenever any bundle requests the service. ServiceFactory
will create a new object of Service
for every bundle, and will also delay creation of the actual service until someone really needs it.
Follow these steps to change the implementation of the com.javaworld.sample.HelloService
bundle developed in the last section to use a ServiceFactory
:
HelloServiceFactory.java
, as shown in Listing 9. public class HelloServiceFactory implements ServiceFactory{
private int usageCounter = 0;
public Object getService(Bundle bundle, ServiceRegistration registration) {
System.out.println("Create object of HelloService for " + bundle.getSymbolicName());
usageCounter++;
System.out.println("Number of bundles using service " + usageCounter);
HelloService helloService = new HelloServiceImpl();
return helloService;
}
public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
System.out.println("Release object of HelloService for " + bundle.getSymbolicName());
usageCounter--;
System.out.println("Number of bundles using service " + usageCounter);
}
}
ServiceFactory
interface defines two methods:
BundleContext.getService(ServiceReference)
method. In Listing 9, we use this method to create a different object of HelloServiceImpl
for every bundle, and we return that object. The OSGi framework caches the value returned (unless it is null), and will return the same service object on any future calls to BundleContext.getService()
from the same bundle. usageCount
of the service and print the number of clients for the service. HelloServiceActivator.java
and modify the start()
method of your activator so that it will register objects of ServiceFactory
instead of HelloService
, as shown in Listing 10: public class HelloServiceActivator implements BundleActivator {
ServiceRegistration helloServiceRegistration;
public void start(BundleContext context) throws Exception {
HelloServiceFactory helloServiceFactory = new HelloServiceFactory();
helloServiceRegistration =context.registerService(HelloService.class.getName(), helloServiceFactory, null);
}
public void stop(BundleContext context) throws Exception {
helloServiceRegistration.unregister();
}
}
Now try running this sample code. You should notice that it prints the service usage count as one (1) when HelloWorld
bundle is started and zero (0) when the HelloWorld
bundle is stopped.
In the "OSGi services" section you learned how to search for a service using its interface name. But what happens if more than one bundle registers a service under the same interface name? In that case the OSGi container will return the service with the highest ranking -- that is, the service that is registered with the highest valued SERVICE_RANKING
property. If more than one service has an equally valued SERVICE_RANKING
property, then the OSGi container will return the service with the lowest PID.
But let's say that you are creating a consumer that needs to know whenever an object is registered or unregistered under a particular interface. In this situation, you should use the ServiceTracker
class. Let's see what happens when we change the sample code to utilize a service tracker, as described below.
MANIFEST.MF
for your HelloWorld
bundle to import the org.osgi.util.tracker
package. HelloServiceTracker.java
as shown in Listing 11. public class HelloServiceTracker extends ServiceTracker {
public HelloServiceTracker(BundleContext context) {
super(context, HelloService.class.getName(),null);
}
public Object addingService(ServiceReference reference) {
System.out.println("Inside HelloServiceTracker.addingService " + reference.getBundle());
return super.addingService(reference);
}
public void removedService(ServiceReference reference, Object service) {
System.out.println("Inside HelloServiceTracker.removedService " + reference.getBundle());
super.removedService(reference, service);
}
}
HelloServiceTracker
class, we are passing the name of the HelloService
interface to its super class, which is the equivalent of saying that HelloServiceTracker
should track services registered under the HelloService
interface name. The HelloServiceTracker
class extends the ServiceTracker
class and implements two methods:
Activator.java
so that it starts using the HelloServiceTracker
class to manage services instead of looking them up directly, as shown in Listing 12. public class Activator implements BundleActivator {
HelloServiceTracker helloServiceTracker;
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
helloServiceTracker= new HelloServiceTracker(context);
helloServiceTracker.open();
HelloService helloService = (HelloService)helloServiceTracker.getService();
System.out.println(helloService.sayHello());
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
helloServiceTracker.close();
}
}
start()
method we first create a HelloServiceTracker
object, then we ask HelloServiceTracker
to start tracking the underlying service. A call to getService()
will now get the HelloService
object. If you try executing this sample you will notice that whenever you start or stop the HelloService
bundle it will result in a call to either the addingService()
or removedService()
method of the HelloServiceTracker
object.
In this first article in the three-part Hello, OSGi series, I've introduced you to the basic concepts of modular application development with OSGi. You've learned about the three currently available open source OSGi containers and walked through the steps to develop a simple component bundle using Equinox, the fully OSGi-compliant Eclipse container. You've also learned how bundles can interact by importing and exporting each other's packages and services.
In this article you may have noticed one of the challenges of developing OSGi bundles: that each of your bundles needs to be aware of OSGi AP. In some development scenarios this could mean writing a lot of infrastructure code. In the next article in the Hello, OSGi series I'll introduce you to the Spring Dynamic Modules for OSGi Service Platforms project, which simplifies development of Spring-based OSGi bundles. See the Resources section in the meantime, to learn more about OSGi .
Sunil Patil is a Java Enterprise/Portlet developer working for Ascendant Technology in San Francisco, California. He is the author of Java Portlets 101 (SourceBeat, April 2007) and has written numerous articles published by O'Reilly Media. Sunil was a member of IBM's WebSphere Portal Server development team for three years and is actively involved in the Pluto community. In addition to being an IBM Certified WebSphere Portal Server Application Developer for both v5.0 and v5.1, he is a Sun Microsystems Certified Java Programmer, a Web component developer, and a business component developer. You can view Sunil's blog at http://jroller.com/page/SunilPatil .
All contents copyright 1995-2008 Java World, Inc. http://www.javaworld.com