Maven Assembly Plugin: empaquetando aplicaciones con Maven para la ejecución de procesos batch.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Configuración del módulo batch.
- 4. El resultado del empaquetado.
- 5. Los scripts de los procesos batch.
- 6. Conclusiones.
1. Introducción
Ya hemos escrito en adictos sobre Empaquetamiento de
aplicaciones de escritorio (standalone) con Maven y, en este tutorial, vamos a hablar sobre el mismo plugin pero orientado al empaquetamiento de un módulo de
nuestra aplicación para la ejecución de procesos batch, o la ejecución de procesos puntuales, de migración de datos, que requieren tener montado todo el entorno de la capa
de servicios de nuestra aplicación.
Estamos hablando de aplicaciones JEE, lo suficientemente bien modularizadas y la necesidad consiste en ejecutar una aplicación standalone, que tenga accesibles
los mismos servicios que tendría el módulo web.
Para dar soporte a esos procesos batch o de migración, vamos a crear un módulo específico dentro de nuestra estructura de aplicación, al que llamaremos app-batch,
y que, como el resto de módulos, dependerá de un módulo parent.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil Asus G1 (Core 2 Duo a 2.1 GHz, 2048 MB RAM, 120 GB HD).
- Sistema operativo: Windows Vista Ultimate.
- JDK 1.5.0_15
- Eclipse 3.5, con IAM.
3. Configuración del módulo batch.
He aquí el pom.xml del módulo app-batch, está comentado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
... <parent> <groupId>com.autentia.training.maven</groupId> <artifactId>appp-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <groupId>com.autentia.training.maven</groupId> <artifactId>app-batch</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> ... <dependencies> <!-- incluimos una dependencia del módulo core de nuestra aplicación, aquél que contiene la capa de servicios, de manera que cuando empaquetemos el módulo batch arrastre dicha dependencia, así como las transitivas del mismo --> <dependency> <groupId>com.autentia.training.maven</groupId> <artifactId>app-core</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- si el módulo core tiene acceso a base de datos, lo normal es que el driver esté incluido a nivel de servidor de aplicaciones, aquí estamos hablando de un proceso standalone con lo que necesitaremos incluirlo en el empaquetado, o documentarlo para que añada en función del entorno --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.0.5</version> </dependency> </dependencies> ... <build> <plugins> <!-- configuración del plugin de empaquetado --> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <!-- ruta al descriptor del empaquetado: por convención se incluye en el directorio assembly --> <descriptor>src/main/assembly/installer.xml</descriptor> </descriptors> </configuration> <!-- enganchamos la ejecución del plugin al ciclo de vida de maven, en concreto a la fase de instalación de este modo al instalar el artefacto generará nuestro distribuible del módulo batch --> <executions> <execution> <id>package-native</id> <phase>install</phase> <goals> <goal>attached</goal> </goals> </execution> </executions> </plugin> </plugins> </build> |
A continuación el contenido del descriptor de empaquetado src/main/assembly/installer.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
<?xml version="1.0" encoding="UTF-8" ?> <assembly> <id>installer</id> <formats> <!-- formato de salida del empaquetado --> <format>zip</format> </formats> <!-- no queremos que el empaquetado tenga como raíz un directorio con el nombre del módulo batch --> <includeBaseDirectory>false</includeBaseDirectory> <!-- las dependencias del módulo (las librerías) se incluirán en un directorio /lib--> <dependencySets> <dependencySet> <outputDirectory>/lib</outputDirectory> <scope>runtime</scope> </dependencySet> </dependencySets> <!-- además el contenido del zip incluirá los siguientes directorios --> <fileSets> <!-- irá al directorio lib el contenido del directorio target del módulo batch --> <fileSet> <directory>target</directory> <outputDirectory>lib</outputDirectory> <!-- a exclusión del empaquetado del javadoc y los fuentes --> <excludes> <exclude>app-batch-*-source.jar</exclude> <exclude>app-batch-*-javadoc.jar</exclude> </excludes> <!-- se incluirá la propia librería del módulo batch --> <includes> <include>app-batch-*.jar</include> </includes> </fileSet> <!-- se creará un directorio doc en el que se incluirá el site del módulo batch --> <fileSet> <directory>target/site</directory> <outputDirectory>doc</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <!-- los ficheros de configuración de la aplicación deberán estar disponibles también en el entorno del módulo batch, aunque quizás no se requiera la configuración de todas sus propiedades --> <fileSet> <directory>src/main/config</directory> <outputDirectory>/config</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <!-- para la ejecución de los procesos batch haremos uso de unos ejecutables que invoquen a los métodos main de las clases que implementen los procesos --> <fileSet> <directory>src/main/bin</directory> <outputDirectory>/bin</outputDirectory> <lineEnding>dos</lineEnding> <includes> <include>**/*.cmd</include> </includes> </fileSet> <!-- al distribuir multiplataforma debemos tener en cuenta que el entorno puede ser windows o unix. Los sctips para ambos serán empaquetados bajo el directorio bin. --> <fileSet> <directory>src/main/bin</directory> <outputDirectory>/bin</outputDirectory> <lineEnding>unix</lineEnding> <fileMode>0755</fileMode> <includes> <include>**/*.sh</include> </includes> </fileSet> <!-- crearemos un directorio de salida para los logs de los procesos --> <fileSet> <directory>src/main/logs</directory> <outputDirectory>/logs</outputDirectory> </fileSet> </fileSets> </assembly> |
Con todo lo configurado necesitaremos mantener una estructura de directorios en el módulo batch similar a la que sigue:
Nuestra arquitectura de servicios está basada en Spring y JPA, de ahí la existencia de los ficheros de configuración de ambos frameworks.
4. El resultado del empaquetado.
Si invocamos al goal install de maven, en el directorio target del módulo batch encontraremos un fichero zip, con el nombre del módulo más el sufijo del identificador del assembly, en nuestro caso: app-batch-1.0-SNAPSHOT-installer.zip.
Dicho fichero comprimido es nuestra empaquetación del módulo batch, con todo lo necesario para ejecutar los procesos que hayamos programado. No tenemos más que descomprimir el zip en la máquina en la que queremos ejecutarlo, configurar los ficheros de entorno (acceso a la base de datos, propiedades individuales de la aplicación que ya tendremos configuradas en el módulo web,...) e invocar a los ejecutables de cada uno de los procesos.
5. Los scripts de los procesos batch.
Los ejecutables de los procesos batch simplemente invocarán a los métodos main de las clases que los implementen, pasándoles los parámetros que requieran por línea de comandos.
En este punto tenemos que resolver dos cuestiones:
- la carga dentro del classpath de ejecución de la máquina virtual de las librerías que han sido empaquetadas, para que todas estén disponibles en tiempo de ejecución,
- el paso de parámetros desde el script de ejecución de los procesos al método main de la clase que lo implementa
Estas cuestiones tienen diferentes soluciones en función del entorno.
5.1. UNIX.
El contenido del ejecutable de los procesos tendrá el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/sh PRGDIR=`dirname "$0"` # -- Prepare CLASSPATH ------------------------------------------------------- LOCALCLASSPATH=$CLASSPATH:$PRGDIR/../config for i in $PRGDIR/../lib/*.jar do LOCALCLASSPATH=$LOCALCLASSPATH:"$i" done # -- Invocation to client ------------------------------------------------------------ java -cp $LOCALCLASSPATH com.autentia.training.maven.client.TestClient $@ |
- la primera cuestión la resolvemos con un bucle, en el que vamos añadiendo a una variable local cada uno de los jar que distribuimos en el directorio lib, una línea más arriba ya hemos incluido el contenido del directorio config. Con -cp añadimos a la ejecución de la máquina virtual todo lo incluido en la variable local.
- con $@ resolvemos la cuestión del paso de parámetros del script a la clase.
5.2. Windows.
El contenido del ejecutable de los procesos tendrá el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@echo off setlocal rem -- Check existence of JVM -------------------------------------------------- java -version >nul 2>&1 if errorlevel 1 goto :NO_JAVA rem -- Prepare CLASSPATH ------------------------------------------------------- call "%~dp0cp\atcp.cmd" %~dp0..\config for %%1 in ("%~dp0..\lib\*.jar") do call "%~dp0cp\atcp.cmd" %%1 rem -- Invocation to client ------------------------------------------------------------ echo. java -Xms256m -Xmx512m -XX:MaxPermSize=128m -cp "%CLASSPATH%"; com.autentia.training.maven.client.TestClient %* goto :END :NO_JAVA echo Java Virtual Machine not found, please add JRE's bin directory to PATH environment variable goto :END :END endlocal |
- para resolver la primera cuestión volvemos a necesitar un bucle, pero ahora nos tenemos que apoyar en un segundo script atcp.cmd que vaya concatenando a la variable %CLASSPATH% una a una, la librería que itera. Con -cp añadimos a la ejecución de la máquina virtual todo lo incluido en la variable %CLASSPATH%.
- con %* resolvemos la cuestión del paso de parámetros del script a la clase.
El contenido del script atcp.cmd, es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 |
@echo off set ARG=%1 shift :APPEND set ARG=%ARG% %1 shift if not "%1"=="" goto :APPEND @set CLASSPATH=%CLASSPATH%;%ARG% |
6. Conclusiones.
Seguimos haciendo uso extensivo de maven para la gestión del ciclo de vida de nuestros proyectos y, aunque la primera intención pueda ser la de realizar este tipo de tareas manualmente, puesto que quizás sean puntuales o pensemos que total, seguro que tardo menos,... la experiencia nos dice que hacer las cosas de una manera controlada, a la larga, es mucho más beneficioso.
Tened en cuenta que si lo hacemos bien, nuestros procesos tendrían que estar documentados en el site, y eso se distribuye también en el zip, con lo que tendríamos cubiertas nuestras necesidades a la hora de ejecutarlos.
En cuanto a los ejecutables, más simple en Unix que en Windows... :-D, pero en ambos entornos "estamos abiertos a extensiones y cerrados a modificaciones" puesto que la inclusión de una librería a nuestra arquitectura o un parámetro adicional a nuestros procesos no implicarán modificación alguna en los scripts.
Un saludo.
Jose