p5rn7vb
osgi modular java | Swarm of XeBees

OSGi : Modular Java

{0 Comments}

  • Sharebar

What is OSGI ?

Open Source Gateway Initiative (OSGi) service platform is a technology which enables the complex Java applications to be modularized easily and effectively. OSGi also known as Dynamic Module System for Java, helps to divide an application into different layers (bundles) ensuring separation of concerns is met effectively. As a continuation of its modularity support, it introduces service-oriented programming model (SOA), to help you clearly separate the interface from implementation. The OSGi Service Platform is essentially an industry standard defined to specifically address the lack of support for modularity in the Java platform.

What do we mean by lack of modularity support in Java?

Let’s first understand what we mean by modularity, modularity is more or less dividing an application into logical parts representing separate concerns. Although Java provides various access modifiers to control visibility (such as public, protected, private and package), but these modifiers can control only very low level object-oriented encapsulation and not logical system partitioning. Java also has a concept of package which is typically used for partitioning code. For code to be visible from one package to another it must be declared public (or protected if using inheritance). But this means any dependencies among the packages must be exposed as public, which makes them accessible to everyone else too.

Let’s understand this problem with a simple example

A)

package com.vinsworld.jungle;

public interface Animal {
	public void makeSomeNoise();

}

B)
package com.vinsworld.jungle.impl;

import com.vinsworld.jungle.Animal;

public class Lion implements Animal {

	public void makeSomeNoise() {
		System.out.println("Ghrrrrrrr");
	}

}
C)
package com.vinsworld.jungle.test;

import com.vinsworld.jungle.Animal;
import com.vinsworld.jungle.impl.Lion;

public class AnimalTest {

	public static void main (String []args){

		Lion lion = new Lion();
		animal.makeSomeNoise();
	}
}

Now here if such a code(A and B) is a part of some API then author will always want the client (C) to interact with the application via Animal interface, but nothing is stopping client from creating a new Lion object using its public constructor as is done in (C).

We can argue here that this could have been achieved in a better way by creating a protected constructor and then using factory pattern etc in (B) or by even not having the unnecessary packaged structure, all this can be very well true in this trivial example. But in the real world application package structure together with the access modifier turns out to be very basic tool for ensuring API coherency. Because supposedly private implementation details can be accessed by third-party developers, we need to worry about changes to private implementation signatures as well as to public interfaces when making updates. This means in Java we will always be forced to decide between two of the followings

1)       Club unrelated classes in a same package to avoid exposing non-public APIs.

2)       Create logical structure properly by using multiple packages at the expense of exposing non-public APIs so they can be accessed by classes in different packages.

Another big problem which impacts Java modularity is maintaining the versions of the jars and dependencies amongst them. The main culprit for this is class path concept. For ex: - if multiple version of same jar is present in the class path, class path will not pay any attention to the version; it will just return whatever it finds first. This may lead to errors like NoSuchMethodError.

Also there is no way in Java to maintain the modules at runtime for ex: - If you have a modularized application with the DAO layer and now if we want to update the DAO layer, we have to bring down the complete application and then only it can be updated.

How OSGi can help ?

The answer lies in OSGi container. OSGi container is the set of services and contracts build using OSGi specification. Some of the popular opensource OSGi containers available are

Equinox

Knopflerfish

Apache Felix

Let’s see how OSGi container helps to overcome Java modularization hurdles effectively, by using a sample HelloWorld sort of application.

I will be using Eclipse Equinox to create this module (bundle).In OSGi, software is distributed in the form of a bundle. Bundle is a physical unit of modularity in the form of jar containing code, resources and metadata. In this sample application we will create two bundles Helloworld and DateService. To create a Bundle using Eclipse please follow these steps.

1)       File > New > Plug-in Project

2)       In the Plug-in project dialog box

Keep Project Name as follows: com.sample.helloworld

Keep target platform as “an OSGI framework” standard.

