Τετάρτη 7 Νοεμβρίου 2012

Declarative Services in Apache Karaf

In this post I would like to demonstrate how easy it is to use Declarative Services in Karaf. The source code will be based on the Converter service and client that were used in a previous example about remote services. This time however both the service and the client will reside in the same OSGi container and instead of Activators Declarative Services will be used. The source code of this example is available in GitHub. The following projects are implemented:
  • converter (interface of a conversion service - same as in previous example)
  • converter-impl (implementation of conversion service using DS)
  • converter-client (client of conversion service using DS)
  • converter-ds-feature (maven host project for converter ds feature)


Converter Service

The converter interface is exact same as in the previous example not using Declarative Services. It simply provides the interface of the service. The service implementation however is modified. We no longer need the Activator. The ServiceImpl class is the only thing needed. In addition to the Interface implementations methods, two more methods are added:
protected void activate(ComponentContext context) {
    System.out.println("*** Activating Service");
} 

protected void deactivate(ComponentContext context) {
    System.out.println("*** Deactivating Service");
}

These methods are called for the Declarative Services runtime, when the service is activated and de-activated. In this simple scenario they do nothig but printing a message. In order for the DS runtime to work, we need to add a service component file. This file is declared in the MANIFEST.MF. In our case we modify the pom.xml to make the appropriate line appear in the manifest file:
<configuration>
    <instructions>
        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
        <Bundle-Name>${project.artifactId}</Bundle-Name>      
        <Bundle-Description>Implementation of a service that converts Celcius temperatures to Fahrenheit and vice-versa</Bundle-Description>
        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
        <Export-Package>${bundle.export.package}</Export-Package>
        <Import-Package>${bundle.import.package}</Import-Package>
        <Service-Component>OSGI-INF/component.xml</Service-Component>
    </instructions>
</configuration>

Notice that a Service-Component entry was added and that the Bundle-Activator was removed.

The last thing we need to do is to actually create the service component file in resources/OSGI-INF/component.xml:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
               name="gr.atc.aniketos.demos.converter.impl">
   <implementation class="gr.atc.aniketos.demos.converter.impl.ConverterImpl"/>
   <service>
      <provide interface="gr.atc.aniketos.demos.converter.Converter"/>
   </service>
</scr:component>

How this file works should be self-explanatory. We define which service we provide and which is the implementation class.


Converter Client

The service client follows a similar approach as the service. The Activator is removed and the pom.xml file is modified to add the service component entry. The compoment.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
               name="gr.atc.aniketos.demos.converter.client">
   <implementation class="gr.atc.aniketos.demos.converter.client.ConverterClient"/>
   <reference name="Converter" policy="static" cardinality="1..1" 
              interface="gr.atc.aniketos.demos.converter.Converter" 
              bind="setService" unbind="unsetService"/>
</scr:component>

In this file we define which service we want to look up. We search for services based on interfaces and not concrete implementations. We also define which methods to call, when the service is set and unset by the DS runtime. This methods are implemented by our class:
public class ConverterClient {

 private ConverterDialog converterDialog;

 private Converter service;

    protected void activate(ComponentContext context) {
     System.out.println("*** Activating Client");
    } 

    protected void deactivate(ComponentContext context) {
     System.out.println("*** Deactivating Client");
        
  service = null;
  if (converterDialog != null) {
         java.awt.EventQueue.invokeLater(new Runnable() {
             public void run() {
                 converterDialog.setVisible(false);
                 converterDialog.dispose();
                 converterDialog = null;
             }
         });
  }        
    }  
    
    public synchronized void setService(Converter _service) {
        System.out.println("Converter Service was set. !");
        this.service = _service;
        
     converterDialog = new ConverterDialog(new ConverterDialog.ConverterDialogListener() {
   @Override
   public double onCelciusToFahrenheit(double celcius) {
                double fahrenheit = service.toFahrenheit(celcius);
                System.out.println("Celcius " + celcius + " => Fahrenheit " + fahrenheit);
                return fahrenheit;
   }
            
   @Override
   public double onFahrenheitToCelcius(double fahrenheit) {
                double celcius = service.toCelcius(fahrenheit);
                System.out.println("Fahrenheit " + fahrenheit + " => Celcius " + celcius);
                return celcius;
   }            
  });
        
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                converterDialog.setVisible(true);
            }
        });        
    }

    public synchronized void unsetService(Converter service) {
        System.out.println("Converter Service was unset.");
        if (this.service == service) {
            this.service = null;
        }
    } 
}

Deploying in Karaf

We are now ready to deploy in Karaf. Although a feature is provided, I would like to install the bundles one by one to demonstrate a subtle point:
karaf&root> install -s mvn:org.apache.felix/org.apache.felix.scr/1.6.0
Bundle ID: 50
karaf&root> install -s mvn:gr.atc.aniketos.demos.converterservice/converter/1.0.0
Bundle ID: 51
karaf&root> install mvn:gr.atc.aniketos.demos.converterservice/converter/1.0.0
Bundle ID: 52
karaf&root> install mvn:gr.atc.aniketos.demos.converterservice/client/1.0.0
Bundle ID: 53

The org.apache.felix.scr dependency is the Felix implementation of the Service Component Runtime. If you are testing this in Equinox, you need the following dependencies:

org.eclipse.osgi.services
org.eclipse.equinox.util
org.eclipse.equinox.ds

I provide links to these bundles from the Spring Enterprise Bundle Repository. There is also a commented out section with them at the features.xml file.

Now let's start the service and the client:
karaf&root> start 52
karaf&root> start 53
*** Activating Service
Converter Service was set. !
*** Activating Client
karaf&root> stop 53
*** Deactivating Client
Converter Service was unset.
*** Deactivating Service

Notice that when we start the service nothing happens. The activate method isn't called. This method isn't a replacement of the Activator's start method. It isn't called, when the bundle is started. It is called by the DS runtime, when it is needed to. This only happens, when a client with a reference to this service is started. So, everything happens when the client is started. Notice the sequence of the print-out messages. First the service activate method is called. Then the set and the activate method of the client follow. When we stop the client, both the client and the service are de-activated. Of course the service wouldn't be de-activated, if there was a reference to it from another bundle.


Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου