Maven: Parent, Module y BOM (Bill of Materials)

Aunque últimamente ando con gradle y en gradle he resuelto el tema que comento aquí, quería ver si podía hacer algo similar con maven.

¿Cual es el tema?

Imagina que tienes un «producto» SW en tu empresa, algo como el control de una flotilla de camiones, que vendes a distintos clientes que tienen camiones y necesidad de controlar su flotilla. ¿Y por qué «producto» entre comillas?. Aunque tu producto está guay, cada cliente siempre quiere algunas funcionalidades adicionales que tu producto no tiene, o modificación de las que ya existen y la política de tu empresa es hacerle caso al cliente.

Así que tienes tu proyecto organizado en un montón de proyectos maven (o gradle):

  • Uno o más proyectos maven con el «core» de tu producto donde está lo que sabes que siempre va a ir a cualquier cliente.
  • Varios proyectos maven con «plugins» con funcionalidad adicional que puedes añadir o no a tu core según el cliente los pida o no.
  • Varios proyectos maven con librerías tuyas propias de utilidades, relativas a o no a tu producto. Por ejemplo, si tu producto lee de alguna forma los GPS de los camiones para saber por dónde andan, puedes tener varias librerías para leer distintos modelos de GPS.

Ahora imagina que tienes que trabajar en tu producto para hacerle modificaciones al cliente «Fulanito Logistic» que quiere determinados plugins, alguno más que tienes que desarrollar, te ha pedido además algo que te interesa meter en el core porque lo ves útil para siempre, etc.

Y encima, tienes otro proyecto en paralelo para «Menganito Logistic» que quiere otros plugins distintos y también quiere ciertas modificaciones, pero que mira tú que cosas, no son las mismas que quiere «Fulanito Logistic».

¿Cómo montas esto en tu IDE favorito? ¿Y tu compañero que está trabajando para la otra compañía?

Pues bien, inicialmente, montábamos todo (core, todos los plugins y todas las librerías), pero eso hace que el proyecto crezca cada vez más, se haga más inmanejable y los compilados sean cada vez más costosos. Además, empiezan a proliferar en el código como como «if es para fulanito ….», o algo más elaborado porque tenemos bastante friki como anotaciones en las clases estilo @Menganito.

Así que al final, montamos algo un poco más complejo, pero que permite que nuestro IDE y nuestros jenkins cojan solo lo que es necesario en cada caso.

En algún sitio de la estructura de git, metemos un pom.xml parent con las cosas que son comunes en nuestro producto. Por ejemplo, dependencyManagment con las versiones de spring-framework, junit, slf4j/logback, etc. También  properties que necesitemos, etc, etc.

En la misma estructura de git, tenemos un directorio plugins y debajo de el un proyecto maven para cada plugin. Justo debajo de plugins, tenemos un pom.xml con module para cada plugin, algo así

plugins/pom.xml
plugins/plugin1/pom.xml
plugins/plugin2/pom.xml

Cada plugin tiene como padre al plugins/pom.xml y el plugins/pom.xml tiene a su vez como padre al parent común que tenemos por ahí.

plugins/pom..xml tiene como module a todos los plugins y al core de nuestra aplicación. Este pom.xml es el que montamos en el IDE si estamos trabajando en el producto en general y no para un cliente concreto. Además, es el que metemos en jenkins para asegurarnos que los plugins y el core siempre son compatibles.

Por otro lado, para trabajar para un cliente concreto, creamos en el mismo git un directorio proyectos con dos subdirectorios, menganito y fulanito. Todos con su pom.xml de forma similar al de plugins

proyectos/pom.xml
proyectos/menganito/pom.xml
proyectos/fulanito/pom.xml

proyectos/pom.xml tiene como padre al padre común que habíamos hecho por ahí y menganito/pom.xml y fulanito/pom.xml tienen como padre a proyectos/pom.xml

A su vez, menganito y fulanito pom.xml tienen «module».que con path relativo, incluye los plugins necesarios para ese cliente concreto, así como el core. Tambien, debajo de menganito y fulanito se crean subproyectos con código que es específico para ese cliente y que no tenemos previsto reutilizar más.

El que trabaje para menganito, monta en su IDE el menganito/pom.xml y así solo tiene el core, los plugins específicos y el código específico para menganito, ganando tiempo de compilado, menos complejidad de clases/proyectos en el IDE, etc. También esto facilita el hacer el zip/instalado específico, vía maven, para este cliente concreto. Y un jenkins que solo compila lo de ese cliente.

En parent y modules con maven tienes un ejemplo y detalles de como montar todo esto.

Maven, Gradle, …


Siempre he usado maven, pero hace unos años me cayó un proyecto ya empezado, con mucho código y que estaba en gradle, así que llevo ya unos añitos trabajando también con gradle. Aquí mis impresiones.

