27/01/2012

OSGi DS Declarative Services example

In this post we will give a fair example of the workings of the OSGi DS services.

I suggest you read a good OSGi tutorial by Lars Vogel before moving further if you are not much confident with the OSGi specification.

We are working with the Equinox OSGi framework, which is part of the Eclipse IDE (for Java). The code can be launched either inside Eclipse or from a standalone OSGi framework; for the latter option, grab the following jar packages from under the plugins/ folder of our Eclipse installation:
  • org.eclipse.osgi
  • org.eclipse.equinox.ds
  • org.eclipse.equinox.util
  • org.eclipse.osgi.services
and copy them in a folder of your choice.
Now open a command prompt, browse to that folder and type:

java -jar org.eclipse.osgi.jar -console

to make the OSGi prompt appear. From here you can issue a lot of commands to the OSGi framework, some of which are:


  • help
  • ss - shows the current status of the installed bundles
  • install file:path - installs the bundle available at the specified path. It will be given an unique numeric ID
  • uninstall ID
  • start ID
  • stop ID
  • refresh - forces a framework refresh and update of the bundles state
The bundles can be in only one of the following states at a time:


  • INSTALLED: the bundle has been successfully installed on the platform;
  • RESOLVED: all of the bundle's dependencies are currently available, the bundle is ready to be started or it has just been stopped;
  • STARTING: the bundle is starting, it is waiting for the method which takes care of the necessary start operations to return;
  • ACTIVE: the bundle is up and running;
  • STOPPING: the bundle is stopping, it is waiting for the method which takes care of the necessary stop operations to return.
There exists an additional state, UNINSTALLED, but it will never be shown by the framework.
In order for the DS component to work properly, you must install and start all the bundles copied before (org.eclipse.osgi excluded) inside the framework. 
 
Now we can begin our example.
 
Interfaces, implementations and clients should all reside in their own package, meaning you should create a new Plug-in project, without Activator or template inside Eclipse (with the RCP Plug-in developer resources plugin installed) for each of them.
 
Suppose we have two services: HappyService and SadService, their interfaces: IHappy and ISad and a client: Customer who declares a dependency on the services by pointing to their interfaces using the DS method.
When a component declares it provides a service, the OSGi framework stores that information in its internal registry.
Download here the example's source code. You should be able to import everything inside Eclipse as-is. Inside you will find:
  • Interface - IHappy.java
package it.eng.test.ds.happy; public interface IHappy { public void imHappy(); }
  • Interface - ISad.java
package it.eng.test.ds.sad; public interface ISad { public void imSad(); }
  • Implementation - HappyService.java
package it.eng.test.ds.happyservice; import it.eng.test.ds.happy.IHappy; public class HappyService implements IHappy{ public void imHappy(){ System.out.println("I'm happy :)"); } }
  • Implementation - SadService.java
package it.eng.test.ds.sadservice; import it.eng.test.ds.sad.ISad; public class SadService implements ISad{ public void imSad(){ System.out.println("I'm sad :("); } }

If you inspect them, you will see that the two services offer a single, simple method which prints something on standard output.
  • Client - Consumer.java
package it.eng.test.ds.consumer; import it.eng.test.ds.happy.IHappy; import it.eng.test.ds.sad.ISad; public class Consumer { private IHappy happy; private ISad sad; public synchronized void bindHappy(IHappy happy){ this.happy = happy; happy.imHappy(); } public synchronized void unbindHappy(IHappy happy){ this.happy = null; sad.imSad(); } public synchronized void bindSad(ISad sad){ this.sad = sad; sad.imSad(); } public synchronized void unbindSad(ISad sad){ this.sad = null; happy.imHappy(); } public void activate(){ System.out.println("I'm here"); } }


The consumer declares the dependency on the two services by requiring their interfaces and has some methods of his own:
  • activate: this method is called as soon as the client bundle is started;
  • bind*: this method is called immediately after the required *Service becomes available on the framework;
  • unbind*: this method is called immediately after the required *Service becomes unavailable on the framework.
The framework knows that Consumer needs those particular services since he declared it in his XML configuration which must be put under the OSGI-INF/ folder:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="it.eng.test.ds.consumer">
   <implementation class="it.eng.test.ds.consumer.Consumer"/>
   <reference bind="bindHappy" cardinality="0..1" interface="it.eng.test.ds.happy.IHappy" name="IHappy" policy="dynamic" unbind="unbindHappy"/>
   <reference bind="bindSad" cardinality="0..1" interface="it.eng.test.ds.sad.ISad" name="ISad" policy="dynamic" unbind="unbindSad"/>
</scr:component>


In there the bundle is saying that the class it.eng.test.ds.consumer.Consumer dynamically and optionally depends on one of the possible it.eng.test.ds.happy.IHappy interface implementations and that when one of them becomes available or unavailable inside the framework, its bindHappy or unbindHappy method should be called.

The dynamically policy attribute means that the bundle is able to work properly even when the services are dynamically switched; the alternative would be static, which would require the DS to deactivate the component and create a new instance each time the required service changes.

The dependency is optional since the cardinality was set to 0..1, meaning the component can be started even if that particular dependency is not currently available, contrarily to 1..1 which means instead that it is mandatory. Another alternative would be 0..N, which would require the DS to invoke the associated method multiple times, one for each service instance currently available in the registry.

