Jul 13

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.

 

 

Jul 10

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.

Mar 27

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
 

 

Oct 20

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.

Sep 29

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.

Jun 19

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.

Mar 04

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.

 

Mar 03

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.

 

Feb 24

OutOfMemoryError con test de maven

 

El otro día me salto un OutOfMemoryError al ejecutarse un test automático desde maven. Teóricamente, para evitar problemas de memoria con maven basta con poner la variable de entorno MAVEN_OPTS con los parámetros que le queremos pasar a la máquina virtual de java, en concreto, los de aumento de memoria

set MAVEN_OPTS=-Xmx512m

De hecho, tengo esa variable puesta por defecto en el entorno y estaba correctamente inicializada. Pero el OutOfMemoryException persiste. Así que a buscar en google.

Al final encuentro que maven arranca una máquina virtual java separada para ejecutar los test y que el parámetro MAVEN_OPTS sólo afecta a la máquina virtual en la que corre maven y no a la máquina virtual en la que se ejecutan los test. El plugin de maven que se encarga de ejecutar los test automáticos se llama maven-surefire-plugin y tiene su propia configuración. La variable argLine permite indicar, entre otras cosas, la cantidad de memoria que queremos que se asigne a la máquina virtual java en la que se ejecutan los test. Para ello, debemos ejecutar así

mvn -DargLine=-Xmx512m test

o bien, configurarlo en el mismo pom.xml del proyecto

<project>
<build>
<plugins>
   …
   <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>               
      <configuration>
         <argLine>-Xmx512m</argLine>
      </configuration>
   </plugin>
 

May 08

Sobre dependencias

 

Un par de cosillas/problemillas que me he encontrado sobre las dependencias de unos jar con otros.

Maven por un lado

Supón que tenemos un proyecto A con maven que tiene a su vez dos subproyectos B y C. Si le decimos a maven a través de los ficheros pom.xml que B necesita de C para compilar y que C necesita de B para compilar, maven, obviamente, protesta. La excusa es que no sabe qué debe compilar primero, ya que el uno depende del otro. Hasta aquí todo parece correcto.

Supongamos ahora la misma estructura de proyectos de antes, pero esta vez decimos que B necesita a C para compilar y que C necesita de B sólo en tiempo de ejecución (runtime). Pues bien, maven protesta igualmente. Sin embargo, esta vez, la excusa ya no vale. Si B necesita de C para compilar y C sólo necesita a B en runtime, entonces está claro que se puede compilar primero C y luego B. Esto ya es algo que puede en un momento dado molestar, aunque quizás no sea muy correcto un proyecto con esta dependencia.

Pero ahora viene lo peor. Si en vez de un proyecto A con subproyectos B y C tenemos directamente dos proyectos maven independientes, B y C, maven teóricamente sería capaz de detectar igualmente las dependencias. En el repositorio de jars que usemos estarán los jar de B y C y sus ficheros pom.xml con las dependencias. Sin embargo, al ser independientes, NO protesta. En principio no podemos llegar a esta situación directamente, ya que si B necesita de C para compilar y C necesita de B para compilar, no podemos compilar ninguno de los dos, ya que en ningún caso estará el jar del otro en el repositorio (ya que no hemos podido compilarlo). Pero sí podemos hacerlo poco a poco, en unos proyectos reales que evolucionan con el tiempo. Podemos empezar creando los proyectos B y C como indpendientes e ir compilándolos. Según avanzan los proyectos, en un momento dado podemos necesitar la dependencia de B con C y la añadimos. Todo bien. Pero según avanzamos más y por descuido, podemos añadir la dependencia de C con B…. y maven no protesta, puesto que encuentra los jar anteriores en el repositorio y no revisa las dependencias entre proyectos independientes.

Usar muchas librerías, por otro lado

Este otro problema es más filosófico que de una herramienta concreta, aunque me he tropezado con él en la realidad. Supón que haces un proyecto java y decides usar librerías de terceros, de esas libres que hay por ahí, por ejemplo, jasperreport, jfreechart, ibatis, hibernate, etc. Digamos, por ejemplo, que decides usar la A y la B. Pues bien, es bastante fácil que ambas necesiten de alguna librería más de terceros, por ejemplo, log4j. Por generalizar, tanto A como B necesitan que nos descarguemos e incluyamos en nuestro proyecto a C. Sin problemas de momento.

Pero, ¿qué pasa si A necesita C-1.0 y B necesita C-2.0 y las versiones 1.0 y 2.0 son incompatibles?. Pues básicamente que la hemos cagado. Para que todo vaya bien, necesitamos las dos versiones de la librería y tendremos problemas a la hora de configurar el classpath, porque muchas clases estarán repetidas en ambas versiones y se encontrará la primera que pongamos en el classpath, que puede ser la que no le gusta a la librería A y que de paso le sienta mal a la B. Y si ponemos sólo una, A no funciona y si ponemos sólo la otra, es B el que protesta.

Desgraciadamente, estas situaciones van siendo cada vez más comunes. Hay librerías java como las apache-commons, log4j, etc que son muy, pero que muy utilizadas por muchísimas librerías de más alto nivel, como hibernate, jfreechart, etc. Todas estas librerías de alto nivel no avanzan a la vez ni se actualizan con la misma frecuencia, por lo que puede ser normal que una necesite log4j-1.2.13 y otra log4j-1.2.15. Con log4j en concreto no hay mucho problema, pero sí hay otras más conflictivas.

A cuento con lo de elegancia o sencillez, este parece un motivo para ir más a la sencillez que a la elegancia. Las liberías de alto nivel que pretendan ser compatibles con otras librerías de alto nivel que no tienen nada que ver ellas, deberían casi casi reinventar la rueda cada vez en vez de usar librerías de más bajo nivel. O quizás estas librerías de muy bajo nivel y mayoritariamente usadas deberían venir prácticamente incluidas en el JDK de java.

No se, veo difícil solución y preveo que a la larga la cosa ira empeorando. De momento es difícil tropezarse con estas situaciones, pero a mí ya me ha ocurrido alguna vez.