Por un lado, al principio me gustó. Un fichero build.gradle para un proyecto sencillo se escribe desde cero sin demasiados problemas. Apply plugin java, poner repositories, dependencias y ya. Un pom.xml equivalente no se pone tan fácil desde cero, siempre es copiar y pegar de algún sitio donde ya esté hecho.

Pero con el uso y en proyectos complejos que requieren cosas, me he encontrado con bastantes pegas que hacen que gradle no me guste, más allá de usarlo en proyecto sencillos. Comento algunas de las pegas

Los classifier

En los artifactos (jar) se pone el groupId, un artifactId y una versión. A veces se pone también un classifier para poder distinguir dos artefactos que nos interesa agrupar en el mismo groupId, artifactId y version, para poder distinguirlos. Un ejemplo claro es cuando hay librerías nativas de por medio. Imagina una librería java que use gráficos que tire de librerías nativas: dll en windows, .so en linux, etc. Una práctica habitual es meter estas librerías nativas en un jar, ya que un jar no es más que empaquetado como un zip o similar y se le pueden meter todo tipo de ficheros. Luego, en ejecución, nuestro código java debe determinar el sistema operativo en el que está corriendo y cargar la librería nativa que corresponda: la .dll, la .so, etc. Es también habitual, puesto que hay distintos tipos de micros y luego arquitecturas de 32 y 64 bits, no empaquetar todas las posibles librerías nativas en un solo jar, sino meter en un jar distinto cada una de ellas, así tendremos la dlll de 32 y 64 bits, etc. Y aquí es donde entran los classifier en juego. Nuestra librería java sería por ejemplo (formato groupId:artifactId:version:classifier) com.chuidiang:librería:1.0 y cada una de las que tienen librerías nativas dentro llevarían un classifier estilo com.chuidiang:libreria:1.0:windows-32, com.chuidiang:libreria:1.0:windows-64, com.chuidiang:libreria:1.0:linux-64, etc, etc.

Pues gradle, según como montes esto, lo entiende o no. Puedes tener en tu repositorio maven todos los jar correctamente subidos, pero si tu librería tiene dependencia de todas las librerías con classifier, hay casos que gradle no las baja. Hay otros casos que sí, seguramente dependiendo de algún detalle de cómo se han organizado en el repositorio. En maven funciona siempre. Y si el repositorio y la librería no son tuyo y no puedes organizar estas dependencias, no te queda más remedio, como nos ha pasado a nosotros, que poner una a una todas las dependencias con classifier a mano. En nuestro caso, librería es de terceros, en su pom subido al repositorio maven tienen las dependencias runtime de todos las librerías con nativas dentro y gradle lo ignora, no las baja.

Repositorio local propio

Imagina dos proyectos totalmente separados, en tu PC, que no tienen que ver como para montarlos juntos en un multi-proyecto gradle o como módulos maven. Uno es una librería y el otro es un proyecto con su main. E imagina que tu proyecto usa esa librería, así que pones la dependencia correspondiente. Ahora imagina que necesitas tocar la librería, a esta le pones una versión SNAPSHOT para decir que se está modificando y para probar rápido, en tu proyecto pones dependencia de esa versión SNAPSHOT.

Con maven no hay ningún problema. Cuando tocas la librería haces un mvn install y luego desde el proyecto ya estás viendo los cambios.

Gradle, sin embargo, no tiene una forma «nativa» de hacer esto. Por mucho gradle build que hagas en la librería, no verás los cambios en tu proyecto.  Gradle build no guarda los jar que genera en su repositorio/caché de jars, por lo que el proyecto no ve los cambios.

¿Cual es la solución? Ponerle a gradle el plugin de maven, de forma que puedas hacer gradle install que subirá el jar al repositorio local de maven, y poner en tu proyecto un repositorio mavenLocal(). Bueno, es una solución, pero desde mi punto de vista le quita un punto a gradle, para hacer algo que en proyectos relativamente grandes que reutilizan librerías propias hechas aparte, tenemos que tirar de maven.

Y se me presenta además un problema adicional. Si por el motivo que sea la dependencia de la librería SNAPSHOT está en la caché de gradle, ya la hemos liado. Aunque hagas gradle install para subir una nueva versión SNAPSHOT a tu repositorio maven local, el gradle de proyecto se lía un poco, como ya la tienen en la caché, no mira si hay actualizaciones en mavenLocal. La opción –refresh-dependencies no me ha parecido funcionar, debe mirar solo en repositorios externos. Así que mi única forma de hacer esto es borrar de la caché de gradle esas versiones snapshot, cosa que según documentación no se debe hacer (tocar la caché).

Plugins

Maven es más veterano y tiene muchos más plugins hechos y probados que gradle. Con gradle, desde luego, los hay o te los puedes hacer, pero es complicado. Un ejemplo claro que vi hace tiempo es el trabajar con OSGI. En maven hay un plugin, que tendrá sus cosas, pero funciona correctamente. En gradle, cuando miré, había uno de aquella manera que apenas facilitaba nada.

