close

The OSGi is a specification that defines and communicates the modularity of a Java application in a more dynamic way. Traditionally, a Java application is modularized as a JAR bundle. But working with JAR files has its limitations:

  • JAR bundles are resolved through a class path environment variable, which doesn’t provide a robust framework to manage JAR dependencies.
  • JAR bundles can’t be versioned; therefore, you can’t track the history of created or modified JAR bundles.
  • There’s no framework to update the JAR files dynamically at run time whenever there’s a code change.

To address the above issues, you can use the OSGi framework because it redefines the modular system of Java. An OSGi-based system has the following advantages over the traditional JAR modules:

  • OSGi provides a robust integrated environment where bundles can be published as services and exported for other bundles to use.
  • OSGi provides versioning of bundles for every new deployment. You can therefore track the history of bundle creation and changes.
  • With OSGi, you can update the bundles dynamically at run time whenever there’s a code change.

There are currently three known implementations of OSGi:

  • Equinox
  • Knopflerfish
  • Felix

To get started with OSGI, lets develop a sample application – order application with client-side and server-side components. In this example we could cover OSGI API. In next blog, we would introduce Spring. Then we would package these components as OSGi bundles. The client invokes the service component to process the order. The service component has a method that processes the order and prints the order ID. After reading this article, you can apply the concepts and features of Apache Felix to build and package Java component classes as OSGi bundles. To develop and run the examples in this article, make sure the following software is installed and set up on your machine:

  • Java 5 or higher
  • Ant build tool
  • Apache Felix 1.0.4 or higher

Order application

Let’s look at how to create an order application bundle using a Felix-based OSGi framework. The application has two components: OrderClient.java (on the client side) and OrderService.java (on the server side). The client component is bundled as client.jar, the server component as order.jar. Lets first look at the OrderClient class.
Listing 1. The client component OrderClient

public class OrderClient implements BundleActivator {

<%%KEEPWHITESPACE%%>  private ServiceTracker orderTracker;
<%%KEEPWHITESPACE%%>  private OrderService orderService;

<%%KEEPWHITESPACE%%>  public void setService(OrderService orderService) {
<%%KEEPWHITESPACE%%>    this.orderService = orderService;
<%%KEEPWHITESPACE%%>  }

<%%KEEPWHITESPACE%%>  public void removeService() {
<%%KEEPWHITESPACE%%>    this.orderService = null;
<%%KEEPWHITESPACE%%>  }

<%%KEEPWHITESPACE%%>  public void start(BundleContext context) throws Exception {
<%%KEEPWHITESPACE%%>   orderTracker =
<%%KEEPWHITESPACE%%>   new ServiceTracker(context, OrderService.class.getName(), null);
<%%KEEPWHITESPACE%%>   orderTracker.open();
<%%KEEPWHITESPACE%%>   OrderService order = (OrderService) orderTracker.getService();

<%%KEEPWHITESPACE%%>   if (order == null) {
<%%KEEPWHITESPACE%%>     System.out.println("Order service not available");
<%%KEEPWHITESPACE%%>   } else {
<%%KEEPWHITESPACE%%>     order.processOrder();
<%%KEEPWHITESPACE%%>   }
<%%KEEPWHITESPACE%%>  }

<%%KEEPWHITESPACE%%>  public void stop(BundleContext context) {
<%%KEEPWHITESPACE%%>   System.out.println("Bundle stopped");
<%%KEEPWHITESPACE%%>   orderTracker.close();
<%%KEEPWHITESPACE%%> }

}

As you can see in Listing 1, OrderClient invokes the processOrder method on the OrderService component to print the orderID when the client.jar bundle is started. The class implements the BundleActivator interface, which has two callback methods: start() and stop(). The Felix container calls the implemented start() and stop() methods when the client bundle is started and stopped.

