Bundle Manifest Headers, Cabeceras OSGi

Una parte fundamental dentro de la especificación OSGi es el conjunto de metadatos que deben ser situados en el fichero MANIFEST.MF y que permitirán a la implementanción OSGi instalar el bundle dentro del entorno de ejecución, resolver sus dependencias y publicar todo aquello que queramos sea visto por otros bundles.

Me voy a basar en el documento que define la especificación OSGi 4.1 para listar todas las cabeceras disponibles actualmente, su finalidad y sus características.

La lista de cabeceras, por orden alfabético, es la siguiente:

Descripción de cabeceras


Bundle-ActivationPolicy
Especifica como el framework debe activar el bundle. Si debe hacerlo de forma normal cuando arranca el framework o de forma perezosa, no se activará hasta que no sea cargada una clase.
Valores permitidos : lazy
Valor por defecto : ninguno. Si no se incluye esta cabecera se activará el bundle al arranque del framework.
Ejemplo

Bundle-ActivationPolicy : lazy


Bundle-Activator
Nombre de la clase usada para arrancar y parar el bundle. La clase debe implementar la interfaz BundleActivator.
Ejemplo

Bundle-Activator : es.jmac.osgi.infrastructure.Activator


Bundle-Category
Lista de nombres de categoría, separadas por comas.
Valores permitidos : libre, valores separados por comas. OSGi recomienda utilizar los valores de una lista predefinida.
Valor por defecto : ninguno. Si no se incluye esta cabecera se activará el bundleal arranque del framework.
Ejemplo

Bundle-Category : example, device, testing


Bundle-Classpath
Define un lista, separada por comas, de rutas de ficheros JAR y directorios (dentro del bundle) con las clases y recursos del bundle. El “.” especifica el directorio raiz del JAR del bundle.
Valor por defecto : .
Ejemplo

Bundle-Classpath : lib/commons-logging-1.1.1.jar, . , resources/html


Bundle-ContactAddress
Dirección de contacto del proveedor.


Bundle-Copyright
Especificación de copyright y licencia para el bundle.


Bundle-Description
Descripción corta del bundle.


Bundle-DocURL
URL con la documentación del bundle.


Bundle-Localization
Esta cabecera debe contener el fichero base dentro del bundleque defines las información multi-idioma.
Valor por defecto : OSGI-INF/l10n/bundle
Esta ruta la resuelve el framework como OSGI-INF/l10n/bundle.properties, por tanto cada fichero de idioma debe tener el siguiente nombre : bundle_es.properties, bundle_de.propertie
Ejemplo

Bundle-Localization : resources/labels


Bundle-ManifestVersion
Define que versión de la especificación OSGi debe seguir el bundle.
La versión 1 del Manifest es para la release 3.
La versión 2 es para la release 4, la actual.
Valor por defecto : 1
Ejemplo

Bundle-ManifestVersion : 2


Bundle-Name
Nombre corto para el bundle. Puede contener espacios.
Ejemplo

Bundle-Name : Pruebas OSGi


Bundle-NativeCode
Esta cabecera contiene una especificación de las librerías de código nativo contenidas en el bundle.
Ejemplo

Bundle-NativeCode : /lib/http.DLL; osname = QNX; osversion = 3.1


Bundle-RequiredExecutionEnvironment
Contiene una lista, separada por comas, de los entornos de ejecución que deben estar presentes en la Plataforma de Servicio.
Ejemplo

Bundle-RequiredExecutionEnvironment : CDC-1.0/Foundation-1.0


Bundle-SymbolicName
Especifica un nombre único, dentro de la VM, para el bundle. El nombre debería seguir la convención de dominio inverso. Esta cabecera es obligatoria.
Ejemplo

Bundle-SymbolicName : es.jmac.osgi.infrastructure


Bundle-UpdateLocation
Especifica la URL a partir de la cual se recuperarán las actualizaciones para el bundle. Si el bundle es actualizado, esta ruta debería ser usada si está presente, para recuperar el fichero JAR actualizado.
Ejemplo

Bundle-UpdateLocation : https://justoaguilar.com/repo/osgi


Bundle-Vendor
Descripción del proveedor del bundle.
Ejemplo

Bundle-Vendor : Justo Aguilar


Bundle-Version
Especifica la versión del bundle. La versión debe seguir obligatoriamente el formato :

