Hello to OSGi World

      In this entry i will introduce you to a technology called OSGi which is used to create Java modular applications. I will split this post in two parts, a motivation followed by a hands on tutorial(a video of the resulting tutorial is availalble at [6] ) in which we will build a modular Hello World that visits the three layers of the OSGi[8] framework(module, lifecycle and service).

So why should i adopt OSGi?

      I’ve been working with Java for about five years and one of the things that is really hard to achieve in a normal java environment is component reutilization because its hard to create loosely coupled libraries with the standard Java environment due to its limited classpath system, and “componentize”  an application with OSGi is trivial, it also enables dynamic module plugin(hotplugability) where you can upgrade your system while its running, package and module versioning which make possible to have multiple versions of the same module running at the same time, the bundles collaboration is very simple and powerful via service registry. All these are good features of OSGi but in my opinion nothing compares to its classpath system i will not get in details(you can refer to [1] and [2]) but to summarize it extends the standard Java flat classpath hell[3] by improving classpath isolation, it takes seriously and efficiently the divide and conquer ideology which in turn lead to non spaghetti applications, “manutenability”, extensibility, easy securing and so on. To sum it up, big systems are hard to maintain and understand because of the relationships between components and OSGi reduces this complexity.

Who is using OSGi?

     The framework is out since 2000 but its adoption is increasing a lot lately and its platform is becoming the de facto standard for modularized Java applications. For example its being used by most JavaEE application servers (JBossAS, Glassfish, JOnAS, WebSphere), Eclipse IDE etc…(see [4] for a complete list).

Talk is cheap show me the code

     So lets dive in some code, we will build a modular hello world where each language will be an OSGi bundle that will say hello to the world in its idiom, the complete source code is available at [7].

Pre requisites:

Eclipse IDE

Bnd tools eclipse plugin, for a getting started with osgi and bnd tools i recommend[5]

First thing to do is to create our main bundle, in eclipse create a plugin-project named hello-osgi as shown in image1. image1

image1

click next and in the templates wizard uncheck the option “create plugin using one of the templates” click finish, the project created should look like image2 below: image2

image2

create an interface named HelloService which will be the api for other hello world implementations.


package helloosgi.api;
import java.io.Serializable;
public interface HelloService extends Serializable{

/**
 * the hello greeting in the current language
 */
 public String sayHello();

 /**
 * 
 * @return the language name
 */
 public String getLanguage();
}

         Now we will export this package so other bundles(the hello world implementations) can implement it, to do so visit the file MANIFEST.MF located at META-INF folder, go to runtime tab and in the exported packages panel choose helloosgi.api package to export, it will be only package that will be available to other bundles, see image3 below.

.image3 image03

        Another responsibility of hello-osgi bundle beyond export the hello world api will be manage a console(as homework you can create a bundle to manage the console) where user will interact with our application such as list available languages and/or chose a language to prompt hello world, the HelloManager is as follows:

package helloosgi.impl;
import helloosgi.api.HelloService;
import java.io.Serializable;
import java.util.Scanner;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
public class HelloManager extends Thread implements Serializable{
private static final long serialVersionUID = 1L;

 private volatile boolean active = true;

 private BundleContext context;

 private Scanner scanner;

 public HelloManager(BundleContext context) {
   this.context = context;
 }

 /**
 * manager will run in a separated thread waiting for user input
 */
 @Override
 public void run() {
    scanner = new Scanner(System.in);
    while(active){
      String input = scanner.nextLine();
      if(input.equals("l")){
        listAvailableLanguages();
      }
      else if(input.equals("h")){
        help();
      }
     else{
        try{
           int opt = Integer.parseInt(input);
           System.out.println(this.getHello(opt-1)); 
          }catch (NumberFormatException nfe) {

        }
     }
    }
 }

 private String getHello(int opt) {
    ServiceReference<HelloService>[] serviceReferences;
    try {
       serviceReferences = (ServiceReference<HelloService>[]) context.getServiceReferences(HelloService.class.getName(), null);
     if(serviceReferences != null && opt >=0 && serviceReferences.length > opt){
       return ((HelloService)context.getService(serviceReferences[opt])).sayHello();
      }
     else{ 
       System.out.println("No languages found with given option.");
     }
     } catch (InvalidSyntaxException e) {
        e.printStackTrace();
      }
     return "";
   }
public void help(){
     System.out.println("Hello Manager started, type 'l' to list available languages.\n Type 'h' for help. \nType the number of the language to say hello.\n");
    }

 @SuppressWarnings("unchecked")
 public void listAvailableLanguages(){
      ServiceReference<HelloService>[] serviceReferences;
      try {
        System.out.println("Listing Available languages:\n");
        serviceReferences = (ServiceReference<HelloService>[]) context.getServiceReferences(HelloService.class.getName(   ), null);
       if(serviceReferences != null){
          StringBuilder sb = new StringBuilder();
          for (int i = 0;i<serviceReferences.length;i++) {
              sb.append(i+1).append(" - ").append(((HelloService)context.getService(serviceReferences[i])).getLanguage()).append("\n");
          }
       System.out.println(sb.toString());
     }
       else{ 
          System.out.println("No languages found.");
      }
    } catch (InvalidSyntaxException e) {

         e.printStackTrace();
     }
 }

 public void stopThread(){
   active = false;
   scanner.close();
 }
}

