31/01/2012

DOSGi DS Distributed OSGi Declarative Services example

In this post we will give a fair example of the workings of the OSGi DS Declarative Services remoted with DOSGi.

We can use either one of the following frameworks:
and the Eclipse IDE (for Java). The code can be launched either inside Eclipse or from a standalone OSGi framework.




NOTE:

Using DS with DOSGi requires the developer to use the "old school" BundleActivator else, even if programming following the DS specification, it will not be possible to attach to the desired service due to a bug currently not fixed yet, which prevents the target framework to register the proxy service on demand.


Interfaces, implementations and clients should all reside in their own package, meaning you should create a new Plug-in project, without template inside Eclipse (with the RCP Plug-in developer resources plugin installed) for each of them.


Suppose we have a service: HelloService, its interface: IHello and a client: HelloCunsomer who declares a dependency on the service by pointing to its 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 – IHello.java
package it.eng.test.dosgi.hello;

public interface IHello {

    public String sayHello(String from);
   
}

  • Implementation – HelloService.java

package it.eng.test.dosgi.helloservice;

import it.eng.test.dosgi.hello.IHello;

public class HelloService implements IHello{

    public String sayHello(String from){

        System.out.println("REMOTE CALL");

        return "Hello from "+from;

    } 
}


If you inspect it, you will see that the it offer a single, simple method which returns a String.
  • Client – HelloConsumer.java

package it.eng.test.dosgi.helloconsumer;
import org.osgi.service.component.ComponentContext;
import it.eng.test.dosgi.hello.IHello;


public class HelloConsumer { 
    private IHello hello;



    public void bindHello(IHello h) {
        hello = h;
        System.out.println("binded");
    }



    public void unbindHello(IHello h) {
        hello = null;
        System.out.println("unbinded");
    }


    public void start(ComponentContext bc) {
        System.out.println("started");       System.out.println(hello.sayHello(System.getProperty("os.name")));
    }
   
}



The consumer declares the dependency on the service by requiring its interface and has some methods of his own:

  • start: this method is called as soon as the component is started;
  • bind*: this method is called when the required *Service becomes available;
  • unbind*: this method is called when the required *Service becomes unavailable.
It also requires a BundleActivator:

package it.eng.test.dosgi.helloconsumer;

import it.eng.test.dosgi.hello.IHello;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;


public class Activator implements BundleActivator {   
    private ServiceTracker tracker;



    public void start(BundleContext context) throws Exception {
        tracker = new ServiceTracker(context, IHello.class.getName(), null);
        tracker.open();
    }


    public void stop(BundleContext context) throws Exception {
        tracker.close();
    }
}



With the activator, the bundle declares a ServiceTracker as a workaround for that bug.

The framework knows that Consumer needs that particular service 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" activate="start" name="it.eng.test.dosgi.helloconsumer">
   <implementation class="it.eng.test.dosgi.helloconsumer.HelloConsumer"/>
   <reference bind="bindHello" cardinality="1..1" interface="it.eng.test.dosgi.hello.IHello" name="IHello" policy="static" unbind="unbindHello"/>
</scr:component>


Here the bundle is saying that the class it.eng.test.dosgi.helloconsumer.HelloConsumer statically and necessarily depends on one of the possible it.eng.test.dosgi.hello.IHello interface implementations and that when one of them becomes available or unavailable inside the framework, its bindHello or unbindHello method should be called.


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


The dependency is mandatory since the cardinality was set to 1..1, meaning the component cannot be started until that particular dependency is currently available, contrarily to 0..1 which means instead that it is optional. 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.


Additionally, the client must specify an additional configuration indicating to the framework where to look for remote services; this XML must be put under the OSGI-INF/remote-service folder and must be compiled for every host to connect to:

<endpoint-descriptions xmlns="http://www.osgi.org/xmlns/rsa/v1.0.0">
  <endpoint-description>
    <property name="objectClass">
      <array>
        <value>it.eng.test.dosgi.hello.IHello</value>
      </array>
    </property>
    <property name="endpoint.id">http://localhost:9091/hello</property>
    <property name="service.imported.configs">org.apache.cxf.ws</property>
  </endpoint-description>
</endpoint-descriptions>



On its side, the service must declare its own XML configurations, listing the interface implementations offered, the classes implementing them, and a series of parameters used to make the service remotable.


<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="it.eng.test.dosgi.helloservice">
   <implementation class="it.eng.test.dosgi.helloservice.HelloService"/>
   <property name="service.exported.interfaces" value="*" />
  <property name="service.exported.configs" value="org.apache.cxf.ws" />
  <property name="org.apache.cxf.ws.address" value="http://localhost:9091/hello" />
  <service>
     <provide interface="it.eng.test.dosgi.hello.IHello"/>
  </service>
</scr:component>

  


This line indicates that all methods described in the service interface are to be made remotable:

<property name="service.exported.interfaces" value="*" />




This value is linked to the OSGi framework implementation we used (Apache CXF) and indicates that the service should be exposed as a Web Service:

  <property name="service.exported.configs" value="org.apache.cxf.ws" />

located here:

  <property name="org.apache.cxf.ws.address" value="http://localhost:9091/hello" />


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 "started" right after it is starts and then, alternatively depending on their service implementation bundle's state, the strings: "binded" and "unbinded" plus "Hello from OS\_NAME".

5 comments:

  1. What are the required bungles / plugins that is needed in the target folder, in order to launch the framework?

    ReplyDelete
    Replies
    1. Cheers,

      you should just need the bundles required by the DS services as described here: http://groglogs.blogspot.it/2012/01/osgi-ds-declarative-services-example.html plus the ones of the DOSGI framework you chose (either Camel, Aries or CXF) which are downloadable from the respective project's page.

      Delete
    2. I'm a beginner using eclipse / OSGi and Apache...
      Is there a way to install cxf as a plugin to the workspace / target platform?
      If not, how do I include the cxf plugins in my launch configuration?

      Delete
    3. Hi,

      yes you can install Apache CXF as a plug-in inside Eclipse. You may find these links useful: http://cxf.apache.org/setting-up-eclipse.html and http://angelozerr.wordpress.com/2011/08/23/jaxwscxf_step1/ (section "Install CXF").

      I however, used it stand-alone outside Eclipse; to do so, I downloaded the CXF files from http://cxf.apache.org/download.html (located under /lib) and manually installed them inside my OSGI framework.

      Delete
    4. Thanx, I will go through the links u provided.

      Delete

With great power comes great responsibility