version ::=
major( '.' minor ( '.' micro ( '.' qualifier )? )? )?
major ::= number // See 1.3.2
minor ::= number
micro ::= number
qualifier ::= ( alphanum | ’_’ | '-' )+

Valor por defecto : 0.0.0
Ejemplo

Bundle-Version : 1.0.1.Built20090801-143221


DynamicImport-Package
Esta cabecera contiene una lista, separada por comas, de nombres de paquetes que deberían ser dinámicamente importados cuando se necesiten.
Ejemplo

DynamicImport-Package : es.jmac.osgi.*


Export-Package
Contiene la declaración de los paquetes a exportar.
Ejemplo

Export-Package : !es.jmac.osgi.infrastructure, es.jmac.osgi


Export-Service
Obsoleto. (Deprecated)


Fragment-Host
Define el bundle maestro para este fragmento.
Ejemplo

Fragment-Host : es.jmac.osgi.infrastructure;version="[1.0.0,2.0.0)"


Import-Package
Especifica los paquetes a importar por este bundle.
Ejemplo

Export-Package : !es.jmac.osgi.infrastructure, es.jmac.osgi


Import-Service
Obsoleto. (Deprecated)


Require-Bundle
Especifica la lista de bundles (paquetes) obligatorios a utilizar en este bundle.
Ejemplo

Require-Bundle : org.apache.commons.logging;version="1.1.1",org.springframework.context;version="(2.0.0,3.0.0)"

¿Qué es OSGi … y para que sirve?

Pues sí, otro acrónimo más en el mundo Java, no sé por qué te extrañas. Y como ha pasado con muchos otros de su época, véase SOAP, el acrónimo ha pasado a ser el nombre oficial, nombre que en sus orígenes era Open Services Gateway initiative. Bueno, vamos al turrón.

OSGi es la respuesta en la plataforma Java a la programación modular.
¿Qué se entiende por módulo?

Un módulo es un componente autocontenido dentro de un sistema mayor. ¿Autocontenido?, sí, que no necesita de referencias externas para su existencia y funcionamiento.

Bien, detrás de esta definición de módulo tan abstracta se esconden dos de los principios fundamentales de la programación : Alta Cohesión y Bajo Acoplamiento.

Todo módulo debe tener una responsabilidad y funcionalidad perfectamente delimitada dentro de un sistema mayor en el que se encuentre integrado (alta cohesión). Y debe evitar cualquier dependencia con el resto de módulos para realizar la tarea que tiene asignada (bajo acoplamiento). Evidentemente, siempre existirá un determinado grado de acoplamiento, que debe estar, en la medida de lo posible, articulado mediante conceptos abstractos, lo que en Java vienen a ser las interfaces.

¿Cómo gestiona Java actualmente la modularización?

Se podría decir que dentro de la plataforma Java la analogía de módulo sería un fichero JAR. Sin embargo, si conocemos el proceso mediante el cual es cargado un fichero jar en la JVM, podemos advertir una serie de carencias que invitan a pensar que la modularización en Java es una mera ilusión.

Actualmente, por defecto, una JVM tiene un único class loader (system class loader que es cargado por el bootstrap class loader escrito en código nativo) responsable de cargar en la JVM cualquier clase que se encuentre dentro del classpath de ejecución de una aplicación Java. Cuando existe un fichero jar en el classpath, se carga cada una de las clases que contenga ese JAR. Todo se carga en un espacio común, ahí, al montón.

Seguramente alguna vez habréis cargado el mismo jar dos o más veces, si es de la misma versión no pasa nada pero si usas distintas versiones puede que tengas un problema. Si tienes dependencias con diferentes versiones de un mismo JAR, que sepas que se cargará la clase del jar que aparezca primero en el classpath. Esto implica que no podamos usar diferentes versiones de una misma clase, al menos, con la aproximación que Java tiene por defecto, tendríamos que crear nuestro propio classloader para cargar una versión distinta de la misma clase.

[Extender este punto]

Propuesta OSGi

La Alianza OSGi empezó a definir allá por el año 1999 (diez añitos ya) una especificación que permitiera la construcción de aplicaciones modulares en Java.



