Create a batch with Spring Batch
Introduction
Applications need to synchronize data from other applications. In general, these treatments are performed at night by Batchs. Lutèce proposes the use of Spring Batch in order to carry out these treatments.
Warning : Spring Batch is not integrated in the core of Lutetia. This is to help develop an external application dedicated to the synchronization of these data.
Spring Batch
Spring Batch is a framework dedicated to batching. It offers features common to batches:
- Reading from different data sources
- Writing on different target
- Management of a paginated treatment
- Transaction Management
- Error management
- Scheduling of the batch steps ...
Development of a batch
Parent pom for batch
Lutèce recommends the use of a parent pom for batch:
<parent> <artifactId>batch-parent</artifactId> <groupId>fr.paris.lutece.batches</groupId> <version>1.0.0</version> </parent>
This parent pom aims to :
- Recover the dependencies Spring batch,
- Recover the MySQL driver dependency,
- Recover dependencies JUnit, Log4J, Apache Commons ...
- Recover Jalopy to easily respect the rules of development Lutèce for the formatting of the code.
- It preconfigures compilation options (Java version)
- It configures the deployment repository.
Writing the batch
The examples below are based on the batch-ivory [1] Additional documentation is available at Spring [2]
pom.xml
Below is an example of (the dependencies fr.paris.lutece.batches. * Are described later in this 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>
General principles
- The launch of a batch consists of launching one or more Jobs (tasks performed by the batch).
- The JobLauncher is used to launch a Job.
- A Job is composed of several Step, corresponding to the steps of the Job.
- ItemReaders allow playback of a stream (example: a file)
- Many utility classes allow for example the reading of file with separator, or the reading in base ...
- ItemWriter allow writing a stream (example: a file)
- ItemProcessors allow to transform a stream from one type to another (example: the object read from the file to another type of business object)
- Listeners can listen to events at each level (step, reader, writer, job ...)
Project structure
- src
- hand
- assembly: descriptor for batch packaging
- bin: batch launch scripts
- java: Java sources
- logs
- resources: resources - Spring context, loggers, application properties
- tmp
- test: directories used for testing
- hand
- pom.xml
Spring context
The set of classes to implement must be referenced in the Spring context of the application. It must contain the following header :
<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">
In order to outsource the properties, you need to use a property-placeholder :
<context:property-placeholder location = "classpath: batch_ivory.properties" />
It can also be useful to declare the configuration by annotation :
<context:annotation-config />
Assembly
The assembly folder contains a src.xml file describing the packaging of the 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>
Realization
JobLauncher
The overload of a SimpleJobLauncher makes it possible to carry out treatments in case of error of the Job.
The JobExecutionListener interface allows pre- and post-Job processing.
Job
A Job can be likened to a task performed by the batch. A SimpleJob allows you to execute the Step sequentially. Example of defining a Job (simpleJob can be used for several 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
A step defines a reader, a processor, a writer.
Reader
A reader reads a data source. Various implementations make it easy to manage File, JDBC, Hibernate, JPA access... Example of reader based on file reading :
<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>
The ItemReadListener interface is used to manage read errors, and pre/post processing.
Writer
ItemWriter is the interface that defines the writing of data. Implementations facilitate this writing. The ItemWriteListener interface is used to manage write errors, and pre/post processing.
Launch of the batch
You have to use Spring's CommandLineJobRunner, and initialize the classpath, the location of the 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- ivory.out 2> & 1
Libraries
Lutèce offers several libraries that can be useful in the development of a batch
batch-library-signrequest
This library allows you to sign links.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-signrequest</artifactId> <version>1.0.0</version> </dependency>
Configuration
In the context.xml file of the batch, add the bean corresponding to the request authenticator as follows :
<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}" />
The hash key must always be declared as follows :
workflow.ws.rest.privateKey = MyPrivateKey
Use
@Autowired private RequestAuthenticator _requestAuthenticator;
batch-library-httpaccess
This library allows to connect in HTTP to a remote application, and supports the signature of the requests.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-httpaccess</artifactId> <version>1.0.0</version> </dependency>
Configuration
In the context.xml file of the batch, the bean corresponding to the httpaccess must be added as follows :
<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}" />
And have in the batch properties file :
################################################## ############## ## 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 * pwd # Content charset - optionnal httpAccess.contentCharset = UTF-8
Use
@Autowired private HttpAccess _httpAccess;
batch-library-logger
This library provides easy logging, with 3 loggers.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-logger</artifactId> <version>1.0.0</version> </dependency>
Configuration
In the context.xml file of the batch, add the bean corresponding to the selected logger (see below) as follows :
<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>" />
It is then necessary to configure the file log4j.properties (or log4j.xml) in a classic way.
Use
- SimpleLogger : Allows simple logging into the log4j logger.
- SimpleBatchLogger : In addition to logging in the log4j logger, it will store errors as a String and add them to a list that can be retrieved by calling the getErrors () method.
- SimpleErrorListBatchLogger : It's the same as the SimpleBatchLogger, except that it stores errors in an object implementing the IError interface.
- AbstractErrorMapBatchLogger : This is an abstract class for storing errors in a map (used for example for line error handling).
batch-library-notification
This library allows email notifications.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-notification</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
In the batch context.xml file, you must add the bean corresponding to the mail service as follows :
<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}" />
And in the batch properties file :
################################################## ############## # 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 = Ivory Batch Error Notification mail.encoding = UTF-8
Use
@Autowired private INotificationService _mailService;
batch-library-workflow
This library provides Lutece workflow services in a batch. It also allows you to retrieve all information (actions, tasks, etc ...) but also to perform workflow actions.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-workflow</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
In the context.xml file of the batch, you must add the import of the context file of the librarie :
<import resource = "classpath: libraries / workflow_context.xml" />
Add the bean corresponding to the workflow dataSource:
<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}" />
Add the configuration of the workflow dataSource to the batch properties file :
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
Use
@Autowired private IIWorkflowService _workflowService;
Example
An example of a batch using this library is available at http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/
batch-library-workflow-notifycrm
This library provides the CRM workflow notification service in a batch. It also allows you to execute the notification actions to a CRM webapp.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-workflow-notifycrm</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
In the context.xml file of the batch, you must add the import of the context file of the librarie:
<import resource = "classpath:libraries/workflow-notifycrm_context.xml" />
Add the beans corresponding to the workflow-notifycrm and directory dataSources:
<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}" />
Add the configuration of the workflow-notifycrm and directory dataSources in the batch properties file:
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
If both plugins use the same dataSource, it is possible to use aliases, which avoids declaring several times the same dataSource with different IDs :
<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" />
Example
An example of a batch using this library is available on
batch-library-crmclient
This library brings the CAD implementation to add CRM notifications to the processing queue, so that the CRM notification batch of the CRM webapp can send the notifications.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-crmclient</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
In the context.xml file of the batch, you must add the import of the context file of the librarie:
<import resource = "classpath:libraries/crmclient_context.xml" />
Add the beans corresponding to the dataSource of 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}" />
Add the crmclient dataSource configuration to the batch properties file:
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
Once the batch has been packaged (mvn package), you must configure the conf/libraries/crmclient.roperties file and configure the Base URL of the CRM webapp :
crmclient.crm.rest.webapp.baseurl=http://localhost:8080/lutece
Example
An example of a batch using this library is available on http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/
batch-library-freemarker
This library brings the implementation of the FreeMarker service. It also allows you to transform notification messages containing freemarker labels.
Declaration
<dependency> <groupId>fr.paris.lutece.batches</groupId> <artifactId>batch-library-freemarker</artifactId> <version>1.0.0</version> <type>jar</type> </dependency>
Configuration
In the context.xml file of the batch, you must add the import of the context file of the librarie :
<import resource = "classpath:libraries/freemarker_context.xml" />
Once the batch has been packaged (mvn package), you have to configure the conf/libraries/freemarker.properties file and configure the absolute path to the Lutece webapp
freemarker.webapp.path=/home/user/apps/tomcat/webapps/lutece/
Example
An example of a batch using this library is available on http://dev.lutece.paris.fr/svn/lutece/portal/trunk/batches/batch-workflowsample/