Friday, March 18, 2011

Introduction to OSGi

As my first blog entry here I want to introduce you to the OSGi framework. It allows you to build Java applications with high modularity and I believe that this technology will become more and more important in the future. The goal of this introduction is that the reader has a quick overview about the OSGi framework and that he understands the benefits of it. Further, this should be an introduction to my planned series of Eclipse plug-in development.

1 What is OSGi?

The OSGi Alliance (formerly known as Open Services Gateway initiative, now an obsolete name), founded in March 1999, originally specified and maintains the OSGi specification. This specification allows you to write so called bundles, self contained modules that can provide functionality to other bundles. There are several implementations of this specification, the most common ones are:
  • Eclipse Equinox
  • Apache Felix
  • Knoplerfish

1.1 Why OSGi?

As mentioned before, the OSGi specification aims to create Java applications with extremely high modularity. Of course, this is what every developer aims at, but OSGi encourages and supports you in doing so. Also a great feature is that you can install or uninstall such bundles during runtime of the application without restarting it. It also supports lazy loading. This means that you can configure a bundle to be loaded when it’s called the first time. This truly increases the performance of an application. The OSGi specification also defines some useful services, that have to be provided by an OSGi implementation.
What does that mean to CTP? As high modularity is becoming more and more important, clients will have the need to extend their application. As seen in the sM-Client project, for example, the client wants to make small extensions like custom forms without packaging a whole new release. Which is quite understandable. Now a plug-in style mechanism is evaluated, but there may be problems, especially with classloading, which are well solved in OSGi.

2 Architecture

The OSGi framework is based on several Layers:

Security layer: The OSGi Security Layer is an optional layer that underlies the OSGi Service
Platform. The layer is based on the Java 2 security architecture. It provides
the infrastructure to deploy and manage applications that must run in finegrained
controlled environments.

Module layer: The Module Layer is responsible for the packaging of the modules (“bundles”), those are simple JAR files which follow some requirements. I will explain more about bundles in section 2.1.

Life cycle layer: This layer gives you the possibility to install, uninstall, start and stop bundles inside an application framework without restarting the whole framework, (e.g. if you contribute a bug fix inside a bundle). This also increases the availability of your application framework.

Service Layer: The Service Layer enables you to provide OSGi services to other bundles. An OSGi service is nothing but a POJO, that is registered in the Service Registry and can be referenced from everywhere outside the declaring bundle. The service has to implement an agreed interface.


2.1 Bundles

As the description of the Module Layer says, a bundle is a plain old JAR file. But it has some special characteristics. There has to be a MANIFEST.MF file in the META-INF directory, which is located at the root of the JAR file (see the JAR specification). This file provides several meta information about the bundle like dependencies, exported and imported packages etc. Note, that in difference to a normal JAR file, all packages are hidden by default to other bundles. So you have to define which packages are exported and therefore are accessible from other bundles. Additionally, you have to specify which packages you want to import from other bundles. Also you can define required bundles, which will import all exported packages of the required bundles. Let’s have a look at an example MANIFEST.MF file:

Bundle-Name: Hello World
Bundle-SymbolicName: com.ctp.helloworld
Bundle-Description: A Hello World bundle
Bundle-ManifestVersion: 2
Bundle-Version: 1.0.0
Bundle-Activator: com.ctp.Activator
Export-Package: com.ctp.helloworld;version="1.0.0"
Import-Package: org.osgi.framework;version="1.3.0"
Require-Bundle: com.ctp.other;bundle-version="2.5.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.6

These configuration elements are explained as follows:
  • Bundle-Name: Defines a human-readable name for this bundle, Simply assigns a short name to the bundle.
  • Bundle-SymbolicName: The only required header, this entry specifies a unique identifier for a bundle, based on the reverse domain name convention (also used by the java packages).
  • Bundle-Description: A description of the bundle's functionality.
  • Bundle-ManifestVersion: This little known header indicates the OSGi specification to use for reading this bundle.
  • Bundle-Version: Designates a version number to the bundle.
  • Bundle-Activator: Indicates the class name to be invoked once a bundle is activated.
  • Export-Package: Expresses what Java packages contained in a bundle will be made available to the outside world.
  • Import-Package: Indicates what Java packages will be required from the outside world, in order to fulfill the dependencies needed in a bundle.
  • Require-Bundle: Indicates what OSGi bundles will be required from the application framework. This will import all exported packages from the specified bundle(s).
  • Bundle-RequiredExecutionEnvironment: Specifies the minimum execution environment(s) required to run this bundle.

2.1.1 Bundle life cycles

A bundle’s life cycle could have the following states (managed by OSGi’s Life Cycle Layer):
Bundle StateDescription
INSTALLEDThe bundle has been successfully installed.
RESOLVEDAll Java classes that the bundle needs are available. This state indicates that the bundle is either ready to be started or has stopped.
STARTINGThe bundle is being started, the BundleActivator.start method will be called, and this method has not yet returned. When the bundle has an activation policy, the bundle will remain in the STARTING state until the bundle is activated according to its activation policy.
ACTIVEThe bundle has been successfully activated and is running; its Bundle Activator start method has been called and returned.
STOPPINGThe bundle is being stopped. The BundleActivator.stop method has been called but the stop method has not yet returned.
UNINSTALLEDThe bundle has been uninstalled. It cannot move into another state.