La especificación OSGi propone una arquitectura montada sobre una JVM que permita la instalación, actualización y eliminación de módulos. Cada módulo podrá publicar las clases que serán visibles por otros módulos de la máquina virtual y podrá consumir cualquier clase que haya sido publicada por otros módulos de la VM. Entre los módulos cliente y proveedor existe un registro central, en el que los proveedores registran los servicios que desean hacer públicos al resto de módulos y los clientes solicitan los servicios que desean consumir. Evidentemente un módulo puede ser, a la vez, cliente y proveedor. Este paradigma es similar a la solución definida en SOA, por ello se dice que:

OSGi = SOA en una JVM

Dentro de la especificación OSGi, un módulo tiene el nombre de bundle. Un bundle OSGi es un fichero JAR con un conjunto de metadatos que especifican las características de bundle. Estos metadatos deben de incluidos dentro del fichero MANIFEST.MF, por tanto :

Bundle OSGi = JAR + MANIFEST.MF con METADATOS OSGi

Un ejemplo de MANIFEST.MF adaptado para ser utilizado dentro de un entorno OSGi puede ser el siguiente :

Manifest-Version: 1.0
Export-Package: org.apache.commons.logging;version="1.1.1",org.apache.
 commons.logging.impl;version="1.1.1";uses:="javax.servlet,org.apache.
 avalon.framework.logger,org.apache.commons.logging,org.apache.log,org
 .apache.log4j"
Implementation-Title: Jakarta Commons Logging
Implementation-Version: 1.1.1
Bundle-Classpath: .
Built-By: dlg01
Specification-Vendor: Apache Software Foundation
Bundle-Name: Apache Commons Logging
Created-By: Apache Maven
X-Compile-Source-JDK: 1.2
Implementation-Vendor: Apache Software Foundation
Bundle-Vendor: SpringSource
Implementation-Vendor-Id: org.apache
Build-Jdk: 1.4.2_16
Bundle-Version: 1.1.1
Specification-Title: Jakarta Commons Logging
Bundle-ManifestVersion: 2
Import-Package: javax.servlet;version="[2.1.0, 3.0.0)";resolution:=opt
 ional,org.apache.avalon.framework.logger;version="[4.1.3, 4.1.3]";res
 olution:=optional,org.apache.log;version="[1.0.1, 1.0.1]";resolution:
 =optional,org.apache.log4j;version="[1.2.15, 2.0.0)";resolution:=opti
 onal
Bundle-SymbolicName: com.springsource.org.apache.commons.logging
Specification-Version: 1.0
Extension-Name: org.apache.commons.logging
Archiver-Version: Plexus Archiver
X-Compile-Target-JDK: 1.2

El código anterior correponde al fichero MANIFEST.MF contenido en el JAR de Apache Commons Logging 1.1.1 descargado del repositorio OSGi de SpringSource.

De todas las cabeceras (headers) que componen el fichero vamos a explicar las más significativas:

  • Bundle-SymbolicName : Única cabecera obligatoria y que debe identificar unívocamente dentro de la VM al bundle.
  • Bundle-Version: Versión del bundle. La dupla (Bundle-SymbolycName,BundleVersion) permite referenciar una versión de un bundle.
  • Export-Package : Lista de paquetes que el bundle publica para que puedan ser consumidos por otros bundles dentro de la misma VM.
  • Import-Package : Lista de paquetes que el bundle necesita consumir y que por tanto deben ser exportados por otros bundles.
  • Bundle-Classpath : Lista de rutas donde se encuentran las clases del bundle

Como habréis notado, todas las referencias a paquetes van acompañadas de un rango de versiones, no es obligatorio pero si recomendable. Por ejemplo, este bundle consume, de manera opcional, el paquete javax.servlet desde la versión 2.1.0 en adelante y menor a la 3.0.0. Esto permite a un bundle importar paquetes de varias versiones de un bundle, lo que puede ser necesario en ocasiones cuando tengamos dependencias con sistemas antiguos y que tengan dependencias con versiones antiguas mientras que la parte nueva del desarrollo esté vinculada a las versiones más modernas.

Otra ventaja es la posibilidad publicar únicamente lo que quiero que sea público a otros bundles sin necesidad de utilizar modificadores de ámbito a nivel de código como private, protected o package.

Por último os dejo con la definición que Craig Walls hace en su libro, Modular Java.

