Réalisation d'un batch avec Spring Batch
Introduction
Des applications ont besoin de synchroniser des données depuis d'autres applications. En général, ces traitements sont effectués la nuit par des Batchs. Lutèce propose l'utilisation de Spring Batch afin de réaliser ces traitements.
Attention : Spring Batch n'est pas intégré dans le coeur de Lutèce. Il s'agit d'aider au développement d'une application externe dédié à la synchronisation de ces données.
Spring Batch
Spring Batch est un framework dédié aux batchs. Il propose des fonctionnalités communes aux batchs :
- Lecture depuis différentes sources de données
- Écriture sur différentes cible
- Gestion d'un traitement paginé
- Gestion des transactions
- Gestion des erreurs
- Ordonnancement des étapes du batch...
Développement d'un batch
Pom parent pour les batch
Lutèce préconise l'utilisation d'un pom parent pour les batch :
<parent> <artifactId>batch-parent</artifactId> <groupId>fr.paris.lutece.batches</groupId> <version>1.0.0</version> </parent>
Ce pom parent a pour but de :
- Récupérer les dépendances Spring batch,
- Récupérer la dépendance au driver MySQL,
- Récupérer les dépendances JUnit, Log4J, Apache Commons...
- Récupérer Jalopy afin de respecter facilement les règles de développement Lutèce pour la mise en forme du code.
- Il préconfigure les options de compilation (version de Java)
- Il configure le repository de déploiement.
Écriture du batch
Les exemples ci-dessous sont basés sur le batch-ivoire [1] De la documentation complémentaire est disponible chez Spring [2]
pom.xml
Ci-dessous un exemple de POM (les dépendances fr.paris.lutece.batches.* sont décrites plus loin dans ce document) :
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>batch-parent</artifactId> <groupId>fr.paris.lutece.batches</groupId> <version>1.0.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-ivoire</artifactId> <version>1.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Batch Ivoire</name> <description> <![CDATA[Synchronize the Ivoire application states with PASU application workflow actions.]]> </description> <repositories> <repository> <id>lutece</id> <name>luteceRepository</name> <url>http://dev.lutece.paris.fr/maven_repository</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <properties> <json.version>2.4</json.version> <componentName>batch-ivoire</componentName> <jiraProjectName>BATCHIVOIRE</jiraProjectName> <jiraComponentId>10637</jiraComponentId> </properties> <dependencies> <dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-httpaccess</artifactId> <version>1.0.0</version> <type>jar</type> </dependency> <dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-signrequest</artifactId> <version>1.0.0</version> <type>jar</type> </dependency> <dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-logger</artifactId> <version>1.0.0</version> <type>jar</type> </dependency> <dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-notification</artifactId> <version>1.0.0</version> <type>jar</type> </dependency> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>${json.version}</version> <classifier>jdk15</classifier> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <finalName> ${pom.artifactId}-${project.version} </finalName> <descriptors> <descriptor> src/assembly/src.xml </descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <scm> <connection>scm:svn:http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-ivoire</connection> <developerConnection>scm:svn:https://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-ivoire</developerConnection> <url>http://dev.lutece.paris.fr/viewvc/portal/trunk/batches/batch-ivoire</url> </scm> </project>
Principes généraux
- Le lancement d'un batch consiste au lancement d'un ou plusieurs Jobs (tâches réalisées par le batch).
- Le JobLauncher permet de lancer un Job.
- Un Job est composé de plusieurs Step, correspondant au étapes de la Job.
- Les ItemReader permettent la lecture d'un flux (exemple : un fichier)
- De nombreuses classes utilitaires permettent par exemple la lecture de fichier avec séparateur, ou la lecture en base...
- Les ItemWriter permettent l'écriture d'un flux (exemple : un fichier)
- Les ItemProcessors permettent de transformer un flux d'un type à un autre (exemple : l'objet lu à partir du fichier vers un autre type d'objet métier)
- Des Listeners permettent d'écouter les évènements à chaque niveau (step, reader, writer, job...)
Structure du projet
- src
- main
- assembly : descripteur pour le packaging du batch
- bin : scripts de lancement du batch
- java : les sources Java
- logs
- resources : les ressources - contexte Spring, loggers, propriétés de l'application
- tmp
- test : répertoires utilisés pour les tests
- main
- pom.xml
Contexte Spring
L'ensemble des classes à implémenter doivent être référencées dans le context Spring de l'application. Il doit contenir le header suivant :
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sb="http://www.springframework.org/schema/batch" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
Afin d'externaliser les propriétés, il faut utiliser un property-placeholder :
<context:property-placeholder location="classpath:batch_ivoire.properties" />
Il peut également être utile de déclarer la configuration par annotation :
<context:annotation-config />
Assembly
Le dossier assembly contient un fichier src.xml décrivant le packaging du batch.
<assembly> <id/> <formats> <format>tar.gz</format> </formats> <fileSets> <fileSet> <directory>target</directory> <outputDirectory>lib</outputDirectory> <includes> <include>batch-ivoire*.jar</include> </includes> </fileSet> <fileSet> <directory>src/bin</directory> <outputDirectory>bin</outputDirectory> </fileSet> <fileSet> <directory>src/conf</directory> <outputDirectory>conf</outputDirectory> </fileSet> <fileSet> <directory>src/tmp</directory> <outputDirectory>tmp</outputDirectory> </fileSet> <fileSet> <directory>src/main/resources</directory> <outputDirectory>conf</outputDirectory> <includes> <include>*.properties</include> </includes> </fileSet> <fileSet> <directory>src/lib-ext</directory> <outputDirectory>lib-ext</outputDirectory> </fileSet> <fileSet> <directory>src/logs</directory> <outputDirectory>logs</outputDirectory> </fileSet> </fileSets> <dependencySets> <dependencySet> <outputDirectory>/lib-ext</outputDirectory> <excludes> <exclude>batch-ivoire*.jar</exclude> </excludes> </dependencySet> </dependencySets> </assembly>
Réalisation
JobLauncher
La surcharge d'un SimpleJobLauncher permet d'effectuer des traitements en cas d'erreur du Job. L'interface JobExecutionListener permet d'effectuer des traitement pre et post Job.
Job
Un Job peut être assimilé à une tâche réalisée par le batch. Un SimpleJob permet d'exécuter séquentiellement les Step. Exemple de définition d'un Job (simpleJob peut être utilisé pour plusieurs jobs) :
<bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob" abstract="true" p:jobRepository-ref="jobRepository" p:restartable="false" /> <sb:job id="job" parent="simpleJob"> <sb:step id="executeActionStep" parent="simpleStep"> <sb:tasklet> <sb:chunk reader="executeActionReader" processor="executeActionProcessor" writer="executeActionWriter" commit-interval="${job.commit.interval}" /> </sb:tasklet> </sb:step> </sb:job>
Step
Un step définit un reader, un processor, un writer.
Reader
Un reader permet de lire une source de données. Différentes implémentations permettent de gérer facilement les accès Fichiers, JDBC, Hibernate, JPA... Exemple de reader basé sur la lecture de fichier :
<bean id="executeActionReader" class="fr.paris.lutece.batches.ivoire.job.IvoireItemReader"> <property name="resource" value="file:${ivoire.file.path}" /> <property name="file" value="file:${ivoire.file.path}" /> <property name="lineMapper"> <bean class="fr.paris.lutece.batches.ivoire.job.internal.IvoireLineMapper"> <property name="lineTokenizer"> <bean class="fr.paris.lutece.batches.ivoire.util.transform.LengthLineTokenizer" p:names="idIvoire,idFolder,processType,idDemandDemat,idMerchantAccount,ivoireCode,labelIvoireCode,dateFinalVisa,inspectorLastNameFirstName,inspectorPhoneNumber" p:listLengthDelimiters="11,13,11,20,50,3,100,19,60,14" p:strict="false" /> </property> <property name="fieldSetMapper"> <bean class="fr.paris.lutece.batches.ivoire.job.internal.IvoireFieldSetMapper" /> </property> </bean> </property> </bean>
L'interface ItemReadListener permet de gérer les erreurs de lecture, et des pre/post traitement.
Writer
ItemWriter est l'interface qui définit l'écriture des données. Des implémentations facilitent cette écriture. L'interface ItemWriteListener permet de gérer les erreurs de d'écriture, et des pre/post traitement.
Lancement du batch
Il faut utiliser le CommandLineJobRunner de Spring, et initialiser le classpath, l'emplacement des logs, JAVA_OPTS...
$JAVA -cp $TMP_CP $JAVA_OPTS -Dproducts.home=$PRODUCTS_HOME -Dlogs.home=$PRODUCTS_HOME/logs org.springframework.batch.core.launch.support.CommandLineJobRunner batch_ivoire_context.xml job >> $PRODUCTS_HOME/logs/batch-ivoire.out 2>&1
Librairies
Lutèce propose plusieurs librairies pouvant se montrer utiles dans le développement d'un batch
batch-library-signrequest
Ce librairie permet de signer des liens.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-signrequest</artifactId> <version>1.0.0</version> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch le bean correspondant au request authenticator de la manière suivante :
<bean id="hashService" class="fr.paris.lutece.batches.util.signrequest.security.Sha1HashService" /> <bean id="requestAuthenticator" class="fr.paris.lutece.batches.util.signrequest.HeaderHashAuthenticator" p:hashService-ref="hashService" p:privateKey="${workflow.ws.rest.privateKey}" />
Il faut également déclarer la clé de hash de la manière suivante :
workflow.ws.rest.privateKey=MyPrivateKey
Utilisation
@Autowired private RequestAuthenticator _requestAuthenticator;
batch-library-httpaccess
Cette library permet de se connecter en HTTP à une application distante, et supporte la signature des requêtes.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-httpaccess</artifactId> <version>1.0.0</version> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch le bean correspondant au httpaccess de la manière suivante :
<bean id="httpAccess" class="fr.paris.lutece.batches.util.httpaccess.HttpAccess" p:proxyHost="${httpAccess.proxyHost}" p:proxyPort="${httpAccess.proxyPort}" p:proxyUserName="${httpAccess.proxyUserName}" p:proxyPassword="${httpAccess.proxyPassword}" p:hostName="${httpaccess.hostName}" p:domainName="${httpAccess.domainName}" p:realm="${httpaccess.realm}" p:noProxyFor="${httpAccess.noProxyFor}" p:contentCharset="${httpAccess.contentCharset}" />
Et avoir dans le fichier de properties du batch :
################################################################ ## Proxy settings httpAccess.proxyHost= httpAccess.proxyPort= httpAccess.proxyUserName= httpAccess.proxyPassword= # computer name httpaccess.hostName= # domain name httpAccess.domainName= httpaccess.realm= httpAccess.noProxyFor=127.0.0.1,localhost,*mdp # Content charset - optionnal httpAccess.contentCharset=UTF-8
Utilisation
@Autowired private HttpAccess _httpAccess;
batch-library-logger
Cette library fournit des faciliter le logging, avec 3 loggers.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-logger</artifactId> <version>1.0.0</version> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch le bean correspondant au logger choisi (cf. ci-dessous) de la manière suivante :
<bean id="logger" class="<classLogger>" scope="singleton" p:loggerInfo="<nomLoggerInfo>" p:loggerDebug="<nomLoggerDebug>" p:loggerError="<nomLoggerError>" p:appenderInfo="<nomAppenderInfo>" p:appenderDebug="<nomAppenderDebug>" p:appenderError="<nomAppenderError>" />
Il faut ensuite configurer le fichier log4j.properties (ou log4j.xml) de façon classique.
Utilisation
- SimpleLogger : Permet de loguer simplement dans le logger log4j.
- SimpleBatchLogger : En plus de loguer dans le logger log4j, il va stocker les erreurs sous forme de String et les ajouter dans une liste qui peut être récupérer en appelant la méthode getErrors( ).
- SimpleErrorListBatchLogger : C'est la même chose que le SimpleBatchLogger, excepté qu'il stocke les erreurs dans un objet implémentant l'interface IError.
- AbstractErrorMapBatchLogger : C'est une classe abstraite permettant de stocker les erreurs dans une map (utilisé par exemple pour une gestion d'erreur par ligne).
batch-library-notification
Cette library permet des notifications par mail.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-notification</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch le bean correspondant au mail service de la manière suivante :
<bean id="mailService" class="fr.paris.lutece.batches.util.notification.MailService" p:mailingList="${mail.mailinglist}" p:mailSubject="${mail.subject}" p:mailHost="${mail.host}" p:mailType="${mail.type.html}" p:mailNoReply="${mail.noreply.email}" p:mailEncoding="${mail.encoding}" />
Et dans le fichier de properties du batch :
################################################################ # Mail settings mail.host=smtpmail.mdp mail.noreply.email=noreply@paris.fr mail.type.html=text/html;charset="utf-8" # list of mails separated by a comma (,) to be notified when there are errors mail.mailinglist=lutece@paris.fr mail.subject=Notification d'erreurs du batch Ivoire mail.encoding=UTF-8
Utilisation
@Autowired private INotificationService _mailService;
batch-library-workflow
Cette library apporte les services de workflow Lutèce dans un batch. Il permet en outre de pouvoir récupérer l'ensemble des informations (actions, tâches, etc...) mais également de pouvoir exécuter des actions de workflow.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-workflow</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch l'import du fichier de context de la librarie :
<import resource="classpath:libraries/workflow_context.xml" />
Ajouter le bean correspondant au dataSource de workflow :
<bean id="workflow.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${batch.jdbc.workflow.driver}" p:url="${batch.jdbc.workflow.url}" p:username="${batch.jdbc.workflow.user}" p:password="${batch.jdbc.workflow.password}" p:validationQuery="${batch.jdbc.workflow.validationQuery}" p:testWhileIdle="${batch.jdbc.workflow.testWhileIdle}" />
Ajouter la configuration du dataSource de workflow dans le fichier de properties du batch :
batch.jdbc.workflow.driver=com.mysql.jdbc.Driver batch.jdbc.workflow.url=jdbc:mysql://localhost/lutece?autoReconnect=true&useUnicode=yes&characterEncoding=utf8 batch.jdbc.workflow.user=root batch.jdbc.workflow.password=lutece batch.jdbc.workflow.testWhileIdle=true batch.jdbc.workflow.validationQuery=SELECT 1
Utilisation
@Autowired private IIWorkflowService _workflowService;
Exemple
Un exemple de batch utilisant cette librairie est disponible sur http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/
batch-library-workflow-notifycrm
Cette library apporte le service de notification CRM de workflow dans un batch. Il permet en outre de pouvoir exécuter les actions de notification vers une webapp CRM.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-workflow-notifycrm</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch l'import du fichier de context de la librarie :
<import resource="classpath:libraries/workflow-notifycrm_context.xml" />
Ajouter les bean correspondant aux dataSources de workflow-notifycrm et directory :
<bean id="workflow-notifycrm.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${batch.jdbc.workflow-notifycrm.driver}" p:url="${batch.jdbc.workflow-notifycrm.url}" p:username="${batch.jdbc.workflow-notifycrm.user}" p:password="${batch.jdbc.workflow-notifycrm.password}" p:validationQuery="${batch.jdbc.workflow-notifycrm.validationQuery}" p:testWhileIdle="${batch.jdbc.workflow-notifycrm.testWhileIdle}" /> <bean id="directory.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${batch.jdbc.directory.driver}" p:url="${batch.jdbc.directory.url}" p:username="${batch.jdbc.directory.user}" p:password="${batch.jdbc.directory.password}" p:validationQuery="${batch.jdbc.directory.validationQuery}" p:testWhileIdle="${batch.jdbc.directory.testWhileIdle}" />
Ajouter la configuration des dataSources de workflow-notifycrm et directory dans le fichier de properties du batch :
batch.jdbc.workflow-notifycrm.driver=com.mysql.jdbc.Driver batch.jdbc.workflow-notifycrm.url=jdbc:mysql://localhost/lutece_workflow_notifycrm?autoReconnect=true&useUnicode=yes&characterEncoding=utf8 batch.jdbc.workflow-notifycrm.user=root batch.jdbc.workflow-notifycrm.password=lutece batch.jdbc.workflow-notifycrm.testWhileIdle=true batch.jdbc.workflow-notifycrm.validationQuery=SELECT 1 batch.jdbc.directory.driver=com.mysql.jdbc.Driver batch.jdbc.directory.url=jdbc:mysql://localhost/lutece_directory?autoReconnect=true&useUnicode=yes&characterEncoding=utf8 batch.jdbc.directory.user=root batch.jdbc.directory.password=lutece batch.jdbc.directory.testWhileIdle=true batch.jdbc.directory.validationQuery=SELECT 1
Si les 2 plugins utilisent le même dataSource, il est possible d'utiliser des alias, ce qui évite de déclarer plusieurs fois le même dataSource avec des IDs différents :
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${batch.jdbc.driver}" p:url="${batch.jdbc.url}" p:username="${batch.jdbc.user}" p:password="${batch.jdbc.password}" p:validationQuery="${batch.jdbc.validationQuery}" p:testWhileIdle="${batch.jdbc.testWhileIdle}" /> <alias name="dataSource" alias="directory.dataSource" /> <alias name="dataSource" alias="workflow-notifycrm.dataSource" />
Exemple
Un exemple de batch utilisant cette librairie est disponible sur
batch-library-crmclient
Cette librairie apporte l'implémentation du DAO pour ajouter des notifications CRM en queue de traitement, afin que le batch de notification CRM de la webapp CRM puisse envoyer les notifications.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-crmclient</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch l'import du fichier de context de la librarie :
<import resource="classpath:libraries/crmclient_context.xml" />
Ajouter les bean correspondant au dataSource de crmclient :
<bean id="crmclient .dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${batch.jdbc.crmclient .driver}" p:url="${batch.jdbc.crmclient .url}" p:username="${batch.jdbc.crmclient .user}" p:password="${batch.jdbc.crmclient .password}" p:validationQuery="${batch.jdbc.crmclient .validationQuery}" p:testWhileIdle="${batch.jdbc.crmclient .testWhileIdle}" />
Ajouter la configuration du dataSource de crmclient dans le fichier de properties du batch :
batch.jdbc.crmclient .driver=com.mysql.jdbc.Driver batch.jdbc.crmclient .url=jdbc:mysql://localhost/lutece_directory?autoReconnect=true&useUnicode=yes&characterEncoding=utf8 batch.jdbc.crmclient .user=root batch.jdbc.crmclient .password=lutece batch.jdbc.crmclient .testWhileIdle=true batch.jdbc.crmclient .validationQuery=SELECT 1
Une fois que le batch ait été packagé (mvn package), il faut configurer le fichier conf/libraries/crmclient.properties et configurer la Base URL de la webapp CRM :
crmclient.crm.rest.webapp.baseurl=http://localhost:8080/lutece
Exemple
Un exemple de batch utilisant cette librairie est disponible sur http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/
batch-library-freemarker
Cette librairie apporte l'implémentation du service FreeMarker. Il permet en outre de pouvoir transformer les messages de notification contenant des labels freemarker.
Déclaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-freemarker</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
Il faut ajouter dans le fichier context.xml du batch l'import du fichier de context de la librarie :
<import resource="classpath:libraries/freemarker_context.xml" />
Une fois que le batch ait été packagé (mvn package), il faut configurer le fichier conf/libraries/freemarker.properties et configurer le chemin absolu vers la webapp Lutèce
freemarker.webapp.path=/home/user/apps/tomcat/webapps/lutece/
Exemple
Un exemple de batch utilisant cette librairie est disponible sur http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/