2.2 OSGi Services

OSGi Services are Java objects, that implement agreed interfaces. This means that the Service itself is defined by a Java interface and the bundles which use the service don’t have to know where the specific implementation actually is. The following is a simple OSGi Service:

The service interface:
public interface HelloWorld {
public String getMessage();
}
The service implementation:
package com.ctp.helloworld.service.impl;

import com.ctp.helloworld.service.HelloWorld;

class HelloWorldImpl implements HelloWorld {
@Override
public String getMessage() {
return “Hello World”;
}
}
Now, without any tools like Spring Dynamic Modules, iPOJO or Declarative Services, we have to register our service manually so that it can be accessed by other bundles. I will explain this in the following section.
Please note that the -Impl naming convention does not really make sense in OSGi, since it’s possible to have multiple implementations of one service, it was just the easiest for this example.

3 Example

Let’s write a simple bundle that contains the HelloWorld service we created above. First, download an OSGi framework (I’m using Equinox 3.6, but this example should work on all implementations since it’s very basic). This example is made as you could get going without any special IDE. The source code can be downloaded here.

Copy the downloaded jar file to a location where you would like to start your application (further called $APP_HOME).

Now create a work folder, where your source files are (further called $SRC_HOME).
Create the following folders and files:
$SRC_HOME/com/ctp/helloworld/service/HelloWorld.java
$SRC_HOME/com/ctp/helloworld/service/impl/HelloWorldImpl.java
$SRC_HOME/com/ctp/helloworld/Activator.java
$SRC_HOME/META-INF/MANIFEST.MF

The MANIFEST.MF file is very basic and should have the following contents:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello World
Bundle-SymbolicName: com.ctp.helloworld
Bundle-Version: 1.0.0
Bundle-Activator: com.ctp.helloworld.Activator
Bundle-Vendor: CTP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-ActivationPolicy: lazy


Make sure you have a carriage return and/or newline character at the end of the last line as this is required (see manifest specification in the JAR specification).

Let’s create the OSGi Service as the example in section 2.2:

$SRC_HOME/com/ctp/helloworld/service/HelloWorld.java:

package com.ctp.helloworld.service;

public interface HelloWorld {
public String getMessage();
}

$SRC_HOME/com/ctp/helloworld/service/impl/HelloWorldImpl.java:
package com.ctp.helloworld.service.impl;

import com.ctp.helloworld.service.HelloWorld;

public class HelloWorldImpl implements HelloWorld {

@Override
public String getMessage() {
return "Hello World";
}
}


The defined activator ($SRC_HOME/com/ctp/helloworld/Activator.java) should have the following contents so that it is able to run:
package com.ctp.helloworld;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

private static BundleContext context;

static BundleContext getContext() {
return context;
}

public void start(BundleContext bundleContext) throws Exception {
Activator.context = bundleContext;
}

public void stop(BundleContext bundleContext) throws Exception {
Activator.context = null;
}

}

At this state, the OSGi bundle should be ready to compile and deploy it. But first, we want to register our service. We do this in the method com.ctp.helloworld.Activator#start:
import org.osgi.framework.ServiceReference;
import com.ctp.helloworld.service.HelloWorld;
import com.ctp.helloworld.service.impl.HelloWorldImpl;
...
public void start(BundleContext bundleContext) throws Exception {
System.out.println("Registering HelloWordld service...");

// this will register the service in the Service Registry
bundleContext.registerService(HelloWorld.class.getName(), new HelloWorldImpl(), null);

// Test the availability of the service
ServiceReference ref = bundleContext.getServiceReference(HelloWorld.class.getName());

if(ref == null) {
System.out.println("Service is not registered...");
} else {
// it’s also possible to get an array of services...
HelloWorld service = (HelloWorld)bundleContext.getService(ref);
System.out.println("HelloWorld#getMessage(): " + service.getMessage());
}
Activator.context = bundleContext;
}
...

Now we can compile the code and create a JAR file:
cd $SRC_HOME;
javac -cp $APP_HOME/org.eclipse.osgi_3.6.0.v20100517.jar com/ctp/helloworld/service/HelloWorld.java com/ctp/helloworld/service/impl/HelloWorldImpl.java com/ctp/helloworld/Activator.java;
jar cvfm example.jar META-INF/MANIFEST.MF com;
cp example.jar $APP_HOME/example.jar;

If your bundle was built correctly you could start your test application:
cd $APP_HOME;
java -jar org.eclipse.osgi_3.6.0.v20100517.jar -console;

Now you can administrate the OSGi platform with the console. Test the bundle like the following:
osgi>install file:///$APP_HOME/example.jar
Bundle id is 4

osgi>start 4
Registering HelloWorld service...
HelloWorldService#getMessage(): Hello World