OSGi is a component framework specification that brings modularity to
the Java platform. OSGi enables the creation of highly cohesive, loosely
coupled modules that can be composed into larger applications. What’s
more, each module can be individually developed, tested, deployed,
updated, and managed with minimal or no impact to the other modules.

Craig Walls,
Modular Java

Próximamente más.

Configuración Equinox, OSGi, Spring DM y Maven

Actualmente estoy trabajando en un proyecto en el que OSGi va a jugar un papel determinante. En próximos posts hablaré sobre OSGi, y sobre que papel juega dentro de las aplicaciones Java.

Cuando se habla de OSGi, se habla de programación modular donde un módulo es un fichero JAR (Java ARchive). En OSGi un módulo tiene el nombre de bundle.

Para poder entender este post se requieren conocimientos de :

  • Maven 2
  • Spring Framework
  • Eclipse, Equinox
  • OSGi
  • Spring Dynamic Modules (Spring DM)

La prueba de concepto que vamos a describir a continuación, paso a paso, es la siguiente:

  • Crear un bundle e instalarlo en Equinox.
  • Asociar al bundle una clase BundleActivator para comprobar que el bundle ha sido inicializado correctamente.
  • Instalar el bundle Extender de Spring DM y sus dependencias dentro de Equinox.
  • Configurar el bundle para que arranque un ApplicationContext de Spring.

La idea de este post es mostrar de manera rápida el proceso de configuración, por ello no voy a explicar detalladamente el conjunto de herramientas y plugins utilizados a lo largo del proceso. Esto quedará pendiente para próximos posts.

1. Creación de un bundle OSGi

Creamos un nuevo proyecto Java dentro de nuestro espacio de trabajo de Eclipse. En mi caso, es.jmac.springdm

Mavenizamos ese proyecto, yo he usado el plugin de Sonatype M2Eclipse, pero vamos puede hacerse a mano, basta crear un fichero pom.xml.

La información del fichero Maven de configuración del proyecto será la siguiente :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>es.jmac</groupId>
  <artifactId>springdm</artifactId>
  <packaging>jar</packaging>
  <name>springdm</name>
  <version>1.0.0-SNAPSHOT</version>
  <description>Prueba de concepto de Spring Dynamic Modules sobre Equinox</description>

</project>

Creamos una nueva source folder, src/main/java.

Al final del proceso, nuestro proyecto en Eclipse debería tener una estructura similar a la siguiente:

Estructura inicial del proyecto

Estructura inicial del proyecto

Una vez que tenemos nuestro proyecto Java mavenizado vamos a incorporarle la información necesaria que lo identifique como un bundle OSGi.

Al inicio del post se ha comentado que un bundle OSGi es un fichero JAR. Sin embargo no basta con eso. Ese fichero JAR debe contener un información concreta que permita a la plataforma OSGi identificar ese fichero como un bundle OSGi. Esta información viene especificada en el fichero MANIFEST.MF. Esa será nuestra siguiente tarea generar ese fichero con la información necesaria para ser identificado como un bundle OSGi.

Bundle OSGi = JAR + MANIFEST.MF con CABECERAS OSGi

Bueno, ahora hay que generar el MANIFEST.MF. Hay varias formas de hacerlo, se puede hacer mano, si se conocen el conjunto de cabeceras OSGi o bien a través de alguna herramienta. En mi caso he utilizado un plugin para Maven que permite realizar varias operaciones sobre bundles OSGI, maven-bundle-plugin. Como he comentado anteriormente, no voy a explicar como funciona el plugin, os indico que hay que poner en el pom.xml y la orden que hay que lanzar.

Actualizar el pom.xml con el siguiente código :

...
<packaging>bundle</packaging>
...
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-bundle-plugin</artifactId>
      <version>2.0.0</version>
      <extensions>true</extensions>
      <configuration>
        <instructions>
	  <Bundle-SymbolicName>${groupId}.${artifactId}</Bundle-SymbolicName>
	  <Bundle-Version>${pom.version}</Bundle-Version>
	</instructions>
      </configuration>
    </plugin>
  </plugins>
</build>
...

ATENCIÓN : No olvidéis cambiar el valor de elemento packaging a bundle.

Ejecutad por consola o a través de Eclipse, el siguiente comando Maven :


mvn clean package -Declipse.pde