3)       Click next and for everything else just keep the default values.

As soon as wizard will be over, Eclipse will create two files Activator and manifest.mf.

com.sample.helloworld.Activator.java
package com.sample.date;

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 we want the bundle to be notified when the bundle is started or stopped by a container we need to implement BundleActivator interface.

Please make sure these guide lines are followed while implementing the same

a)       BundleActivator class must have a public constructor that takes no parameter. OSGi container may need to create the object of the BundleActivator by using Class.newInstance().

b)      Start and stop method will be called by the container during the start and stop of the bundle. Developer can use this opportunity to initialize/destroy the resource objects to be used by bundle for ex:- Database connection.

Manifest.mf

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 typical Manifest.mf will look as follows.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworld
Bundle-SymbolicName: com.sample.helloworld
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.sample.helloworld.Activator
Bundle-Vendor: SAMPLE
Import-Package: org.osgi.framework;version="1.3.0"

Let’s take a closer look on each of these headers

Bundle-ManifestVersion:  Tells 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: Defines a short, human-readable name for the bundle.

Bundle-SymbolicName: 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: Specifies the version of the bundle.

Bundle-Activator: Specifies the name of the optional listener class to be notified of bundle start and stop events. In the above example the value of this header is com.javaworld.sample.Activator.

Bundle-Vendor: Contains a human-readable description of the bundle vendor.

Import-Package: Defines imported packages for the bundle. This is the header which helps to attain dependency management. We will see more about this as we proceed further.

Let’s run our first bundle using OSGi container of eclipse. Here are the steps

1)       Click on Run > Run configuration

2)       In “Create, Manage and Run configuration window” dialog box name the configuration as something meaningful.

3)       You will notice that in the Bundles section beneath workspace com.sample.helloworld is selected by default.
OSGi-image1

4)       Make sure that you also select org.eclipse.osgi as well beneath target platform.\
OSGi-image2

5)       Now click the run button and you will see the “Hello World message “in the eclipse console view. Also note that Eclipse has opened OSGI console using which now you can start/stop/install/uninstall/update this module without restarting the container.          

OSGi-image3

Some of the common commands to interact with OSGi containers


• 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   starts a bundle.
• stop    stops a bundle.
• update  updates a bundle with a new JAR file.
• install  installs a new bundle into the OSGi container.
• uninstall  uninstalls already installed bundles from the OSGi container.

Let’s create the Dateservice bundle and we will have it associated with Helloworld such that Helloworld will use DateService to get the current system date.

DateService will have an interface to expose the contract for the client modules and an implementation. We will notice how OSGi is shielding the implementation and just exposing the interface (contract).

Create DateService bundle using the same methodology as we used for HelloWorld.

Now inside the bundle create an interface DateService as follows


package com.sample.service;

public interface DateService {

	public String getDate();

}

Create an implementation of DateService in a different package as follows


package com.sample.service.impl;

import java.util.Date;

import com.sample.service.DateService;

public class SystemDateServiceImpl implements DateService{

	@Override
	public String getDate() {

		return (new Date()).toString();
	}

}

Before we create an association between Helloworld and DateService lets understand how OSGi is going to create a well-defined layer between these two modules.

OSGi container introduces another scope known as bundle scope. The way it works is that OSGi container creates a custom class loader for each bundle and hence each bundle becomes invisible to its fellow bundle running on the same JVM in the same container. When OSGi container loads the class for the bundle it use following algorithm

1. If the class is in the java.* package, then the OSGi framework searches for the class definition at the top layer, that is, at the boot class loader. If it isn’t found, then the process fails.

2. Next, the framework checks to see if the class is in a package that’s being imported using the Import-Package header. If it is, then there must be some bundle that’s exporting this package, in which case the importer and exporter are wired together and the process stops. If no exporter is found, the process fails. If the class isn’t in a package that’s being imported using the Import-Package header, then the framework continues to the next step.