osgi>

NOTE: If you want to use the service inside another bundle, you have to add the package com.ctp.helloworld.service to the Export-Package directive in the MANIFEST.MF file.

4 Multiple Implementations

This example shows you how to use multiple implementations of a service. It’s a solution which I think, personally, is quite elegant. I won’t go through all steps that we already did in the previous example. A source code can be downloaded here. Let’s create a bundle with the symbolic name com.ctp.playground.multiple and the following classes:
com.ctp.playground.multiple.Activator
com.ctp.playground.multiple.math.OperationService
com.ctp.playground.multiple.math.OperationServiceFactory
com.ctp.playground.multiple.math.internal.Addition
com.ctp.playground.multiple.math.internal.Substraction

Also create the following Enum:
com.ctp.playground.multiple.math.Operation

The bundle activator should have the following contents (don’t forget the imports):
public class Activator implements BundleActivator {

private static BundleContext context;

public static BundleContext getContext() {
return context;
}

public void start(BundleContext bundleContext) throws Exception {
Activator.context = bundleContext;
}

public void stop(BundleContext bundleContext) throws Exception {
Activator.context = null;
}

}

The interface com.ctp.playground.multiple.OperationService has one method:
public Double doOperation(Double a, Double b);

The classes Addition and Substraction implement this interface and are our actual services:
@Override
public Double doOperation(Double a, Double b) {
return a + b;
}
and:
@Override
public Double doOperation(Double a, Double b) {
return a - b;
}
The Enum Operation looks like the following (don’t forget the imports):
public enum Operation {
ADDITION {
@Override
public Class<?> getServiceClass() {
return Addition.class;
}
}, SUBSTRACTION {
@Override
public Class<?> getServiceClass() {
return Substraction.class;
}
};
public abstract Class<?> getServiceClass();
}

And now comes the part where we get our service implementations and decide which one to take. For that we created the Class OperationServiceFactory. Now extend it with the following method:
public static OperationService getService(Operation o) {
BundleContext bundleContext = Activator.getContext();
ServiceReference[] refs = null;
try {
// get the service references
refs = bundleContext.getServiceReferences(OperationService.class.getName(),null);

} catch (InvalidSyntaxException e) {
// this should never happen, because the second argument
// of bundleContext.getServiceReferences() is null.
// You have the possibility to give filter criterias with
// this parameter
}
OperationService service = null;
if(refs != null) {
for(int i = 0;i<refs.length;i++) {
service = (OperationService) bundleContext.getService(refs[i]);
// check, if the service is the wanted one
if(service.getClass().equals(o.getServiceClass())) {
return service;
}
}
}
return null;
}

All we have to do now is to register the services in the Activator class and test them. Extend the com.ctp.playground.multiple.Activator:
...
public void start(BundleContext bundleContext) throws Exception {

Activator.context = bundleContext;
// register services
bundleContext.registerService(OperationService.class.getName(), new Addition(), null);
bundleContext.registerService(OperationService.class.getName(), new Substraction(), null);

// test the services
OperationService operation = OperationServiceFactory.getService(Operation.ADDITION);
Double result = operation.doOperation(10.0, 20.0);
System.out.println(result);
operation = OperationServiceFactory.getService(Operation.SUBSTRACTION);
result = operation.doOperation(10.0, 20.0);
System.out.println(result);
}
...

The MANIFEST.MF file should have the following contents:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Multiple Service implementations Example
Bundle-SymbolicName: com.ctp.playground.multiple
Bundle-Version: 1.0.0
Bundle-Activator: com.ctp.playground.multiple.Activator
Bundle-Vendor: CTP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-ActivationPolicy: lazy
Export-Package: com.ctp.playground.multiple.math

Now build the bundle (compile and make jar file), put it in the $APP_HOME directory, run the platform and install / start it like we did in the previous example.
You should have the following output after starting the bundle:

osgi> 30.0
-10.0

5 Conclusion

Let’s summarize and see if you reached your targets:
  • OSGi is a framework specification for creating highly modular Java applications
  • A bundle is a simple jar file, defined through manifest headers
  • An OSGi Service is a simple POJO that needs to be registered in the OSGi Service Registry
  • You can create a simple bundle
  • You are able to create a simple OSGi Service
  • You can manage multiple service implementations


As I mentioned at the top of this blog, this is the first part of my series about OSGi and Eclipse plug-in development. I will publish the blogs like the following (there may be changes):
  1. Introduction to OSGi
  2. OSGi in a declarative way
    How to use additional frameworks like Spring DM or Declarative Services
  3. OSGi and Eclipse
    The way how Eclipse uses OSGi and introduction to Eclipse plug-in development
  4. Eclipse UI
    How the Eclipse UI works and how to use it
  5. Eclipse SDK 4.0 - Model driven development
    All about the new Eclipse SDK and its model driven approach
  6. Eclipse SDK 4.0 - New features
    New features of the SDK like XWT and usage of dependency injection

I hope you enjoyed this little introduction to OSGi and I’m looking forward to read your comments, so I could improve my further planned blogs.