Esta orden generará un fichero JAR con nuestro proyecto, que por ahora no tiene nada, y que contendrá un fichero MANIFEST.MF con las cabeceras necesarias para ser reconocido como un bundle OSGi. Lo que tenéis que hacer es iros a la carpeta target donde se ha copiado el fichero JAR y sacar de él fichero MANIFEST.MF para copiarlo a vuestro proyecto en Eclipse, ojo, dentro de la carpeta META-INF. Ya sé que no es muy sutil, pero es rápido.

ACTUALIZACIÓN
Una forma más automática de generar el fichero MANIFEST.MF sería utilizando el goal manifest del anterior plugin. Tendríamos que cambiar nuestra definición anterior del plugin por la siguiente:

...
<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.0.0</version>
  <extensions>true</extensions>
  <configuration>
    <manifestLocation>META-INF</manifestLocation>
    <instructions>
      <Bundle-SymbolicName>${groupId}.${artifactId}</Bundle-SymbolicName>
      <Bundle-Version>${pom.version}</Bundle-Version>
    </instructions>
  </configuration>
  <executions>
    <execution>
      <id>genera-manifest</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>manifest</goal>
      </goals>
    </execution>
    <execution>
      <id>genera-bundle</id>
      <phase>package</phase>
      <goals>
        <goal>bundle</goal>
      </goals>
    </execution>
  </executions>
</plugin>
...

El plugin anterior tiene dos ejecuciones en dos fases distintas del ciclo de vida de Maven. En la primera fase, generate-resources, se ejecuta el goal manifest. En la segunda fase, package, se ejecuta el goal definido por defecto en el plugin, bundle.

No olvidar añadir el elemento :

<manifestLocation />

Vuestro fichero MANIFEST.MF debería contener algo parecido a esto:

Manifest-Version: 1.0
Built-By: justo
Created-By: Apache Maven Bundle Plugin
Import-Package: es.jmac.springdm,org.osgi.framework;version="1.3"
Export-Package: es.jmac.springdm;uses:="org.osgi.framework"
Bundle-Version: 1.0.0.SNAPSHOT
Bundle-Name: springdm
Bundle-Description: Prueba de concepto de Spring Dynamic Modules sobre
 Equinox
Build-Jdk: 1.5.0_19
Private-Package: es.jmac.springdm,
Bundle-ManifestVersion: 2
Bundle-SymbolicName: es.jmac.springdm
Tool: Bnd-0.0.311
2. Ejecución del bundle dentro de Equinox

Ya hemos creado un bundle OSGi, ahora vamos a ver si lo hemos creado correctamente. Para ello, vamos a lanzar una instancia de Equinox y especificaremos que dentro de esa instancia se cargue nuestro bundle recien creado.

Nos vamos al menún Run –> Run Configurations… de Eclipse

Run Configurations...

Run Configurations...

Selecciónamos OSGi Framework y pulsando el botón derecho seleccionamos New…

New OSGi Framework

New OSGi Framework

En la parte derecha de la ventana aparecerán la lista de bundles que tenemos actualmente, dividos en dos grupos, los bundles creados en nuestro workspace y los bundles instalados en la Target Platform, que suele ser $ECLIPSE_HOME/plugins.

Lista de Bundles

Lista de Bundles

Los siguientes pasos serán seleccionar nuestro bundle, deseleccionar todos los bundles de la sección Target Platform y pichar sobre el botón Add Required Bundles, es nos marcará un único bundle el core de OSGi, org.eclipse.osgi. Pinchamos en Apply para salvar los cambios y pichamos en Run.

A continuación se debe abrir la ventana de Console en la que aparecerá el shell de OSGi, algo como :
osgi>
Para comprobar si nuestro bundle ha sido inicializado correctamente, introducimos el comando ss y pulsamos intro. Debería aparecer lo siguiente, si no, algo hemos mal :

Short Status (ss)

Short Status (ss)

Acabamos de ejecutar nuestro bundle dentro de Equinox. Tampoco tiene mucho mérito porque es un bundle vacío, pero bueno, vamos poco a poco.

3. Añadimos una clase BundleActivator a nuestro bundle

Una clase activator se encarga de inicializar/liberar un bundle. Esta pequeña clase nos avisará que el bundle ha sido inicializado/parado correctamente.

