Maven y osgi
Un jar de OSGI es un jar un tanto especial. Lleva un fichero de manifiesto con unas propiedades específicas de OSGI y lleva dentro también los jar de los que depende. Este tipo de jar de OSGI reciben el nombre de bundle.
En cuanto a un proyecto OSGI para eclipse, también es un proyecto especial. Necesita que el fichero de manifiesto esté en el directorio META-INF en el directorio raíz del proyecto eclipse, aparte de que debe tener la "faceta" de plugin.
Veamos aquí como configurar el fichero pom.xml de maven para que pueda hacer ambas cosas, crear el jar al estilo OSGI y generar el proyecto eclipse. Para ello, utilizaremos el plugin de maven maven bundle plugin
Poner maven bundle plugin en el pom.xml[editar]
Partiendo de un proyecto java normal creado con maven, por ejemplo, con el comando
mvn archetype:create -DgroupId=com.chuidiang.ejemplos -DartifactId=ejemplo-maven-bundle-plugin
editamos el fichero pom.xml para añadir el plugin en cuestión. Debemos poner lo siguiente
<project> ... <build> ... <plugins> ... <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.5.3</version> <extensions>true</extensions> <configuration> <instructions> <Export-Package>com.chuidiang.ejemplos.api</Export-Package> <Private-Package>com.chuidiang.ejemplos.*</Private-Package> <Import-Package>org.osgi.framework;version="[1.3,2)";resolution:=optional</Import-Package> <Bundle-Activator>com.chuidiang.ejemplos.impl1.Activator</Bundle-Activator> <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency> <Embed-Transitive>true</Embed-Transitive> <Embed-Directory>lib</Embed-Directory> </instructions> </configuration> </plugin> </plugins> </build> ... </project>
Si además nuestro código necesitara usar cosas de OSGI (Como BundleContext o similares), necesitamos añadir las dependencias
<dependency> <groupId>org.osgi</groupId> <artifactId>osgi_R4_core</artifactId> <version>1.0</version> <scope>provided</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi_R4_compendium</artifactId> <version>1.0</version> <scope>provided</scope> <optional>true</optional> </dependency>
Son provided porque el entorno OSGI nos las proporcionará.
Instructions del plugin[editar]
Veamos algunos detalles de lo que hemos puesto en ese plugin.
groupId, artifactId y version son los habituales en cualquier plugin, especifican qué plugin queremos usar, en este caso, maven-bundle-plugin en versión 2.5.3, que es la más actual en el momento de escribir este artículo.
extensions a true. Los plugin que modifican de alguna forma en el proceso de construcción del jar de maven, necesitan habilitar las extensiones de maven y este es el caso, debemos poner extensions a true para que maven-bundle-plugin funcione correctamente.
Y ahora la parte interesante, la configuración del plugin que nos permite indicar cómo queremos nuestro bundle (nuestro jar de OSGI). La configuración lleva dentro un paquete de instructions. Dentro podemos poner muchas cosas, entre otras, algunas indicaciones de lo que queremos que vaya en el fichero de manifiesto del bundle. Veamos algunas de ellas que son habituales
Export-Package contiene los paquetes que queremos que vayan dentro del jar y sean exportados a otros bundles que corran en el mismo entorno. Podemos separar varios paquetes por comas.
Private-Package son los paquetes que queremos que vayan dentro del jar y que no sean exportados. Por defecto, si omitimos esta línea, se meterán en el jar todos los paquetes de nuestro src/main/java, así que omitir esta línea suele ser una buena opción. Se pueden poner varios paquetes separados por comas y se admiten astericos, como en el ejemplo, de forma que se meterían en el jar todos los paquetes y subpaquetes que cuelguen del que se indica.
Si Export-Package y Private-Package solapan (ambos indican de alguna forma el mismo paquete), el paquete será exportado.
Import-Package indica qué paquetes deben importarse de otros bundle. Si omitimos esta línea, se importarán por defecto todos los package exportados y todos los package a los que nuestro código haga referencia y no estén en src/main/java. Si nuestro código no necesita otros jar externos (como log4j.jar, hibernate.jar, etc), puede ser buena opción omitir esta línea. Sin embargo, si dependemos de otros jar externos, aunque estén dentro de nuestro jar, Import-Package, por defecto, importará todos los packages que encuentre dentro de esos jar, lo que puede ser bastante engorroso. En este caso, es mejor poner a mano los packages concretos que queremos importar, incluyendo los que exportamos. Se deben incluir los paquetes que se exportan para evitar conflictos de classpath en caso de que varios bundles decidan exportar los mismos package/clases.
Bundle-Activator es, si la hay, la clase Activator de nuestro Bundle. Ver 3.1 en Hola Mundo con OSGI
Embed-Dependency indica que jar de los que dependemos queremos dentro de nuestro bundle. En el ejemplo hemos indicado que queremos todos los jar de los que dependemos para "compile" o "runtime". Quedan así excluidas depedencias de "test" o "provided"
Embed-Transitive es por defecto false, e indica si queremos o no que se metan en el bundle los jar de los que dependen los jar de los que dependemos. Me explico, si nuestro bundle en el pom.xml dice que depende de un.jar y este un.jar, por su cuenta, depdende de otro.jar, Embed-Transitive indica si queremos que otro.jar se meta también o no en nuestro bundle.
Embed-Directory indica en qué directorio dentro de nuestro bundle queremos meter los jar de los que dependemos. "lib" suele ser un nombre habitual, aunque por seguir el estilo maven, también se suele usar un directorio "target/dependencies".
Listo. Si ejecutamos el comando
mvn package
se generará el jar de la forma habitual, pero contendrá el fichero de manifiesto al estilo OSGI con lo que hemos indicado y además tendrá dentro un directorio lib con todos los jar de los que dependemos.
Montar el proyecto en eclipse[editar]
Un proyecto de plugin OSGI en eclipse es un proyecto especial, por lo que el simple mvn eclipse:eclipse no nos va a valer, necesitamos configurar alguna cosa más en el pom.xml
El proyecto eclipse de plugin necesita que el fichero de manifiesto esté en el raíz del proyecto eclipse, dentro de una carpeta META-INF. Es decir, en nuestro raíz del proyecto necesitamos META-INF/MANIFEST.MF. Debemos entonces indicarle a maven-bundle-plugin que haga una copia en esa ubicación. Para ello, añadimos la siguiente línea en el pom.xml, donde el maven-bundle-plugin
<extensions>true</extensions> <configuration> <manifestLocation>META-INF</manifestLocation> <instructions> <Export-Package>com.chuidiang.ejemplos.api</Export-Package>
es decir, dentro de la configuración del plugin, ponemos la etiqueta 'manifestLocation indicando en qué directorio queremos que se genere el fichero de manifiesto.
Y para que el comando mvn eclipse:eclipse sepa el tipo de proyecto a montar, debemos habilitar el flag pde. Esto podemos hacerlo bien en el mismo comando
mvn eclipse:eclipse -Declipse.pde
bien configurando el plugin maven-eclipse-plugin dentro de nuestro pom.xml, poniendo dentro de los tags build lo siguiente
... <build> <plugin> <artifactId>maven-eclipse-plugin</artifactId> <version>2.9</version> <configuration> <pde>true</pde> </configuration> </plugin> ... </build>
Otro detalle a tener en cuenta es que el fichero META-INF/MANIFEST.MF se genera automáticamente cuando hacemos mvn package, por lo que es necesario ejecutar este comando antes de el de eclipse, de forma que tengamos el fichero de manifiesto creado y actualizado
mvn package eclipse:eclipse
¿Quién fue antes? ¿La gallina o el huevo?[editar]
Hay un pequeño detalle que debemos tener muy en cuenta. maven-bundle-plugin revisa nuestro código para ver qué paquetes necesitamos y así hacer el Import-Package correspondiente dentro del fichero de manifiesto. Por ello, si creamos nuestro proyecto eclipse antes de empezar a usar clases, tendremos un Import-Package escaso y cuando empecemos a codificar, veremos que no tendremos acceso a esos paquetes. Asi que se nos presenta el problema del huevo y la gallina : necesitamos código que uses paquetes para que se genere el Import-Package, pero necesitamos Import-Package para poder usar esos paquetes. No queda más remedio que ir haciendo un pequeño juego. Por ejemplo, si queremos usar javax.swing, en el pom.xml debemos poner en Import-Package que queremos javax.swing, luego generar el proyecto eclipse para que nos regenere el fichero de manifiesto con el nuevo Import-Package y luego empezar a codificar.
Si el Import-Package se genera automáticamente porque no hemos puesto específicamente esta línea en el pom.xml, la opción es en el fuente java añadir el import javax.swing aunque proteste, salvar el fichero y luego regenerar el proyecto eclipse con maven, para que se actualice el fichero de manifiesto.
Servicios declarativos[editar]
Existe un nuevo plugin, maven-scr-plugin que automáticamente nos generaría los ficheros xml para servicios declarativos. Este plugin necesita que en nuestro código java pongamos anotaciones estilo @Component, @Service (propias de OSGI) de forma que luego él analizará los fuentes y sus anotaciones para generar los ficheros xml correspondientes.
A mí, personalmente, no me gusta "guarrear" mis clases con anotaciones que no son propias de java, porque eso me ata al framework que estoy usando (OSGI en este caso) y prefiero hacer manualmente el xml dejando las clases límpias de anotaciones extrañas. Por ello, vamos a ver aquí cómo hacerlo manualmente.
Si queremos usar Servicios Declarativos, en la configuración de maven-bundle-plugin podemos poner bajo Instructions un nuevo tag Service-Component indicando la ubicación del fichero xml donde se define nuestro servicio, puede ser algo así
<plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.5.3</version> <extensions>true</extensions> <configuration> <manifestLocation>META-INF</manifestLocation> <instructions> ... <Service-Component>OSGI-INF/ThePublicInterface.xml</Service-Component> ... </instructions> </configuration> </plugin>
El proyecto eclipse necesita que este directorio/fichero OSGI-INF/ThePublicInterface.xml esté en el raíz, así que ahí lo creamos. Sin embargo, necesitamos también que este directorio/fichero se meta dentro de nuestro bundle (nuestro jar) cuando lo generemos, por lo que debemos añadirlo como "recurso" del proyecto maven. En un proyecto maven, son recursos todos aquellos directorios que se empaquetarán dentro del jar. Por defecto, maven considera como directorio de recursos el src/main/resources, por lo que todo lo que haya ahí dentro se meterá en el jar. Para añadir además como directorio de recursos el raíz, pero indicándole que sólo incluya el subdirectorio OSGI-INF, en el pom.xml debemos añadir la etiqueta resources con el siguiente contenido
<build> <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>.</directory> <includes> <include>OSGI-INF/*</include> </includes> </resource> </resources> <plugins> ...
Necesitamos añadir el resource OSGI-INF/* para que se empaqueten en el jar todos lo que haya dentro del directorio raíz (directorio .) en el subdirectorio OSGI-INF. Al indicar nosotros manualmente los resources, src/main/resources dejaría de estar incluido, por ello, debemos ponerlo también explícitamente si queremos seguir conservándolo. src/main/resources es el directorio habitual donde se meten iconos o ficheros de configuración que queramos que vayan dentro del jar.
Con esto está todo listo. Si generamos un jar con
mvn package
y lo abrimos con winzip, winrar o similar, veremos dentro el directorio OSGI-INF con el fichero xml. Si generamos el proyecto eclipse con
mvn package eclipse:eclipse
y ejecutamos, veremos que nuestro servicio declarativo funciona correctamente.
Ejemplo completo[editar]
Puedes ver este ejemplo completo con algo de código en ejemplo-maven-felix-plugin