Let’s look closely at the start() method. In the start() method, you first get the reference of the ServiceTracker class. You can think of this class as a factory class. Then you get the service reference from this factory class by calling its getService method. The ServiceTracker is constructed with the following parameters: bundle context and the name of the OrderService class.
Listing 2. OrderService implementation

public class OrderServiceImpl implements OrderService, BundleActivator {

<%%KEEPWHITESPACE%%>  private ServiceRegistration registration;

<%%KEEPWHITESPACE%%>  public void start(BundleContext context) {
<%%KEEPWHITESPACE%%>   registration =
<%%KEEPWHITESPACE%%>   context.registerService(OrderService.class.getName(), this, null);
<%%KEEPWHITESPACE%%>   System.out.println("Order Service registered");
<%%KEEPWHITESPACE%%> }

<%%KEEPWHITESPACE%%>  public void stop(BundleContext context) {
<%%KEEPWHITESPACE%%>   System.out.println("Order Service stopped");
<%%KEEPWHITESPACE%%> }

<%%KEEPWHITESPACE%%>  public void processOrder() {
<%%KEEPWHITESPACE%%>   System.out.println("Order id: ORD123") ;
<%%KEEPWHITESPACE%%>  }
}

The server side order.jar file contains two components: the OrderService interface and OrderServiceImpl class. The interface has an abstract method processOrder that’s implemented by the OrderServiceImpl class. The class also implements a BundleActivator interface that’s invoked by the Felix container to start and stop the order.jar bundle. The start() method registers the OrderService component in the registry for the client bundle to use. Registering is the same as exporting the objects for other bundles to use.


Communication through Manifest

The question really is, how does the client bundle know about the registered service bundles? This communication is handled through the use of Manifest files. In the Manifest file, you provide the reference of bundles as part of import and export packages. The client bundle Manifest typically imports the service component package, while the service bundle Manifest exports its own package. Note that when bundle A imports a package of bundle B, then bundle B has to export its own package. The communication fails without the proper import and export definition.
Listing 3. Client Manifest file

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Order Service Client
Bundle-SymbolicName: orderclient
Bundle-Version: 1.0.0
Bundle-Activator: order.client.OrderClient
Import-Package: org.osgi.framework, org.osgi.util.tracker, order

Listing 4. Service Manifest file

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Order Service
Bundle-SymbolicName: orderservice
Bundle-Version: 1.0.0
Export-Package: order
Bundle-Activator: order.impl.OrderServiceImpl
Import-Package: org.osgi.framework

With your order application, the client.jar bundle Manifest has the entry Import-Package: org.osgi.framework, org.osgi.util.tracker, order. This effectively means that the client bundle imports core OSGi packages and the OrderService package. Similarly, the order.jar bundle Manifest has the entry Export-Package: order. This means that the bundle exports its packages for the client to use. OSGi throws runtime errors if the imports and exports aren’t explicitly declared.

The Manifest file also has other information, such as the name of the bundle activator class. The activator class is responsible for invoking start() and stop() methods in the bundles. In this case, the activator class for the client.jar bundle is OrderClient and OrderService for the order.jar bundle.


Deployment

Before deploying and playing around with bundles:

  1. Create the directory structure shown in Figure 1 under your root C:\osgi folder, and put the components covered previously in this article into it:
    • The Java code goes into the respective package folders (both client and service).
    • Manifest files go into respective META-INF folders (both client and service).

    Figure 1. Code directory structure
    folder_structure

The next step is to deploy these bundles. For building the source code, we would use ANT. A sample build file is provided in each steps below. You use the Felix OSGi container to deploy the client and service bundles following these steps:

    1. Copy the following build.xml in service folder and run it using ANT.
<project name="Order Service – OSGi" default="dist.service" basedir=".">
<property name="felix.home" location="/felix-1.0.4″/>
<property name="service.src" location="${basedir}/src"/>
<property name="service.build" location="${basedir}/build"/>
<property name="bin" location="${basedir}/bin"/>
<property name="felix.lib" location="${felix.home}/bin"/>
<property name="version" value="1.0″/>

<target name="clean" description="clean up">
<delete dir="${bin}"/>
<delete dir="${service.build}"/>
</target>

<target name="init" depends="clean">
<tstamp/>
<mkdir dir="${service.build}"/>
<mkdir dir="${bin}"/>
</target>

<target name="compile.service" depends="init">
<javac srcdir="${service.src}" destdir="${service.build}">
<classpath>
<pathelement path="${service.src}"/>
<pathelement location="${felix.lib}/felix.jar"/>
</classpath>
</javac>
</target>

<target name="dist.service" depends="compile.service">
<jar jarfile="${bin}/order.jar" basedir="${service.build}" manifest="${basedir}/META-INF/Manifest.mf" />
</target>

</project>
  1. Copy the build.xml in client folder and run it using ANT.
<project name="Order Client – OSGi" default="dist.client" basedir=".">
<property name="felix.home" location="/felix-1.0.4″/>
<property name="client.src" location="${basedir}/src"/>
<property name="client.build" location="${basedir}/build"/>
<property name="felix.lib" location="${felix.home}/bin"/>
<property name="service.lib" location="${basedir}/../service/bin"/>
<property name="bin" location="${basedir}/bin"/>

<property name="version" value="1.0″/>

<target name="clean" description="clean up">
<delete dir="${bin}"/>
<delete dir="${lib}"/>
<delete dir="${client.build}"/>
</target>

<target name="init" depends="clean">
<tstamp/>
<mkdir dir="${client.build}"/>
<mkdir dir="${bin}"/>
</target>

<target name="compile.client" depends="init">
<javac srcdir="${client.src}" destdir="${client.build}">
<classpath>
<pathelement path="${client.src}"/>
<pathelement location="${felix.lib}/felix.jar"/>
<pathelement location="${service.lib}/order.jar"/>
</classpath>
</javac>
</target>

<target name="dist.client" depends="compile.client">
<jar jarfile="${bin}/client.jar" basedir="${client.build}" manifest="${basedir}/META-INF/Manifest.mf" />
</target>

</project>

After you execute the above build files, client.jar and order.jar bundles are created in the client/bin and service/bin folders, respectively.

    1. Start the felix runtime using the following command

java -Dfelix.config.properties=file:/felix-1.0.4/conf/config.properties -jar /felix-1.0.4/bin/felix.jar

Every time you start the Felix run time, it prompts you for the profile name. The profile name acts more like a project name. You can provide any name as your project profile. With every subsequent start of the Felix run time, if you provide the same profile name previously entered, Felix loads all the installed bundles associated with that project name. If you give a new profile name, then you need to install the bundles explicitly again:

  1. Install the bundle by providing the following commands on the Felix shell:
    • install file:service/bin/order.jar
    • install file: client/bin/client.jar
    • start service_bundle_id
    • start client_bundle_id

To indicate the IDs of the bundle, you can give the ps command. You have to first start the service bundle and then the client bundle. Upon starting the respective bundles, the following output is displayed (see Figure 2).
Figure 2. Program output
output

You can also update the bundles by providing the command update bundle_id at the Felix shell. The bundle is updated on the fly at run time.


Conclusion

This article briefly described the features and concepts of the OSGi framework and demonstrated how you can use it to create dynamic JAR bundles. You learned about building and packaging components as OSGi bundles and running them in a Felix runtime environment. You also looked at creating bundle Manifest files, which act as a communication interface between bundles.

OSGi has given a new dimension to the way JAR bundles are built and managed. Stay tuned for Part 2 of this series, which will show you how the Spring framework takes over the responsibility for managing bundles from OSGi in an OSGi environment.

This article of mine was first published by IBM DeveloperWorks at http://www.ibm.com/developerworks/webservices/library/ws-osgi-spring1/. All rights retained by IBM and the author.

Tags : OSGIspringtutorial
Navveen

The author Navveen