La clase debe implementar la interfaz BundleActivator definida en la API de OSGi. Para que nuestro código compile correctamente será necesario añadir un par de dependencias a nuestro pom.xml. Basta con añadir el siguiente fragmento de código:

<dependencies>
  ...
  <dependency>
    <groupId>org.osgi</groupId>
    <artifactId>osgi_R4_core</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.osgi</groupId>
    <artifactId>osgi_R4_compendium</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
  </dependency>
...
</dependencies>

El código de la clase SpringDMActivator es el siguiente:

package es.jmac.springdm;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

/**
 * @author Justo Aguilar
 * Creation date : 20/07/2009
 *
 * <p>
 */
public class SpringDMActivator implements BundleActivator {

	public void start(BundleContext context) throws Exception {
		System.out.println("Arrancando bundle " + context.getBundle().getSymbolicName() + "...");
	}

	public void stop(BundleContext context) throws Exception {
		System.out.println("Parando bundle " + context.getBundle().getSymbolicName() + "...");
	}
}

Un último paso necesario es añadir al fichero MANIFEST.MF la siguiente línea:

Bundle-Activator: es.jmac.springdm.SpringDMActivator

con la ruta de nuestro clase BundleActivator.

La próxima vez que arranquemos la instancia del OSGi Framework creada anteriormente debería aparecer el mensaje definido por el método start de la clase SpringDMActivator.

4. Spring DM entra en juego

En este post se hace un uso trivial e insignificante de Spring DM. En próximos post se mostrará la verdadera potencia de integrar Spring Framework con OSGi.

Nuestro objetivo en este último paso es inicializar un ApplicationContext de Spring al iniciar un bundle en OSGi.

Lo primero que haremos será crear un POJO para referenciarlo posteriormente en el fichero applicationContext.xml de Spring. Yo he creado una clase Entity :

package es.jmac.springdm.domain;

import java.io.Serializable;

/**
 * @author Justo Aguilar
 * Creation date : 20/07/2009
 *
 */
public class Entity implements Serializable {

	/** Identificador de Serialización  */
	private static final long serialVersionUID = 147003001012587224L;

	/** Identificador de la entidad */
	private Long id;

	/** Nombre de la entidad */
	private String name;

	/**
	 * Constructor de Entity. Necesita el identificador.
	 * @param id Identificador de la entidad.
	 */
	public Entity(Long id) {
		this.id = id;
		System.out.println("Creando entidad con id : " + id);
	}

}

A continuación hay que crear y definir el fichero applicationContext.xml de Spring. Este fichero se situará dentro de una carpeta spring que a su vez debe estar contenida en la carpeta META-INF del proyecto.

Proyecto
|__META-INF
    |__spring
        |__applicationContext.xml

Este fichero contendrá la siguiente información :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="entity" class="es.jmac.springdm.domain.Entity">
      <constructor-arg><value>1</value></constructor-arg>
    </bean>
</beans>

Ahora es cuando entra en juego Spring DM. Evidentemente, por si solo, Equinox no tiene ni idea para que sirve la carpeta spring creada bajo META-INF ni los ficheros contiene ésta, por tanto si volvemos a arrancar Equinox veremos que no sucede nada nuevo. Para dar un tratamiento especial a este tipo de bundles, Spring-Powered bundle según la documentación de Spring DM, es necesario que alguién esté pendiente de cuando se inicializa o se para este tipo de bundles. Ese alguien es conocido como Extender. Extender no es más que un bundle que está a la escucha del ciclo de vida del restos de bundles, cuando un bundle se inicializa o se para, comprueba si se trata de un Spring-Powered bundle para realizar la tarea de inicialiar/parar el ApplicationContext asociado al éste.

Por tanto será necesario instalar el bundle Extender y todos sus dependencias dentro de la Target Platform y marcarlos para que se ejecuten dentro de Equinox. Bueno, vamos por pasos.

Primero, nos descargamos todos los bundles que necesitamos. Para ello acudimos al repositorio de springsource, http://www.springsource.com/repository/app/ y nos descargamos el jar org.springframework.osgi.extender, en mi caso la versión 1.2.0. En la parte inferior de esa ventana aparece una sección plegada que dice Required Dependencies (9), la desplegamos y nos bajamos cada uno de los bundles que ahí se indican.

Dependencias Bundle Extender de Spring DM