HelloManager uses two layers of OSGi(ok it also uses the module layer via MANISFEST) which is Lifecycle and Service, here is some important things to note:

  •  HelloManager runs in a separated thread that will be started in the Activator( a class that is managed by OSGi – the lifecycle layer)  start method  until the bundle is stopped by stop method in activator.
  • HelloManager access HelloService implementations via OSGi Service layer by invoking context.getServiceReferences(HelloService.class.getSimpleName(),null) which asks for objects that implement HelloService interface and are registered in OSGi service registry, the second parameter is for filtering so you can ask for specific HelloService implementations

Above is the Activator class which is the bridge to the lifecycle layer:

package helloosgi;

import helloosgi.impl.HelloManager;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

 private static BundleContext context;

 private HelloManager helloManager;

 static BundleContext getContext() {
    return context;
 }

  
 public void start(BundleContext bundleContext) throws Exception {
   Activator.context = bundleContext;
   helloManager = new HelloManager(bundleContext);
   helloManager.start();
   helloManager.help();
   helloManager.listAvailableLanguages();
 }

/*
 * (non-Javadoc)
 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
 */
 public void stop(BundleContext bundleContext) throws Exception {
   Activator.context = null;
   helloManager.stopThread();
   helloManager.join();
   System.out.println("Hello Manager stopped.");
 }
}

here is the hello-osgi bundle resulting MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello-osgi
Bundle-SymbolicName: hello-osgi
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: helloosgi.Activator
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: helloosgi.api

now create another plugin project named hello-osgi-brazil applying the same steps we did in the hello-osgi bundle. The first thing we will do is to import the hello world api exported by hello-osgi bundle, you can do this in dependencies tab in MANIFEST, see image04:

image4 image04

After that create the HelloService implementation as follows:

package helloosgibrazil.impl;

import helloosgi.api.HelloService;

public class BrazilHelloService implements HelloService{

	private static final long serialVersionUID = 1L;

	@Override
	public String sayHello() {

		return "Olá Mundo!";
	}

	@Override
	public String getLanguage() {
		return "Brasileiro";
	}

}

Now last thing to do is to register our HelloService implementation through OSGi service registry when the bundle starts, we can do this in start method of our Activator:

package helloosgibrazil;

import helloosgi.api.HelloService;
import helloosgibrazil.impl.BrazilHelloService;

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;
		context.registerService(HelloService.class.getName(), new BrazilHelloService(), null);
		System.out.println("Brazil Hello service started");
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext bundleContext) throws Exception {
		Activator.context = null;
	}

}

here is the hello-osgi-brazil bundle resulting MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello-osgi-brazil
Bundle-SymbolicName: hello-osgi-brazil
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: helloosgibrazil.Activator
Import-Package: helloosgi.api,
 org.osgi.framework;version="1.3.0"

Running our Bundles

1 – Running from Eclpse: Running OSGi from IDE is quite straightforward, right click in hello-osgi project and choose Run as -> run configuration and choose osgi-framework menu, check hello-osi and hello-osgi-brazil bundles and click run  as shown in image5:

image5image5

after that both bundles will start and you can interact with the application via eclipse console as shown in image 6

image6

image6

The problem when running using the IDE is that i cant interact with OSGi framework(at least i dont know) to start/stop bundles, to do that i will run our bundles using a terminal.

2 – Running from terminal(you can also refer to [6]):

     First thing to do is to create a folder named ‘hello-osgi’  and copy org.eclipse.osgi_3.7.2.v20120110-1415.jar which is under eclipse_home/plugins, the version depends on ecplise instalation, v3.7.2 comes with eclipse indigo.

    now we need to create our physical bundles(jar files), to do that right click in the project and choose export and choose deployable plug-ins and fragments click next and choose the bundles you want to export(hello-osgi and hello-osgi-brazil) and browse the folder you created in last step as in image7.

image7

image7

After that open a terminal, cd into hello-osgi folder and start osgi with java -jar osgi.jar -console 9999, it will start osgi framework which will be listening to command at port 9999 see image8:

image8

  image8

open a new tab and connect to osgi via telnet(in windows you can use putty) as in image9, you are now able to send commands to osgi.

image9 image9

yet in osgi tab install hello-osgi bundle with command: install file:/home/rmpestano/hello-osgi/plugins/hello-osgi.jar and start it with start command as in image 10:

image10

image10

now in the first tab you can see hello-service has started, keep in osgi tab and install hello-osgi-brazil bundle with install file:/home/rmpestano/hello-osgi/plugins/hello-osgi.jar and now you are able to list languages and print hello world in the first tab as in image11:

image11

image11

and thats it you have a modular java application  running through a OSGi container(equinox in this case).

I hope you could learn from this mini tutorial, any doubt and/or suggestion is always welcome.

[1] http://www.manning.com/hall

[2] http://www.aqute.biz/Blog/2007-02-19

[3] http://stackoverflow.com/questions/373193/what-is-classpath-hell-and-is-was-it-really-a-problem-for-java

[4] http://en.wikipedia.org/wiki/OSGi#Projects_using_OSGi

[5] http://www.vogella.com/articles/OSGi/article.html

[6] http://www.youtube.com/watch?v=mwSFjM6ntbQ

[7] https://github.com/rmpestano/hello-osgi

[8] http://www.osgi.org