Gradle, por supuesto, es mucho más versátil que maven, en el build.gradle prácticamente puedes hacer programación en groovy y hacer lo que quieras, pero no es fácil y precisamente esta versatilidad, creo que es también un problema. ¿Por qué?. En maven, si quieres hacer un plugin, hay unas reglas muy definidas, te haces una clase que debe heredar de AbstractMojo y hacer lo que ahí diga, con las reglas que definen esa clase abstracta. En gradle puedes hacer lo que quieras como quieras. En maven quizás te limita, en gradle tienes libertad de acción

¿Cual es el problema? Pues principalmente entender su configuración si eres simplemente un usuario del plugin o su forma de funcionar. En maven las configuraciones de los plugin se escriben siempre igual, puedes no entender qué significan algunos valores de la configuración o incluso si funcionan correctamente, pero sabes donde ponerlos. En gradle es poco menos que imposible saberlo si no tienes una buena documentación, que no siempre es así.

Aparte, en gradle, posibles efectos secundarios con otros plugins porque el desarrollador del otro plugin puede haber hecho algo que interfiera con el tuyo o el tuyo con el de él, mantenimiento del plugin según avanza gradle, porque igual lo que usa el plugin de gradle luego no está o cambia, etc, etc.

Un ejemplo concreto, hace poco me puse a mirar cómo ofuscar código desde maven/gradle. Lo intenté con Proguard.

¿Mi experiencia con proguard y maven?. Correcta. Busqué el maven-gradle-plugin en google, tuve que aprender algo de proguard para saber qué ofuscar, que no ofuscar y cómo, hice mis experimentos y funcionó correctamente sin problemas. No tuve que aprender cómo meter el plugin en maven, sólo como configurarlo siguiendo los pasos de maven para configuración de plugins.

¿Mi experiencia con gradle?. Bueno, seguramente la culpa no es de gradle, sino del plugin. La página de Proguard tiene un plugin para gradle, así que ese usé. La primera pega, la versión del plugin que dice dicha página (7.0.0) no está en los repositorios, tuve que buscar en mvnrepository la más moderna.

Bueno, eso no es importante. Siguiente problema, un mensaje de error como este «ERROR: Output jar must be specified after an input jar or it will be empty» realmente quiere decir que no encuentra el input jar. Una horita de google para darse cuenta de que habia puesto mal el path del jar que quiero ofuscar.

Y luego el tercer problema, donde ya desistí. proguard necesita de alguna forma ver las dependencias externas que tiene tu proyecto. Si tu proeycto necesita log4j, apache-commons, etc, necesita verlas para hacer bien su trabajo. En maven no tuve que hacer absolutamente nada. En gradle me da el error de que nos las encuentra y tienes que «currarte» el ponérselas accesibles. Googleando, no hay documentación a este respecto y los posts que he visto donde intentan resolver este problema hablan de hacerse un task de gradle que se baje las dependencias en un directorio y luego decirle al plugin de proguard que las busque ahí.

Bien, lo dicho, seguramente no es culpa de gradle, sino de la gente de proguard con su plugin, igual la versión antigua porque la 7.0.0 no está, igual mi versión de gradle, no lo sé. Pero con maven fue un rato tenerlo en marcha y con gradle veo que voy a tener que echar un buen rato.

Así que, por mi parte, seguiré usando maven en proyectos más o menos serios/grandes/con necesidad de plugins y dejaré gradle para mis pequeños programas de prueba o donde no prevea la utilización de las cosas que son más o menos estándar en gradle.

maven-versions-plugin

Tenemos proyectos maven gigantes, con subproyectos que a su vez tienen más subproyectos. Estos a su vez tienen dependencias de otros proyectos nuestros maven también gigantes, con subproyectos y más subproyectos y estos a su vez … Con todo esto, el control de las versiones de nuestros jar es un pequeño infierno. Son muchos jar y muchos <dependency> esparcidos por los pom.xml de muchos proyectos. Actualizar una versión de un jar requiere ir recorriendo todos los pom.xml buscando <dependency> de ese jar y actualizar o no a la nueva versión.

Con la intención de ayudarnos a cambiar todos esos números, hice hace mucho tiempo un plugin de cambio de versiones. Aunque mi intención era buena y el plugin se usó algunas veces, la realidad cae por su propio peso. El plugin ahorraba trabajo, pero seguía siendo un poco engorroso de usar y dejó de usarse.

La siguiente aproximación fue poner en el pom.xml padre (de esos tenemos sólo tres o cuatro) unas properties con la versión deseada de cada jar. Tanto en la definición de la versión del proyecto como en las dependencias usábamos la property correspondiente. Nuevamente un fracaso, podemos tener cerca de un centenar de jars y un centenar de properties en los pom.xml de más alto nivel son muchas properties.

Así que actualmente nuestra versión de desarrollo es permanentemente 1.0-SNAPSHOT. Cuando hacemos una entrega a cliente, creamos una rama SVN y en esa rama, con un script, modificamos todos los 1.0-SNAPSHOT de todos los pom.xml por el nuevo número de versión para ese cliente.

