Servicios en OSGI

De ChuWiki

OSGI permite a sus bundles publicar servicios que pueden utilizar otros bundles. La publicación y el consumo de un servicio se puede hacer de tres formas:

  1. Usando desde nuestro código Java la API de OSGI que nos permite publicar y recoger servicios.
  2. Usando un fichero XML que le diga a OSGI qué servicios publicamos o consumimos.
  3. Por medio de anotaciones en las clases Java.

Veamos aquí un ejemplo sencillo de ofrecer y consumir un servicio OSGI usando la segunda forma, la del fichero XML

Si no conoces algo básico de OSGI, echa un ojo a Hola Mundo con OSGI

Ofrecer un Servicio[editar]

Con java hacemos nuestra clase que ofrece el servicio. Por aquello de hacerlo bien y seguir las buenas costumbres, haremos un paquete en el que pondremos una interface de nuestro servicio y en un paquete separado la implementación del mismo. El paquete con la interface será el paquete que exporte nuestro bundle OSGI

La interface puede ser como esta

package com.chuidiang.ejemplos.osgi_services.sumador;

/**
 * Interfaz de un servicio OSGI
 * 
 * @author Chuidiang
 * 
 */
public interface Adder {
   double add(double a, double b);
}

y la clase que implementa esta interface será la siguiente

package com.chuidiang.ejemplos.osgi_services;

import com.chuidiang.ejemplos.osgi_services.sumador.Adder;

/**
 * Implementacion de un servicio OSGI
 * 
 * @author Chuidiang
 * 
 */
public class AdderImpl implements Adder {
   @Override
   public double add(double a, double b) {
      return a + b;
   }

   public void startup() {
      System.out.println("Adder started");

   }

   public void shutdown() {
      System.out.println("Adder shutdown");
   }
}

Esta clase, como se puede ver, está en un paquete distinto que la interface. Además de implementar el método add() que hereda de la interface, tiene dos métodos startup() y shutdown() que será a los que llame OSGI cuando levante o tire abajo nuestro servicio. Los nombres de estos métodos pueden ser cualesquiera, ya le indicaremos a OSGI cuales son.

Como todos los bundles, necesitamos un fichero de manifiesto MANIFEST.MF adecuado para OSGI. El contenido de nuestro fichero de manfiesto es el siguiente

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGIAdderService
Bundle-SymbolicName: OSGIAdderService
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: com.chuidiang.ejemplos.osgi_services.sumador;version="1.0.0"
Service-Component: OSGI-INF/sumador.xml

Aparte de lo habitual, debemos fijarnos en que exporta el paquete de la interface, indicando que es la versión 1.0.0 de dicho paquete. Por supuesto, podemos poner el número de versión que queramos usando tres cifras separadas por puntos.

Hay que advertir que como no tenemos Activator, no hay ninguna línea relativa a él.

La otra línea interesante es la de Service-Component, indicando ahí un fichero XML donde vamos a definir cual es nuestro servicio. Ese fichero, como indica esta línea, estará en el directorio OSGI-INF dentro del bundle y se llamará sumador.xml. Veamos el contenido de este fichero XML

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
	activate="startup" deactivate="shutdown" name="Sumador">
   <implementation class="com.chuidiang.ejemplos.osgi_services.AdderImpl"/>
   <service>
      <provide interface="com.chuidiang.ejemplos.osgi_services.sumador.Adder"/>
   </service>
</scr:component>

Empezamos con el tag <scr:component> y ahí indicamos el nombre de nuestro componente (el servicio en este caso) y cuales son los métodos a los que debe llamar cuando se active el componente y cuando se desactive. Aquí están los métodos que habiamos añadido a nuestro AdderImpl.java, es decir, startup() y shutdown().

Luego en el tag <implementation> indicamos cual es la clase que implementa el servicio, es decir AdderImpl, con todo su paquete delante.

Finalmente, dentro del tag <service> indicamos cual es la interface que implementa el servicio, es decir, Adder.

Con esto tenemos todo hecho. Puedes ver aquí el proyecto completo de OSGIAdderService. Veamos ahora cómo consumirlo.


Consumir el servicio[editar]

Podríamos hacer un bundle normal con Activator y conseguir el servicio a través del BundleContext que recibiríamos en el método start() del activator, pero como queremos hacerlo con el fichero XML, hacemos una clase que consuma el servicio, de esta manera

package com.chuidiang.ejemplos.osgi_services;

import com.chuidiang.ejemplos.osgi_services.sumador.Adder;