Furthermore, the name of the bind* and unbind* methods can be changed as pleased as long as it reflects the ones written in the java code.
On their side, the two services must declare their own XML configurations, listing the interface implementations offered and the classes implementing them:

HappyService XML:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="it.eng.test.ds.happyservice">
   <implementation class="it.eng.test.ds.happyservice.HappyService"/>
   <service>
      <provide interface="it.eng.test.ds.happy.IHappy"/>
   </service>
</scr:component>


SadService XML:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="it.eng.test.ds.sadservice">
   <implementation class="it.eng.test.ds.sadservice.SadService"/>
   <service>
      <provide interface="it.eng.test.ds.sad.ISad"/>
   </service>
</scr:component>


When we export our projects as Deployable plug-ins and fragment and install all the resulting bundles inside the same OSGi framework and start them, we see that Consumer prints on the standard output the string "I'm here" right after it is starts and then, alternatively depending on their service implementation bundle's state, the strings: "I'm happy :)" and "I'm sad :(".

6 comments:

  1. I get the following error if I try to install the org.eclipse.osgi.services bundle:

    Missing Constraint: Import-Package: org.osgi.framework; version="1.6.0"

    Where can I get this bundle? Shouldn't this part of the OSGi-Container?

    Steffen

    ReplyDelete
    Replies
    1. Cheers,

      I get no such error using these bundles: org.eclipse.osgi_3.6.0.v20100517.jar and org.eclipse.osgi.services_3.2.100.v20100503.jar which I took directly from the /plugins folder of my Eclipse installation.

      Your error makes me think you're using different bundle versions and specifically the org.eclipse.osgi.services bundle requires the org.eclipse.osgi one to be either a lower or higher one (I don't know which versions you're using).

      NOTE: the numbers after the bundle name are NOT necessarily the bundle version as well.

      I (temporarily) uploaded the two bundles I used here: http://www.filesend.net/download.php?f=500e47bc0e86a440493b8f44b5431f9b (must wait a little before downloading) - the link will expire sooner or later.

      Hope it helps

      Delete
  2. Ok, it looks like a major change in version 3.8. in the equinox console. In version 3.8 I have to install the following bundles:

    org.eclipse.equinox.console.jar
    org.eclipse.equinox.ds.jar
    org.eclipse.equinox.util.jar
    org.eclipse.osgi.services.jar
    org.eclipse.osgi_3.8.1.jar
    org.apache.felix.gogo.command.jar
    org.apache.felix.gogo.runtime.jar
    org.apache.felix.gogo.shell.jar

    Now it works for me

    Thanks a lot
    Steffen

    ReplyDelete
  3. What is the convention for the interface and the consumer classes when a method has arguments?

    Example:

    HappyService:
    public void ImHappy(int x, float y, etc.)

    IHappy:
    public void imHappy(???);

    Consumer:
    happy.imHappy(???);


    Thank you.

    ReplyDelete
    Replies
    1. Cheers,

      basically, what OSGi does for you in this example, is similar to a Java Bean (http://en.wikipedia.org/wiki/JavaBeans) method invocation. It's not a problem if your method takes arguments. Remember that if you go remote, they have to be Serializable (and that not every remoting structure supports OUT parameters).

      In your case, you just need to change the method declaration to accept an IN parameter:

      public class HappyService implements IHappy{
      public void imHappy(String howMuch){
      System.out.println("I'm "+howMuch+" happy :)");
      }
      }

      And how you invoke it:

      public synchronized void bindHappy(IHappy happy){
      this.happy = happy;
      happy.imHappy("A LITTLE");
      }

      public synchronized void unbindSad(ISad sad){
      this.sad = null;
      happy.imHappy("VERY");
      }

      You should note that the example is composed of two parts:
      1) the "bean" injection:

      public synchronized void bindHappy(IHappy happy){
      this.happy = happy;

      2) the "bean" method invocation right after:

      happy.imHappy("A LITTLE");
      }

      In the example, those two parts are condensed in the bind action, meaning that every time the service is binded, the "bean" is injected AND the specified method is invoked immediately.
      Of course, we could have split that logic so that only the binding operations are perfomed when then binding occurs, storing the "bean" object in a class attribute as we do and then using it to invoke one of the available methods later on.

      public class Consumer {
      private IHappy happy;

      public synchronized void bindHappy(IHappy happy){
      this.happy = happy;
      }

      public synchronized void unbindHappy(IHappy happy){
      this.happy = null;
      }

      public void activate(){
      System.out.println("I'm here");
      }

      public void callHappy(String howMuch){
      if(this.happy!=null) this.happy.imHappy(howMuch);
      else System.out.println("Not yet binded!");
      }

      }

      Now you invoke the callHappy method, passing an IN parameter, from wherever you want.

      The real usefulness of the OSGi approach lies right there: being able to plug in services at runtime and seeing the system automatically connecting all the pieces for you.

      Here, for reference, you can find a short post stating the difference between OSGi services and Spring beans: http://blog.osgi.org/2007/01/spring-and-osgi-jumping-beans.html

      Hope it helps,

      Have a nice day

      Delete

With great power comes great responsibility.

Da grandi poteri derivano grandi responsabilità.