La aproximación que quiero intentar ahora es una sola versión para todos los jar de un proyecto maven y subproyectos. Cuando se cambia la versión, se cambia para todos los jar de ese proyecto maven. En una primera intentona, en el proyecto padre, definí una property con la versión, estilo <proyecto.version>2.1-SNAPSHOT</proyecto.version>. y en todos los pom.xml, tanto en los <parent> como en los <version> de definición del jar puse ${proyecto.version}. Por ejemplo, en el pom.xml padre quedaría así

<project>
   <properties>
      <proyecto.version>2.1-SNAPSHOT</proyecto.version>
   </properties>
   <groupId>com.chuidiang</groupId>
   <artifactId>miproyecto</artifactId>
   <version>${proyecto.version}</version>
   …

 

pero empezamos con los problemas. Según maven, sí podemos poner variables ${variable} en las <version> de los <dependency>, pero no en los <version> de definición de proyecto o <parent>. Curiosamente maven no debe tener esto muy bien implementado y si lo haces, no protesta si compilas desde el proyecto raíz, pero sí lo hace si compilas desde los subproyectos.

Así que dejamos estas versiones de nuevo "a pelo", sin variables. Eso sí, definimos las properties para las dependencias de los otros proyectos. Como sólo tenemos tres o cuatro proyectos gordos de maven, sólo son dos o tres properties como mucho (bueno, alguna más hay). Así que nos queda esto

<project>
   <properties>
      <proyecto1.version>2.1-SNAPSHOT</proyecto.version>
      <proyecto2.version>3.4</proyecto.version>
   </properties>
   <groupId>com.chuidiang</groupId>
   <artifactId>miproyecto</artifactId>
   <version>1.1-SNAPSHOT</version>
   <dependencies>
      <dependency>
         ….
         <version>${proyecto1.version}

 

Hay un detalle importante a tener en cuenta. Al pom.xml padre le hemos puesto una versión. Si a los pom.xml de los subproyectos NO les ponemos versión, heredan la del padre. De esta forma, cuando queremos cambiar una versión, vamos al pom.xml padre y cambiamos el número de versión, cambiando así automáticamente para todos los subproyectos. Luego hay que ir a los otros tres o cuatro proyectos gordos y en el pom.xml padre cambiamos la <property> correspondiente.

¿Cual es la pega? Que todos los subproyectos de un proyecto, en su tag <parent>, llevan la versión del padre puesta "a pelo". Hay que recorrer todos los subproyectos buscando esa <version> de <parent> y cambiarla.  Aquí es donde entra el maven-versions-plugin. Aparte de un montón de opciones interesantes para analizar y cambiar versiones en los pom.xml, el comando

mvn versions:update-child-modules

cambia la versión de <parent> de todos los submodulos para que coincida con la del padre. Es decir, nos basta cambiar la versión del pom.xml raíz y luego ejecutar este comando para actualizar todos los submodulos.

Y aunque no es lo mejor, ya que no tenemos una versión independiente para cada jar, al menos nos permite versionar nuestros jar de forma sencilla.

Aprovecho para comentar algunos comandos útiles de este maven-versions-plugin.

  • mvn versions:display-*-updates muestra un listado de aquellos jar de los que dependemos que tienen versiones más modernas disponibles.
  • mvn versions:use-next-* modifica los pom.xml para que usemos la siguiente version disponible de los jar en los que no estamos usando la última disponible.
  • mvn versions:use-latest-* idem al anterior, pero nos pone la última versión disponible.
  • mvn version:lock-snapshot cambia todos los -SNAPSHOT por una -FechaHoraConcreta. Esto es muy útil para hacer "semi congelaciones" de código. Si tu proyecto va a pasar una prueba no muy importante (digamos una demo) y no quieres que te afecten cambios que otros estén haciendo en las librerías comunes, usas este comando y así tienes "congeladas" las librerías comunes hasta que pase tu demo.

En fin, un plugin que aunque todavía no he usado mucho, promete ser interesante.

 

 

Amago de pasar a maven 3.0.4

Hace tiempo que está maven 3.x disponible para usar y hace tiempo que tengo pendiente probarlo, sigo con mi maven 2.2.1. Hoy, como tenía un rato y estaba investigando algunas cosillas de maven, decidí bajarme y probar con la 3.0.4

Me puse con un proyecto relativamente sencillo, no es más que un jar con un main que hace consultas a unos web services (usando apache CXF) y mete los resultados en una base de datos (con Hibernate y PostgreSQL). Por supuesto, tiene un assembly para generar un zip con todas las dependencias y poder instalarlo fácilmente en el servidor de producción.