3. Next, the framework searches for the class in the bundle’s own internal class path.

5. If the class is still not found, the framework checks to see if a DynamicImport-Package header is defined and it may then attempt to dynamically load the class. If it succeeds, the class is wired to the exporter, and this wiring isn’t changed afterward.

We use Import-package and Export-package manifest.mf headers to create association between two bundles.Let’s understand with an example

After association here is how DateService manifest.mf will look like.


Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Dateservice
Bundle-SymbolicName: com.sample.service
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.sample.service.Activator
Bundle-Vendor: SAMPLE
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Export-Package: com.sample.service
Import-Package: org.osgi.framework;version="1.3.0"

HelloWorld’s manifest.mf will look as follows

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworld
Bundle-SymbolicName: com.sample.helloworld
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.sample.helloworld.Activator
Bundle-Vendor: SAMPLE
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: com.sample.service, org.osgi.framework;version="1.3.0"

Here are the benefits of this export/import model

a)       Only the exported packages of a bundle are visible to other bundles. Developer can now only expose the package containing interfaces and hence can encapsulate the implementation properly.

b)      Also we have version number in the import-package, this will ensure that package of proper version of bundle will be loaded by class loader. Hence will save our application from getting into “class-path hell”.

All this is good, but since we have exposed only the package containing an interface of the DateService then how Helloworld will be able to get the object of the DataService implementation class.

Answer is Service Oriented Architecture (SOA)

Yes, OSGi believes in service oriented architecture. Let’s see how this will be achieved in our Helloworld example.

1)       Exporting package needs to register the service(actual object) using BundleContext as follows.

We have modified the default activator of the DateService as follows.


package com.sample.service;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import com.sample.service.impl.SystemDateServiceImpl;
public class ServiceActivator implements BundleActivator {
	ServiceRegistration serviceRegistration;
	public void start(BundleContext context) throws Exception {

// Here actual object of DateService is getting created and then we will register this object to the Bundle
// Context. So that importing bundles can pick it up from the Bundle context after the service look up.

 DateService dateService = new SystemDateServiceImpl();
 serviceRegistration = context.registerService(DateService.class.getName(), dateService, null);
	}
 public void stop(BundleContext context) throws Exception {
 serviceRegistration.unregister();
 }
}

If you want to rename the Activator class to give it some meaningful name, please make sure you make the changes in manifest.mf as well.

2)       Consumer bundle will look up the service using the Bundle Context as follows.

In our example since HelloWorld is importing the DateService hence we will modify its Activator.

package com.sample.helloworld;

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

import com.sample.service.DateService;

public class Activator implements BundleActivator {

	ServiceReference serviceReference ;

	public void start(BundleContext context) throws Exception {
	System.out.println("Hello World!!");
	// We will look up the date service here and then will fetch the registered object.
         serviceReference = context.getServiceReference(DateService.class.getName());
		DateService service = context.getService(serviceReference);

System.out.println(service.getDate());

	}

	public void stop(BundleContext context) throws Exception {
		System.out.println("Goodbye World!!");
		context.ungetService(serviceReference);
	}

}

As you can see above the DateService implementation is now well encapsulated inside Dateservice bundle. HelloWorld will only get to know whatever is exposed by DateService.

We can now execute these two bundles using the OSGi console, and can start/stop/update either of these without impacting the other bundle. Also we have seen that implementation class of DateService, SystemDateServiceImpl is not at all exposed to outer world. We have also seen how well we can create versions etc of the Bundles. Hence, modularity achieved in all aspects.

Conclusion

In the above document we have learnt the basic concepts around OSGi and also created a basic HelloWorld App using Eclipse’s Equinox a well-defined OSGi container.

Spring Dynamic modules provides a very good framework to the Spring based application which can run on OSGi framework. To learn more about OSGi, I like to recommend these two books “OSGi in Actions” and “OSGi in Depth” by manning publications.

Be Sociable, Share!
Share

Leave a Comment