/**
 * Ejemplo de clase que consume un servicio OSGI.
 * 
 * @author Chuidiang
 * 
 */
public class AdderConsumer {

   private Adder sumador = null;

   public void setSumador(Adder sumador) {
      System.out.println("Adder received");
      this.sumador = sumador;
   }

   public void unsetSumador(Adder sumador) {
      this.sumador = null;
   }

   public void start() {
      System.out.println("AdderConsumer started");
      new Thread(new Runnable() {

         @Override
         public void run() {
            while (true) {
               if (null != sumador) {
                  System.out.println(sumador.add(Math.random(), Math.random()));
               } else {
                  System.out.println("sumador es null");
               }
               try {
                  Thread.sleep(1000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }

         }
      }).start();

   }

   /*
    * (non-Javadoc)
    * 
    * @see
    * org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
    */
   public void stop() throws Exception {
      System.out.println("Goodbye World!!");
   }

}

Hemos puesto métodos set y unset sumador para que OSGI nos pase el servicio cuando esté disponible, o para que nos lo quite si el servicio se detiene. A estos métodos los llamara OSGI automáticamente y podemos ponerles cualquier nombre, ya le indicaremos a OSGI en el fichero XML correspondiente cómo se llaman.

También hemos puesto dos métodos start() y stop(), a los que llamará OSGI cuando nuestro bundle consumidor se levante o se tire abajo. En el método start() lanzamos un hilo que llama al servicio sumador de forma indefinida, con una espera de un segundo entre llamada y llamada. Comprobamos si el sumador lo tenemos (es null o no) antes de usarlo, puesto que si el sumador se detiene, OSGI nos avisará.

El fichero de manifiesto MANIFEST.MF de nuestro bundle puede ser como el siguiente

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGIAdderConsumer
Bundle-SymbolicName: OSGIAdderConsumer
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Import-Package: org.osgi.framework;version="1.3.0",
 com.chuidiang.ejemplos.osgi_services.sumador;version="1.0.0"
Service-Component: OSGI-INF/component.xml

Nuevamente, advertir los siguiente puntos:

Importamos el paquete que exporta el servicio Adder.

No tenemos Activator, por lo que no hay nada relativo a Activator en el fichero de manifiesto.

Indicamos con Service-Component el fichero XML donde indicamos qué componentes tenemos y cómo arrancarlos. El fichero se llama en este caso component.xml y está dentro del directorio OSGI-INF. Veamos el contenido de ese fichero

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="UsaSumador">
   <implementation class="com.chuidiang.ejemplos.osgi_services.AdderConsumer"/>
   <reference bind="setSumador" cardinality="1..1" interface="com.chuidiang.ejemplos.osgi_services.sumador.Adder" policy="static"/>
</scr:component>

Es similar al del servicio. Indicamos los métodos start() y stop() para arrancar y parar nuestro bundle. Y en vez de indicar que ofrecemos un servicio, indicamos que queremos consumirlo por medio de <reference>. En este tag indicamos :

  • el método al que tiene que llamar para pasarle el servicio ( bind="setSumador" ).
  • Podemos poner un unbind="unsetSumador" para indicarle el método al que llamar para quitarlo.
  • cardinality indica cuantos sumadores queremos/podemos recibir. 1..1 indica uno y solo uno. Podemos poner 0..1, 0..n, 1..1 o 1..n. Por supuesto, si admitimos n, en setSumador deberíamos implementar una lista en la que podamos ir guardando todos los que recibamos.
  • policy puede ser static o dynamic. static es el fácil, de alguna forma nos garantiza que nuestro bundle se activará cuando el servicio ya esté disponible y se desactivará si deja de estar disponible. Nuestro código podría confiar ciegamente en que tiene la variable sumador adecuadamente rellena en cada momento. Si pusieramos dynamic, entonces esto no es cierto. Nuestro bundle puede estar activo independiemente de que el servicio lo esté o no. Nuestro código debe comprobar siempre que tiene sumador adecuadamente relleno.

Listo, el código que consume el servicio está listo. Aquí puedes ver el proyecto completo OSGIAdderConsumer


Dependencias[editar]

Para poder arrancar estos bundles, necesitamos que el framework nos proporcione la siguientes dependencias

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

La salida de estos bundles trabajando conjuntos es la siguiente

Adder started
Adder received
AdderConsumer started
1.020721185572897
1.5964304067809065
0.2978075061886274
1.0833296746335963
...