Pues nada, pruebo con maven 3.0.4 y lo primero que veo es que saca más log y es más "protestón". Donde maven 2.2.1 hace y no dice ni mú, este saca warnings de cosas que tienes mal y deberías arreglar. Le voy haciendo caso, puesto que es más sabio que yo y hay que hacerle caso. Al final consigo compilar todo sin warnings con maven 3.0.4. Genero mi zip assembly, me lo llevo al servidor de pruebas, lo desempaqueto, lo arranco y …. "NoDefClassFound".

Horror. Búsqueda de la clase pérdida, está en un jar stax2-api, necesitado por un woodstox-core-asl, que a su vez es necesitado por una librería de CXF. Curioso, ese jar parece estar dentro del zip en su sitio correcto. Reviso el fichero de manifiesto del jar con el main y la dependencia está. La clase perdida efectivamente está dentro del jar. ¡Qué cosa más rara!. Un rato mirando y remirando… hasta que me doy cuenta que la versión del stax2-api no es la misma en el fichero de manifiesto que la del jar que hay en el assembly. Una es la 3.0.5 y la otra la 3.1.1.

A revisar. Pues no, el pom.xml está bien, de hecho,  no referencia directamente esa librería para nada. mvn dependency:tree me dice que necesito la 3.0.5. El fichero de manfiesto generado con el maven-jar-plugin añade al Class-Path la 3.0.5, pero el maven assembly mete la 3.1.1.

Vuelta a maven 2.2.1 (sólo cambiar el path donde se busca maven) y listo, arreglado, ya funciona bien y no hay incoherencia de versiones. Así que nada, me sigo quedando con 2.2.1. O bien el plugin assembly no es muy compatible con maven 3.0.4, o bien tengo alguna cosa rara en algún sitio que afecta sólo a esa versión nueva de maven y no a la vieja.

Maven y CXF: Generar el ciente de web service a partir del WSDL, eligiendo el nombre del paquete

A partir de los WSDL y con CXF podemos generar el código de nuestro cliente de web service. Por defecto, la herramienta wsdl2java de CXF pondrá a estas clases cliente un paquete elegido a partir del namespace que aparece en el wsdl

Puede no interesarnos esto, sino que quizás queramos poner nuestro propio paquete para esas clases. Desde maven y CXF podemos hacerlo de la siguiente forma

<build>
   <plugins>
      <plugin>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-codegen-plugin</artifactId>
         <version>2.4.2</version>
         <executions>
            <execution>
               <id>generate-sources</id>
               <phase>generate-sources</phase>
               <configuration>
                 <sourceRoot>${basedir}/src/main/java</sourceRoot>
                 <wsdlRoot>${basedir}/src/main/resources/wsdl</wsdlRoot>
                 <wsdlOptions>
                    <wsdlOption>
                       <wsdl>${basedir}/src/main/resources/wsdl/UnFichero.wsdl</wsdl>
                       <wsdlLocation>classpath:/wsdl/UnFichero.wsdl</wsdlLocation>
                       <extraargs>
                          <extraarg>-p</extraarg>
                          <extraarg>mi.propio.paquete</extraarg>
                      </extraargs>
                   </wsdlOption>
 ….

 

Más o menos es lo estándar de maven y CXF para generar el cliente. Suponemos que el wsdl estará dentro del jar, por eso lo hemos puesto con wsdlRoot src/main/resources y con wsdlLocation en el classpath.

El "truco" para conseguir el paquete propio son los <extraargs>, que son -p y el nombre del paquete que queremos. Estas son opciones del comando wsdl2java que viene con apache CXF. Esto es válido para cualquier parámetro que admita el comando wsdl2java y que no esté soportado directamente por el plugin de maven
 

 

Jugando con Proguard

Candado de código ofuscado Estos días estoy jugando con Proguard, una herramienta que coge nuestro jar en java y realiza básicamente tres tareas: ofuscarlo, optimizarlo y eliminar sobrantes. Por supuesto, estas tareas son independientes y podemos realizar unas sí y otras no a nuestro gusto. Tiene plugin para maven, por lo que si usamos maven, podemos realizar cualquiera de estas tres tareas automáticamente al generar nuestro jar.

Ofuscado

La parte de ofuscado es sencilla, símplemente coge los paquetes, las clases y los métodos y les cambia el nombre por a, b, c, etc. Así, en vez de persona.setId(7), si descompilamos veríamos a.b(7). Es decir, se puede seguir descompilando pero el código es mucho menos claro.