Dependencias Bundle Extender de Spring DM

Segundo, copiamos los JARs descargados al directorio de nuestra Target Platform, si no sabes a lo que me refiero, no lo habrás cambiado asi que el directorio es $INSTALACION_ECLIPSE/plugins. Reiniciamos Eclipse.

Tercero, abrimos la ventana Run Configurations… y comprobamos que entre los bundles de la sección Target Platform se encuentran los que acabamos de copiar. Marcamos el bundle de Extender y volvemos a pinchar en Add Required Bundles, veremos como se seleccionan el resto de bundles copiados en el paso anterior. Aplicamos y ejecutamos.

Si no os aparece el mensaje definido en el constructor del POJO, algo habréis hecho mal.

Particularidades de Java2WSDL en Axis

Hace un par de semana me estuve peleando con los scripts de Axis que permiten crear documento WSDL a partir de una clase Java, Java2WSDL y viceversa, generar el skeleton o stub Java a partir de un documento WSDL, WSDL2Java. Particularmente la herramienta java2wsdl tiene ciertos detalles que pueden llegar a ser molestos si no se sabe como abordarlos. A continuación os cuento los problemas que tuve y las soluciones que adopté.

 

1. WSDL es un estándar web.
La afirmación anterior implica que la información que puede contener este documento debe ser independiente de cualquier plataforma, arquitectura, sistema operativo o lenguaje de programación, por tanto, en la sección donde se define los tipos no puede haber referencia a ningún tipo específico Java, tal como List, ArrayList, Map, ….

Solución:
Cualquier método que quiera ser publicado a través de un servicio web debe tener como parámetros de entrada y/o salida, tipos estándares, esto es en Java, tipos primitivos, arrays u objetos propios serializables que contengan tipos estándares.

Ejemplo
Supongamos que tenemos una clase Foo que queremos publicar como servicio web y que contiene un tipo no estándar, por ejemplo una Lista.

public class Foo {
  private List<String> lstStrings;

  public List<String> getLstStrings() {
    return this.lstStrings;
  }

  public void setLstStrings(List<String> lstStrings){
    this.lstStrings = lstStrings;
  }
  ...
}

Bien, pues si queremos que la herramienta java2wsdl genere un documento WSDL correcto debemos transformar el código anterior en el siguiente:

public class Foo {
  private List<String> lstStrings;
  private String[] arrayStrings;     // Según la versión de axis, es necesario declararlo o no

  public List<String> getLstStrings() {
    return this.lstStrings;
  }

  public void setLstStrings(List<String> lstStrings) {
    this.lstStrings = lstStrings;
  } 

  public String[] getArrayStrings() {
    if ( getLstStrings() != null ) {
      return getLstStrings().toArray(new String[getLstStrings().size()]);
    }
    return new String[0];
  }

  public void setArrayStrings(String[] arrayStrings) {
    this.setArrayStrings(Arrays.asList(arrayStrings));
  }
  ...
}

 

2. No están soportados los modelos de datos con clases internas.
Actualmente el script no genera correctamente el conjunto de tipos del documento WSDL si el método de la clase a publicar contiene parametros cuyos tipos sean clases internas.

Este error es debido a un fallo en el jar annogen-0.1.0; existe un parche oficioso que genera el WSDL correctamente pero si usamos wsdl2java para generar el cliente, la invocación al servicio dará un fallo.

Solución:

  1. Sacar las clases internas de la clase del servicio.
  2. Utilizar el jar oficioso de Deepal Jayasinghe y crear el stub sin utilizar WSDL2Java.

 

3. Nuestro servicio lanza una excepción.
Siempre que nuestro servicio lanza una Exception la herramienta java2wsdl intenta mapear el tipo de la excepción a un tipo WSDL. Esto siempre produce el código :

...
<xs:complexType name="MyException">
  <xs:complexContent>
    <xs:extension base="xs:Exception" />
    <!-- o <xs:extension base="xs:RuntimeException" /> -->
    <xs:sequence />
  </xs:complexContent>
</xs:complexType>
...

Al final java2wsdl busca la clase padre de la que extiende nuestra exception para mapearla según la semántica que permite WSDL, sin embargo, ni xs:Exception ni xs:RuntimeException existen como tipo estándares de XML-Schema lo que provoca la existerncia de warnings/errores de los editores XML que validan los documentos.

