Dans le chapitre précédent, nous avons vu comment personnaliser la construction de votre projet Maven avec les plugins. Abordons maintenant la génération des livrables de votre projet.
Je vais vous montrer comment générer, par exemple, un WAR de votre application web ou un ZIP de vos batchs.
Le but est d'obtenir un livrable contenant tout le nécessaire : votre application, bien sûr, mais aussi ses dépendances, les fichiers de configuration...
Dans ce chapitre, je m'appuierai sur des exemples concrets portant sur le projet de gestion de tickets d'incidents vu dans les chapitres précédents.
Générer un WAR de l'application web
Dans ce premier volet, le but est de générer un fichier WAR pour le déploiement de l'application web.
Je ne vais pas me contenter simplement de la génération de base du WAR, réalisée par le plugin maven-war-plugin
.
Je vais étoffer le processus de construction afin :
d'ajouter automatiquement certaines informations du projet dans les JSP ;
de m'assurer qu'aucune version SNAPSHOT ne soit envoyée en production.
Filtrer les ressources de la webapp
Afin d'ajouter automatiquement certaines informations du projet dans les JSP, je vais utiliser le même mécanisme que pour les fichiers de ressource classiques : le filtrage.
Cependant, les JSP ne sont pas des ressources classiques, elles doivent être ajoutées au répertoire WEB-INF
du WAR. Le plugin maven-war-plugin
fait déjà cela et permet également de mettre en place le filtrage de ces web resources.
Je commence donc par ajouter le plugin maven-war-plugin
dans la section <pluginManagement>
du POM parent :
<project>
...
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
</plugin>
...
</plugins>
</pluginManagement>
</build>
...
</project>
Je configure ensuite le plugin maven-war-plugin
dans le module ticket-webapp
afin d'activer le filtrage des web resources suivantes :
jsp/_include/header.jsp
: fragment JSP contenant le header de toutes les pages HTML de l'application web. Je vais y injecter le nom « public » de l'application.jsp/_include/footer.jsp
: fragment JSP contenant le footer de toutes les pages HTML de l'application web. Je vais y injecter notamment le nom de l'organisation et la version de l'application.jsp/about.jsp
: page « À propos » où je vais injecter quelques informations sur le projet (version, date du build...)
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<filtering>true</filtering>
<directory>src/main/webapp</directory>
<includes>
<include>jsp/_include/header.jsp</include>
<include>jsp/_include/footer.jsp</include>
<include>jsp/about.jsp</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
Je complète ensuite le POM du module ticket-webapp
pour ajouter les propriétés utiles pour la suite :
<project>
...
<!-- =============================================================== -->
<!-- Propriétés -->
<!-- =============================================================== -->
<properties>
<!-- Le nom "public" de l'application -->
<application.name>TicketTac</application.name>
<!-- Le format à utiliser pour afficher la date du build -->
<maven.build.timestamp.format>dd/MM/yyyy</maven.build.timestamp.format>
<!-- Propriété servant à contourner le bug du non remplacement
de la propriété maven.build.timestamp lors du filtrage des ressources -->
<buildTimestamp>${maven.build.timestamp}</buildTimestamp>
</properties>
...
</project>
Il ne me reste plus qu'à utiliser les propriétés dans les JSP afin que leurs valeurs soient injectées lors du filtrage par le plugin maven-war-plugin
:
jsp/_include/header.jsp
:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">${application.name}</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="..">Accueil</a></li> <li><a href="../jsp/about.jsp">A propos</a></li> </ul> </div><!--/.nav-collapse --> </div> </nav>
jsp/_include/footer.jsp
:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <footer class="footer"> <div class="container"> <p> ${application.name} - version ${project.version} © <a href="${organization.url}">${organization.name}</a> </p> </div> </footer> <!-- Bootstrap --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
jsp/about.jsp
:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>${application.name} - A propos</title> <!-- Bootstrap --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous" /> <link rel="stylesheet" href="../style/custom.css" /> </head> <body> <%@ include file="_include/header.jsp" %> <div class="container"> <ul> <li>Application : ${application.name}</li> <li>Version : ${project.version}</li> <li>Date du build : ${maven.build.timestamp}</li> </ul> </div> <%@ include file="_include/footer.jsp" %> </body> </html>
Voici ce que donne la page après la construction du projet :
Les vérifications supplémentaires pour la cible « production »
Afin de m'assurer qu'aucune version SNAPSHOT ne soit envoyée en production, je vais encore une fois utiliser le plugin maven-enforcer-plugin.
Cependant, comme cette vérification concerne uniquement les constructions ayant pour cible la production, je vais ajouter l'exécution de ce plugin dans un profil dédié à la cible production. Si je reprends les profils que j'ai créés dans le chapitre précédent, je vais donc ajouter le plugin au build du profil target-prod
:
<project>
...
<!-- =============================================================== -->
<!-- Profils -->
<!-- =============================================================== -->
<profiles>
...
<profile>
<id>target-prod</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-target-prod-no-snapshot</id>
<phase>validate</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<!-- Le projet et son parent ne doivent pas être en SNAPSHOT -->
<requireReleaseVersion />
<!-- Aucune dépendance ne doit être en SNAPSHOT -->
<requireReleaseDeps />
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
...
</project>
Voila ! Tout ce que j'avais besoin de faire de particulier pour la génération du WAR a été mis en place.
Passons maintenant à un deuxième volet du projet : les batchs.
Générer une archive pour votre jeu de batchs
L'objectif est de générer une archive (un fichier TAR.GZ et un fichier ZIP) pour le jeu de batchs de l'application de gestion de tickets.
Cette archive contiendra :
le JAR du module
ticket-batch
les JAR de toutes ses dépendances
les fichiers de configuration (modifiables facilement par l'administrateur)
les scripts shell de lancement de chaque batch
Voici comment je vais organiser l'archive :
le répertoire
bin
contiendra les scripts shell de lancement des batchsle répertoire
conf
contiendra les fichiers de configurationle répertoire
lib
contiendra le JAR du moduleticket-batch
ainsi que les JAR des dépendances
🗄 archive
├── 🗁 bin
│ ├── 🗎 batch-X.sh
│ ├── 🗎 batch-Y.sh
│ └── ...
├── 🗁 conf
│ ├── 🗎 config.properties
│ ├── 🗎 db-X.properties
│ └── ...
└── 🗁 lib
├── 🗎 ticket-batch-*.jar
├── 🗎 ticket-business-*.jar
├── 🗎 ticket-model-*.jar
├── 🗎 commons-lang3-*.jar
└── ...
Préparer le terrain
Pour commencer, je vais créer l'arborescence suivante pour héberger les fichiers à inclure dans l'archive :
🗁 src/data
├── 🗁 scripts
│ ├── 🗎 batch-X.sh
│ ├── 🗎 batch-Y.sh
│ └── ...
└── 🗁 conf
├── 🗎 config.properties (conf. de l'application)
├── 🗎 db-X.properties (conf. de la base de données)
└── ...
Configuration du JAR des batchs
Étant donné que je vais fournir les JAR des dépendances à côté du JAR du module ticket-batch
, je vais ajouter le classpath au manifest de ce JAR. Ainsi, toutes les classes seront trouvées automatiquement par la JVM lors de l'exécution du JAR.
Pour cela, je complète la configuration du plugin maven-jar-plugin
:
<project>
...
<build>
<plugins>
<!-- Création du JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.exemple.demo.batch.App</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix></classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
Assembler le tout dans une archive
Afin de générer les fichiers d'archive, je vais utiliser le plugin maven-assembly-plugin.
Définition de l'assemblage
La définition des assemblages se fait grâce à des fichiers XML dans le répertoire src/assembly
.
Je crée donc un nouveau fichier src/assembly/archive-deploy.xml
:
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!-- Identifiant de l'assemblage -->
<id>archive-deploy</id>
<!-- Les formats d'archive à générer -->
<formats>
<format>tar.gz</format>
<format>zip</format>
</formats>
<!-- lib : dépendances + JAR ticket-batch -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<!-- Scripts shell de lancement -->
<fileSet>
<directory>src/data/scripts</directory>
<outputDirectory>bin</outputDirectory>
<!-- Droits UNIX sur les fichiers (-rwx-rx-rx) -->
<fileMode>0755</fileMode>
</fileSet>
<!-- Fichiers de configuration -->
<fileSet>
<directory>src/data/conf</directory>
<outputDirectory>conf</outputDirectory>
</fileSet>
</fileSets>
</assembly>
Voici quelques explications sur les différents éléments contenus dans ce fichier :
id
: l'identifiant de l'assemblage. Maven va se servir de cet identifiant dans le nom des fichiers générés :<project.finalName>-<id>.<format>
. Ce qui donnerait par exemple :ticket-batch-1.0-SNAPSHOT-archive-deploy.tar.gz
.formats
: les formats de fichier à générer. Ici un fichier tar.gz et un fichier zip.dependencySets
: ajout d'un ensemble de dépendances. Ici, j'ajoute toutes les dépendances de runtime (scope runtime et compile) du projet dans le répertoire de destinationlib
.fileSets
: ajout d'un ensemble de fichiers. Ici, j'ajoute deux ensembles de fichiers :les scripts de lancement des batchs contenus dans le répertoire
src/data/scripts
, dans le répertoire de destinationbin
. J'y positionne les droits Unix, notamment celui d'exécution :<fileMode>0755</fileMode>
(-rwxr-x-rx).les fichiers de configuration contenus dans le répertoire
src/data/conf
, dans le répertoire de destinationconf
.
Câbler la génération des fichiers d'archive
La dernière étape consiste à câbler la génération des fichiers d'archive à la phase package
:
<project>
...
<build>
<plugins>
...
<!-- Création des archives de déploiement -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/assembly/archive-deploy.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>assembly-archive-deploy</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
Et voilà, maintenant, si vous lancez la commande suivante, les fichiers archives de l'assemblage archive-deploy
seront générés automatiquement dans le répertoire target
:
mvn package
Si vous voulez aller plus loin dans les assemblages, vous pouvez consulter la documentation.
Conclusion
Nous avons vu deux exemples de personnalisation de la construction et de la génération de livrables de projet Maven.
Je vous invite à tester cela et faire vos propres expériementations.
Dans le prochain chapitre, vous verrons qu'il est possible d'utiliser Maven pour générer un site web pour votre projet. Ce site va donner des informations générales sur le projet mais aussi des informations sur ses différents modules, la documentation, des rapports sur l'exécution des tests.