Tiene opciones para decirle qué clases o métodos no debe ofuscar. Son candidatos claros a no ofuscar el método main() de la clase principal e incluso el nombre de esa clase, para que luego la máquina virtual java sepa qué debe ejecutar. Los métodos write() y read(9 de serialización, etc. También, en el caso de que estemos intentando ofuscar una librería, se deben no ofuscar las clases que se usen desde el programa principal.

Curiosamente, proguard es bastante inteligente y tiene en cuenta la relexión de java. Si ve que usamos cosas como Class.forname(), o Class.getDeclaredMethod()…., nos avisa con un error si intentamos ofuscar las clases a las que hace referencia esa reflexión.

Optimizado

Con esto no me he metido a fondo, porque no hay muchas posibilidades de ver qué es lo que hace. Entiendo que limpia nuestro código ineficiente, borrando variables locales no usadas o arreglando cualquier cosa que tenga que ver con la efectividad de nuestro código.

Eliminar sobrantes

Esta es la funcionalidad que no esperaba de la herramienta y que más me ha llamado la atención. Elimina de nuestro jar todas las clases que no usamos y borra todos los métodos que no se usan en el resto de clases. ¿Y cómo sabe si lo usamos o no?. Pues por la clase que le hemos dicho que contiene el main(). Empieza a tirar de ahí y borra todo lo que no se use. El resultado es que en muchas ocasiones obtendremos un jar mucho más pequeño si llevamos tiempo trabajando en el proyecto y somos reacios a borrar clases y métodos que no se usan "por si acaso me hacen falta más adelante".

Y ya lo máximo, al integralo en maven, el proceso queda totalmente automático, por lo que una vez configurado en nuestro pom.xml, podemos olvidarnos de que usamos esa herramienta y seguir con nuestros comandos de maven típico mvn package, mvn install, mvn deploy, etc. Eso sí, ojo al hacer el javadoc.

jax-ws y maven

 Siguiendo con los web services y yo, he estado probando herramientas como axis2 y jax-ws. Por supuesto, nada me ha funcionado a la primera y llevo casi dos días peleándome con esto para arrancar un "hola mundo"

Con axis2 el problema es que se me ocurrió hacerme mi propia clase de ejemplo, sin copiar la de los tutoriales. Al final descubro que no soporta enumerados de java y qué casualidad, a mí se me había ocurrido poner uno de los métodos con un parámetro enumerado. Una vez conseguido arrancar un servidor y hacerme un cliente, el servidor me da error cuando intento acceder al web service desde el ciente. Ante las pocas pistas que daba el error, decidí dejar axis-2 de momento y probar con jax-ws.

Con jax-ws me cree un proyecto maven y me puse a ello. Pues bien, las dependencias maven teóricas según la documentación son estas https://jax-ws.dev.java.net/guide/Using_JAX_WS_from_Maven.html . Creo el proyecto, arranco el servidor y todo aparentemente correcto. Accedo desde un navegador a http://localhost:8080/MiServicio?WDSL con el que teóricamente debería obtener el fichero WSDL del servicio… y el servidor da error. Buscando el error por google, me encuentro con esto http://forums.java.net/jive/message.jspa?messageID=222799 Parece que la librería sjsxp de la que depende según maven el jax-ws no es correcta y hay que coger la versión 1.0.1 en vez de la 1.0. Así que me toca "tunear" el pom.xml y hacer esta ñapa

 

<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
<version>1.0.1</version>
</dependency>
Dicho de otra forma, poner la dependencia de jaxws-rt, decirle que no me traiga su sjsxp y luego poner la dependencia del que yo quiero. Esto ha funcionado bien y ya tengo el servidor arrancado. Ahora toca pelearse con el cliente…. espero no tener demasiados problemas. Luego volveré con axis2.
 
Y aunque todavía tengo que probar más, de momento me gusta mucho más jax-ws que axis2. Los scripts de axis2 generan montones de clases que luego tampoco facilitan tanto la creación de un cliente. jax-ws también genera clases, pero parece que son menos y además quedan "ocultas", ya que genera los .class que luego se empaquetan en el war o jar.
 
Lo segundo que no me ha gustado de axis2 es que aparentemente no tiene posibilidad de hacer un main() que arranque un servidor web con los webservices, dependemos de un Tomcat ajeno con una webapp axis2 instalada sobre la que desplegamos nuestros web services. Bueno, sí tiene un servidor, pero según la documentación puede usarse sin garantías, ya que no está soportado. jax-ws permite hacerse un main y publicar el servicio de una forma tan fácil como esta
public static void main(String [] args) {
   Endpoint.publish("http//localhost:8080/MiServicio", new MiWebService());
   // Aqui puedes seguir haciendo cosas.
}
Bueno, no es nada grave si quieres publicar webservices en internet. Pero en mi caso, necesito que el servidor aparte de publicar los webservices, haga más cosas.

Pros y contras de maven

 Llevamos ya varios años usando maven y nos hemos acostumbrado a él. Recordamos ahora cómo teníamos antes los proyectos y nos asombramos de la mejora conseguida. Sin embargo, no todo es bueno con maven, tiene sus pegas.

Ventajas de maven

Hay principalmente tres grandes cosas que hemos conseguido con maven y sin las que ahora no seríamos capaces de trabajar.

La primera es que ahora todos nuestros proyectos están organizados igual. Esto, por supuesto, puede conseguirse con disciplina y sin necesidad de maven, pero en grupos numerosos de desarrolladores es más fácil de conseguir si la herramienta te obliga a seguir una estructura o al menos, si te obliga a hacer un esfuerzo importante si te quieres salir de esa estructura. Se acabó el  hacer scripts de compilado, el preguntar a otro cuando cambias de proyecto dónde están las cosas, el dejar ficheros o iconos por cualquier lado. Ahora cualquiera puede pasar de un proyecto a otro y sabe manejarse por la estructura sin ningún problema.

La segunda gran ventaja son los jar. Al dejar todos los jar de los proyectos en un repositorio centralizado (usamos nexus) y usar versiones SNAPSHOT de maven, todos tenemos siempre disponibles los últimos jars. Antes era necesario que cada uno compilase los jar que  necesitara o se los pidiera a alguien que los tuviera, o que alguien se acordara de meterlos en el sistema de control de versiones. Eso ya no es necesario, maven/nexus se encarga de que todos tengamos siempre los últimos jars.

Y la tercera gran ventaja es la herramienta de integración continua (Hudson), que todas las noches saca los fuentes del sistema de control de versiones, los compila y pone los jars en nexus. Al ser hudson el encargado de meter los jar en nexus, estos siempre están actualizados y siempre está disponible la última versión para todo el mundo. Y al ser hudson el que compila en un servidor separado, eliminamos por un lado los típicos errores de código que sólo compila en el PC del desarrollador Fulanito porque inadvertidamente ha puesto un path suyo local en algún sitio, o se ha olvidado de meter algo en el sistema de control de versiones. Este Hudson nos sirve además para obtener de él las versiones de instalación tanto para el entorno de producción como para pruebas. Ya no dependemos de que alguien tenga todo lo necesario para generar estas versiones y de que ese alguien esté.

Pegas con maven

Pero no todo son ventajas. La gran y enorme pega de maven es su dependencia de que haya internet o al menos red, para acceder al servidor de Hudson. Los problemas con la red suelen ser relativamente frecuentes: se va el servidor de nexus por el motivo que sea, se cae algún proxy o router, etc, etc. En esos casos, se nos queda un poco parado el tema de compilado. Sí, maven tiene una ejecución off-line, pero no funciona todo lo bien que debiera. Si le falta algo, se empeña en ir a buscarlo a través de la red.

Y esta dependencia de la red se nos hace especialmente grave cuando vamos a instalaciones del cliente en los que no hay internet ni, por supuesto, acceso a nuestro nexus. Si queremos llevarnos un entorno de desarrollo para depurar, corregir algún bug y compilar en las instalaciones del cliente, nos obliga a llevarnos toda una copia de repositorios, o montar el proyecto de forma totalmente independiente de maven. Hay comandos de maven que ayudan a hacer toda esta copia, como dependency:go-offline, pero hay que acordarse de hacerlo.

Es bastante molesto también el tema de plugins de maven. A veces y sin saber el motivo, maven decide que debe actualizar plugins a versiones más modernas. En la mayoría de los casos esto no afecta demasiado, pero a veces si coincide con algún tipo de problema de red o el estar off-line, nos hace imposible compilar.

En fin, desde luego las ventajas compensan con creces los inconvenientes y ni se nos pasa por la cabeza dejar de usar maven, pero a veces algunos problemas misteriosos pueden tenerte de pelea con ellos toda una mañana.

Rangos de dependencias con maven

 En maven todos estamos acostumbrados a poner las dependencias y en concreto, a poner la versión concreta que queremos de la dependencia. Por ejemplo, si nuestro proyecto depende de log4j, solemos poner algo como esto

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.13</version>
</dependency>

es decir, ponemos la versión concreta que deseamos de log4j, en esta caso, la 1.2.13

Pues bien, es menos conocido, quizás porque la documentación de maven es algo escasa, pero podemos indicar a maven un rango de dependencias, de forma que maven traerá la más moderna disponible. Para ello se utiliza la notación matemática para rangos de valores, donde se abre paréntesis o corchete, se pone el número de versión inicial, coma, el número de versión final y se cierra paréntesis o corchete. Un corchete indica que la versión inicial o final es válida, mientras que un paréntesis indica que no es válida. Dejar en blanco uno de los números de versión indica que no hay límite por ese lado. Así, por ejemplo

[1.2, 1.5)     Cualquier versión entre la 1.2 y la 1.5, incluyendo la 1.2 (corchete al principio), pero excluyendo la 1.5 (paréntesis al final)

[1.3, )           Versión 1.3 o superior, incluyendo la 1.3

[1.0, 2.0)     Cualquier versión 1.x.x, pero inferior a la 2.0

En realidad maven admite hasta tres números en la versión más un "cualificador". El cualificador es un nombre cualquiera que se pone detrás del número de versión separado por un guión. Por ejemplo, imagina que en nuestro proyecto entregamos una versión al cliente FEDERICO y creamos una versión congelada para ese cliente. Su número de versión podría ser 1.2.3-FEDERICO. O más común, si es una "Release Candidate", le ponemos 1.2.3-rc, o si es una beta, pues 1.2.3-beta.

No lo he probado, pero imagino que esto permite poner cosas como

[1.2, )-FEDERICO

de forma que maven traerá cualquier versión 1.2 o superior que tenga el cualificador FEDERICO, es decir, que sea del cliente FEDERICO.

Considero esto realmente interesante, porque en un proyecto grande en el que hay muchos jar desarrollados por la gente de nuestro equipo y en los que cada jar tiene sus responsables, cada responsable puede subir su número de versión cuando lo considere adecuado y los demás traerán automáticamente o no esa versión según lo que pongan en las dependencias. Si yo no quiero traerme los cambios que haga Juan en su jar, símplemente pongo que quiero la versión 1.4. Pero si me interesa traerme siempre su versión más moderna, entonces pongo [1.0, ).

Aunque supongo que es bastante conocido, aprovecho para poner aquí cual es el significado de esos tres números de versión. Si la versión es a.b.c, los números se incrementan normalmente siguiendo los siguientes criterios:

  • a se suele incrementar cuando el cambio es tan importante que hace la nueva versión incompatible con las anteriores. Si guardas datos con una versión 2.0 de un programa, posiblemente no puedas leer esos datos con la versión 1.0 del mismo programa (normalmente al revés si suele ser posible).
  • b se suele cambiar cuando no hay pérdida de compatibilidad, pero sí se han añadido nuevas funcionalidades. Por ejemplo, las versiones 1.3 y 1.4 pueden usar los mismos datos guardados indistintamente, pero la 1.4 permite imprimirlos mientras que la 1.3 no.
  • c se suele cambiar cuando se han arreglado errores respecto a la anterior. Por ejemplo, la 1.2.3 puede caerse cuando el usuario pulsa el botón A, luego minimiza la ventana y le da a la tecla de tabulador. La 1.2.4 ya no tiene ese error.

Por supuesto, esto es una convención más o menos aceptada, pero desde luego no es obligatoria.

 

javadoc con UML

Hurgando por ahí me he encontrado con UMLGraph, una herramienta que genera gráficos UML a partir de unos textos que los describen. Lo bueno es que, al menos para el diagrama de clases, el texto que describe el diagrama UML es exactamente igual que un fuente java. Dicho de otra forma, si ponemos en el fichero "class UnaClase extends UnPadre {}", obtendremos un diagrama con una cajita UnaClase que hereda (flechita con triángulo) de una cajita UnPadre. Puedes ver el ejemplo aquí. Esto lo hace ideal para generar los gráficos UML de clases a partir de código fuente ya hecho.

Otro punto interesante es que viene con la posibilidad de invocar javadoc usando UMLGraph, de forma que en nuestro javadoc se generaría diagramas de clases incrustados. UMLGraph viene con un doclet que se puede usar desde el comando javadoc de java y de esta forma, javadoc generará la documentación de la forma habitual, pero incluyendo un gráfico de UML. En la descripción de un package, pondrá todas las clase incluidas en ese package y las relaciones entre ellas. En la descripción de una clase pondrá un dibujo de dicha clase con las relaciones (herencias, dependencias, etc) con otras clases del paquete o de otros paquetes. En la figura puedes ver un ejemplo de javadoc generado con el doclet de UMLGraph.

javadoc con grafico UML

Un detalle a tener en cuenta es que para que UMLGraph pueda generar los ficheros gráficos es necesario tener instalado previamente GraphViz.

En el diagrama de clases podrían configurarse muchas cosas, poner notas asociadas a las clases, poner otro tipo de cajas que no sean de clases, métodos que deben o no mostrarse, etc. La pega de ello es que iría configurado en código a base de anotaciones, por lo que el código quedaría algo "guarreado" para luego ver el dibujo bonito.

También pueden hacerse diagramas de secuencia, pero desgraciadamente la sintaxis del fichero de texto que lo describe ya no es java, así que no deja de ser una forma alternativa de hacer el diagrama. Puede ser interesante, por ejemplo, si guardamos los diagramas de secuencia en un sistema de control de versiones (como subversion). Siempre ocupa menos y es más interesante para ver diferencias con versiones anteriores un fichero de texto que no un gráfico o un proyecto entero de alguna herramienta compleja de generación de gráficos UML (Together, Rational,…).

Y otra cosa que a mí siempre me viene bien, es que UMLGraph está subido al repositorio ibiblio de maven y tiene plugin para el mismo. De esta forma, configurando el fichero pom.xml de nuestro proyecto maven (en concreto, configurando el plugin de javadoc para que use el doclet de UMLGraph), podemos generar el javadoc con gráficos UML directamente desde maven. La configuración sería algo parecido a esto

<reporting>
   <plugins>
      <plugin>
         <artifactId>maven-javadoc-plugin</artifactId>
         <configuration>
            <source>1.5</source>
            <aggregate>true</aggregate>
            <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>

Con esto, un simple mvn javadoc:javadoc nos generaría la documentación javadoc de nuestro proyecto maven, gráficos UML incluidos.