Solución: La única solución que he encontrado es hacer el siguiente cambio :

...
<xs:complexType name="MyException">
  <xs:complexContent>
    <xs:extension base="xs:anyType" />
    <!-- o <xs:extension base="xs:anyType" /> -->
    <xs:sequence />
  </xs:complexContent>
</xs:complexType>
...

Eso si, si se genera un cliente a partir de este documento WSDL se deberán modificar las clases asociadas a las excepciones para que extiendan de RuntimeException o Exception o cualquier otra.

 

4. <soap12:fault />.
Cuando se crea un documento WSDL para que pueda ser utilizado por las dos versiones de SOAP existentes, 1.1 y 1.2, la parte del binding de SOAP 1.2 es incorrecta.

La herramienta java2wsdl genera un código tal como este :

...
<wsdl:binding...>
  <soap12:opertation ... />
  <wsdl:operation ...>
    ...
    <wsdl:fault ...>
      <soap12:fault use="literal" name="MyException" />
    </wsdl:fault>
  </wsdl:operation>
</wsdl:binding>
...

pues el código anterior falla, el elemento <soap12:fault use="literal" name="MyException" /> no es correcto y, por ejemplo, la herramient wsdl2java no puede crear un cliente correcto a partir del WSDL

Solución: Cambiar <soap12:fault use="literal" name="MyException" /> por <soap:fault use="literal" name="MyException" />

 

Si habéis tenido problemas parecidos y los habéis resuelto de una forma distinta a la mía, por favor, no dudéís es compartirlo para que todos salgamos ganando.

 

Javadoc con Diagramas de Clases usando Graphviz + UMLGraph + Maven

Una de las herramientas más importantes con las que cuenta la plataforma Java es, sin duda, el javadoc. A través de ella es posible crear una referencia completa y homogénea de todo el conjunto de clases definidas es nuestro proyecto.

Toda página de una clase del javadoc está dividida en distintas zonas, la primera de ellas muestra la situación jerárquica de la clase respecto a la clase padre Object. Bajo la situación jerárquica aparece la lista de interfaces que implementa si existe alguna y, por último, la lista de clases que la extienden. Para las interfaces aparece la lista de superinterfaz en primer lugar y la lista de subinterfaces en segundo.

Aunque esta información pueda ser suficiente en muchos casos, no estaría de más completarla con diagramas de clases que permitan una rápida visualización de la jerarquía de clases de nuestro proyecto. Ese es el objetivo de este post, añadir diagramas de clases UML a nuestro javadoc.

Diagrama UML generado con UMLGraph

Para ello necesitaremos dos cosas básicamente :

  • Graphviz, librería que permite la generación de gráficos.
  • UMLGraph plugin para maven que sobreescribe el doclet que genera el javadoc para insertar los gráficos generados por Graphviz.
  • Maven

A continuación vamos a ver como instalar Graphviz y como configurar el pom.xml del proyecto maven para que use el plugin UMLGraph.

Instalación de Graphviz

Hay varias formas de hacerlo, bajando el fuente y compilando, haciendo un checkout del repositorio CVS o dejando que el sistema de paquetes de tu sistema operativo lo haga todo por tí ;-). Esto último es lo que haremos pues la finalidad de éste post es otra.

sudo apt-get update
sudo apt-get install graphviz
man dot

La última entrada es sólo para comprobar que el paquete ha sido instalado correctamente.

Modificación del fichero pom.xml del proyecto

Simplemente se debe añadir al fichero las siguientes líneas de código

...
<reporting>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <configuration>
        <doclet>gr.spinellis.umlgraph.doclet.UmlGraphDoc</doclet>
        <docletArtifact>
          <groupId>gr.spinellis</groupId>
          <artifactId>UmlGraph</artifactId>
          <version>4.6</version>
        </docletArtifact>
        <additionalparam>
          -inferrel -inferdep -quiet -hide java.* -collpackages java.util.* -qualify
          -postfixpackage -nodefontsize 9
          -nodefontpackagesize 7
        </additionalparam>
      </configuration>
    </plugin>
  </plugins>
</reporting>
...

Para generar el javadoc se ejecutará el comando mvn javadoc:javadoc dentro del directorio
donde se encuentra el fichero pom.xml.

Javadoc de la clase Nivel 1