Blade, OSGI y las dependencias

Automatizando la inclusión de dependencias

Publicado por Miguel Ángel Júlvez el 15 de octubre de 2016

Seguramente la tarea que me ha resultado mas tediosa durante estos meses con el cambio de liferay 7 a un contenedor de modules osgi, ha sido la gestión de dependencias transitivas de los mismos usando el entorno de trabajo de Blade (gradle).

Cuando añadía una librería dependiente al fichero build.gradle de un module, tenía que añadir manualmente todas las librerías dependientes a esta y además añadirlas manualmente al fichero bnd.bnd del mismo. Esta labor me llevaba horas (y no es una exageración).

Pues bien, resulta que Blade incorpora un plugin por defecto que usa bndtools para automatizar todo este proceso (este es el plugin en cuestión se llama gradle-bundle-plugin y lo podéis ver aquí https://github.com/TomDmitriev/gradle-bundle-plugin) y que lo que antes me llevaba horas ahora me lleva unos pocos minutos.

Vamos a ver un ejemplo.

Creando nuestro modulo osgi

Lo primero que necesitamos es tener un entorno de trabajo. En este otro post puedes ver como hacerlo (han cambiado algunas cosas desde que lo escribí. Dentro de poco haré una actualización de ese post para incorporar las novedades y algunos trucos que he aprendido durante estos meses).

Una vez creado el entorno de trabajo, vamos a asegurarnos de que estamos usando la última versión disponible del plugin de gradle hecho por la gente de liferay.

En el momento de escribir este post la última versión es la 1.1.0, así que el fichero settings.gradle debería quedarte así

buildscript {
  dependencies {
    classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "1.1.0"
    }
  repositories {
    maven { url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" }
  }
}

apply plugin: "com.liferay.workspace"

Una vez hecho esto, vamos a crear nuestro módulo osgi.

A modo de ejemplo, vamos a suponer que queremos construir un nuevo buscador, completamente separado del buscador por defecto de liferay, que va a exponer una serie de métodos para indexar los assets de liferay que nosotros queramos y que va a realizar una serie de peticiones al motor de búsqueda de elasticsearch directamente para devolver los resultados.

Un poco la idea que tengo con este ejemplo, es ir añadiendo más entradas en el blog en el futuro para ir yendo poco a poco viendo las distintas opciones que tenemos con la modularidad osgi
cd modules

blade create -t mvcportlet -p com.miguelangeljulvez.buscador -c BuscadorPortlet buscador

Una vez creado el módulo, abrimos el fichero build.gradle y veremos algo así:

dependencies {
        compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
        compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
        compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
        compileOnly group: "javax.servlet", name: "servlet-api", version: "2.5"
        compileOnly group: "jstl", name: "jstl", version: "1.2"
        compileOnly group: "org.osgi", name: "org.osgi.compendium", version: "5.0.0"
}

Ahora vamos a añadir la dependencia de elasticsearch (estoy usando elasticsearch 2.2.x ya que es la versión soportada por liferay pero recuerda que puedes poner una versión más nueva y en este post te indicaba como hacerlo)

dependencies {
   compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
   compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
   compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
   compileOnly group: "javax.servlet", name: "servlet-api", version: "2.5"
   compileOnly group: "jstl", name: "jstl", version: "1.2"
   compileOnly group: "org.osgi", name: "org.osgi.compendium", version: "5.0.0"

   compile group: 'org.elasticsearch', name: 'elasticsearch', version: '2.2.2'
}

En este caso la dependencia la incluyo usando “compile” en lugar de “compileOnly” ya que quiero que todas sus dependencias sean incluidas en mi módulo. Podríamos verlo como que la instrucción “compileOnly” sería como el “Provided” de maven mientras que la instrucción “compile” sería igual que el “compile” de maven.

En realidad es una simple manera de proceder ya que realmente lo que va a indicar si las clases de las dependencias se incorporan a nuestro módulo, es lo que vamos a ver a continuación, pero me parece una manera interesante para ver rápidamente qué clases espero que estén incluidas en el módulo osgi.

A continuación, vamos a indicar una serie de instrucciones bndtools en la configuración del plugin gradle-bundle-plugin que indicarán cómo se debe generar el fichero META-INF/MANIFEST.MF de nuestro módulo osgi.

Si necesitas saber más sobre conceptos básicos de osgi, aquí hay una buena referencia de lo mas importante con la explicación de todas las instrucciones que vamos a usar en este post http://www.eclipse.org/virgo/documentation/virgo-documentation-3.6.0.M03/docs/virgo-user-guide/html/ch02s02.html
bundle {
  includeTransitiveDependencies = true

  instructions << [
        'Export-Package' : '!*',

        'Import-Package' : 'com.liferay.*,' +
              'javax.portlet.*,' +
              'javax.servlet.*,' +
              'org.apache.taglibs.*',

        'Private-Package': '!com.liferay.*, ' +
              '!javax.portlet.*, ' +
              '!javax.servlet.*, ' +
              '!org.apache.taglibs.*' +
              '!org.osgi.*,' +
              '*',

        '-sources'       : false
  ]
}

Lo primero que hay que darse cuenta es que, usando este plugin, cualquier instrucción que pongamos en el fichero bnd.bnd y que se defina en la configuración anterior será sobreescrita.

En resumen lo que estamos diciendo en esta configuración es que:

  • Se incluyan en nuestro module todas las dependencias transitivas. Por ejemplo, elasticsearch necesita lucene para funcionar. Esta instrucción hará que las clases de lucene estén disponibles para incorporarlas al module.
  • No exportamos nada (esto lo haremos en otro post). Date cuenta que escribiendo “!” es la negación de la expresión.
  • Nuestro módulo para funcionar necesita una serie de packages que simplemente importamos porque ya que hay otros módulos de liferay que los exportan
  • Vamos a incluir en nuestro jar todas las clases del classpath que no empiecen por com.liferay, javax.portlet, javax.servlet ni org.osgi. Es decir, elasticsearch y todas sus dependencias como por ejemplo las de lucene que hemos comentado antes.
  • No queremos que las clases .java se añadan al módulo para que sea más ligero.

Una vez hecho esto, lo que tenemos que hacer es compilar nuestro módulo.

blade gw build

Durante la construcción veremos muchos warnings de este tipo

Use Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning Package found in [Jar:elasticsearch-2.2.0, Jar:lucene-suggest-5.4.1]
Class path [Jar:com.liferay.portal.kernel-2.0.0, Jar:com.liferay.util.taglib-2.0.0, Jar:portlet-api-2.0, Jar:servlet-api-2.5, Jar:jstl-1.2, Jar:org.osgi.compendium-5.0.0, Jar:elasticsearch-2.2.0, Jar:lucene-core-5.4.1, Jar:lucene-backward-codecs-5.4.1, Jar:lucene-analyzers-common-5.4.1, Jar:lucene-queries-5.4.1, Jar:lucene-memory-5.4.1, Jar:lucene-highlighter-5.4.1, Jar:lucene-queryparser-5.4.1, Jar:lucene-suggest-5.4.1, Jar:lucene-join-5.4.1, Jar:lucene-spatial-5.4.1, Jar:guava-18.0, Jar:securesm-1.0, Jar:hppc-0.7.1, Jar:joda-time-2.8.2, Jar:joda-convert-1.2, Jar:jackson-core-2.6.2, Jar:jackson-dataformat-smile-2.6.2, Jar:jackson-dataformat-yaml-2.6.2, Jar:jackson-dataformat-cbor-2.6.2, Jar:netty-3.10.5.Final, Jar:compress-lzf-1.0.2, Jar:t-digest-3.0, Jar:HdrHistogram-2.1.6, Jar:commons-cli-1.3.1, Jar:jsr166e-1.1.0, Jar:lucene-sandbox-5.4.1, Jar:lucene-misc-5.4.1, Jar:lucene-grouping-5.4.1, Jar:lucene-spatial3d-5.4.1, Jar:spatial4j-0.5, Jar:snakeyaml-1.15, Jar:main, Jar:main],
Split package, multiple jars provide the same package:org/apache/lucene/index/memory

Esto es porque ahora mismo en nuestro classpath, al tener todas las dependencias de elasticsearch, tenemos algunos packages comunes que están en distintas dependencias y bndtools no sabe cómo proceder al ponerlos dentro del mismo .jar. Así que en estos casos, debemos indicarle en la configuración qué hacer.

En nuestro caso la configuración final quedaría así:

bundle {
  includeTransitiveDependencies = true

  instructions << [
        'Export-Package' : '!*',

        'Import-Package' : 'com.liferay.*,' +
              'javax.portlet.*,' +
              'javax.servlet.*,' +
              'org.apache.taglibs.*',

        'Private-Package': '!com.liferay.*, ' +
              '!javax.portlet.*, ' +
              '!javax.servlet.*, ' +
              '!org.osgi.*,' +
              '!org.apache.taglibs.*' +
              'org.apache.lucene.*;-split-package:=merge-first,' +
              'org.joda.*;-split-package:=merge-first,' +
              'org.elasticsearch.*;-split-package:=merge-first,' +
              '*',

        '-sources'       : false
  ]
}

 

Hay una última cosa que debemos darnos cuenta. Cuando usamos la wildcard ‘*’ dentro de Private-Package, estamos diciéndole a bndtools que incluya absolutamente todas las clases que hay en nuestro classpath excepto las que hemos marcado con ‘!’. Esto quiere decir que, probablemente, tendremos clases en nuestro módulo jar que no son necesarias, lo que hace que nuestro tiempo de empaquetado y que el peso final del fichero .jar sea mayor. Por ejemplo, si nuestro buscador no va a usar la búsqueda espacial de lucene, esas clases nunca van a ser necesarias y podríamos indicar que no se incluyeran dentro del module osgi.

 

En la siguiente entrada del post (en construcción), veremos cómo exponer los métodos para indexar los assets de liferay en nuestro elasticsearch y cómo usarlos