Création d’un batch

Présentation de Spring Batch

L’application Batch que nous allons développer dans ce tutoriel, s’appuie principalement sur Spring Batch.
Spring-Batch est un framework développé en collaboration par SpringSource et Accenture.

Il permet de traiter les problématiques suivantes :

  • Traitement « par lot » : pour éviter par exemple de charger en mémoire l’ensemble des données traitées par le batch
  • Gestion des transactions
  • Gestion de la reprise sur erreur

Il s’agit donc d’un framework adapté à l’écriture des batchs (batch : programme automatisé réalisant un ensemble de traitements sur un volume de données).

Le Schéma suivant représente de manière très simplifiée l’architecture associée à un batch.

Pour gérer les données d’un batch, on utilise principalement les trois outils suivants :

  • ItemReader : Il fournit des données à partir de plusieurs types d’entrée
  • ItemWriter : Il a des fonctions similaires à Itemreader, mais avec des opérations inverses. Il fournit des données en sortie (insertion ou mise à jour dans une base de données …). Le format de sortie est spécifique à chaque batch.
  • ItemProcessor : Il prend un élément en entrée, le traite (applique la logique métier dans un scénario de traitement orienté objet) et retourne un autre élément.

Un batch correspond à un Job qui est constitué d’une ou plusieurs Step.
Un Job a besoin d’un JobLauncher pour être lancé et d’un JobRepository qui fournit les opérations CRUD sur les méta-données.
Bien évidemment, un batch peut utiliser bien d’autres paramètres de job.

Voici une liste non exhaustive des JobParameter que vous allez probablement rencontrer :

  • JobRegistry : garde en mémoire (souvent en base de données) les paramètres d’exécution du batch
  • JobExplorer : pour explorer la base de données
  • JobOperator : pour administrer les threads et le JobRegistry
  • JobParameterIncrementer : pour relancer des jobs complétés

Pour plus d’informations, je vous conseille de consulter le site de Spring.

Modèle de projet Batch

Le principal but d’un projet batch est de centraliser tous les batchs d’une application.
Le modèle de projet batch respecte la structure standard de répertoire de Maven2. On retrouve donc :

  • le répertoire src qui regroupe les sources java et les ressources du projet
  • le répertoire target qui contient tous les éléments générés
  • le fichier pom.xml qui contient la description détaillée du projet

Le socle ajoute un répertoire conf qui va aider à gérer les configurations différentes suivant l’environnement (dev, test, prod-test, prod).

Le répertoire src/main/java

Le répertoire src/main/java contient les sources du projet. Il n’y a pas d’architecture particulièrement définie pour ce type de projet. Il est simplement conseillé de les organiser de manière logique (par exemple : toutes les classes d’un même step dans le même package, ou autre).

Le répertoire src/main/resources

Ce répertoire est destiné à accueillir les ressources nécessaires à l’application. Cela permet par exemple de déclarer tous les messages localisés (FR, EN, etc…) utilisés par l’application.

Le répertoire conf

Ce répertoire contient cinq sous répertoires contenant les fichiers de configuration propres à des environnements différents dans lesquels l’application peut être utilisée:

  • Le répertoire common contient les fichiers de configuration communs à tous les environnements de projet.
  • Le répertoire dev contient les fichiers de configuration propres à l’environnement de développement c’est à dire quand l’application est en cours de développement par les développeurs.
  • Le répertoire prod contient les fichiers de configuration propres à l’environnement de production du projet c’est à dire quand l’application est en production chez le client.
  • Le répertoire prod-test contenant les fichiers de configuration propres à l’environnement de test en mode production c’est à dire quand le projet est en cours de test chez le client.
  • Le répertoire test contenant les fichiers de configuration propres à l’environnement de test.

Les fichiers appartenant à ces répertoires, et nécessaires à un projet batch sont :

    • Le fichier filters.properties du répertoire common/filters. Lorsqu’une authentification est nécessaire à l’exécution d’une application et qu’un serveur tente d’exécuter cette application, l’authentification se fait en utilisant une clé d’authentification (runAsKey.local) et un nom de rôle (RUN_As_server). Nous précisons dans ce fichier la clé d’authentification ainsi que le nom de rôle à utiliser. Ces valeurs existent par défaut dès la création de notre projet.
  • Le fichier applicationContext.xml du répertoire common/resources/Ce fichier correspond au fichier de configuration spring dans lequel nous définissons l’ensemble des éléments qui vont constituer notre projet, à savoir :
    • Les beans nécessaires à la gestion des jobs
    • Les jobs
    • Les steps et composants des tasklets
    • L’export de nos jobs via JMX
    • La definition de trigger Cron, afin de mettre en place le scheduling d’un job

     

  • Le fichier filters.properties du répertoire dev/filters. Ce fichier contient la définition des variables nécessaires :
    • à la connexion à la base de données postgresql
    • aux logs
    • à la configuration scp pour le déploiement de l’application sur un serveur distant
    • à la configuration des triggers cron

     

  • Le fichier init-db.sql du répertoire dev/ressourcesCe fichier contient le script d’initialisation de la base de données.
  • Le fichier rmiServiceImporterSpecContext.xml du répertoire dev/ressources. Il permet d’importer des services propres à un environnement particulier (ici l’environnement de développement).

Création d’un projet Batch

Pour créer le projet batch, nous allons utiliser les modèles de projets intégrés au socle. Pour celà, nous allons utiliser l’assistant accessible via :

  • Le menu File -> New -> Other
  • Le menu contextuel puis New -> Other
  • Le raccourcis Ctrl+N

Ensuite, saisissez dans le champ le texte maven pour filtrer l’affichage et sélectionnez Maven Project puis cliquez sur Next.

Vous accédez alors à l’assistant de création d’un projet Maven.

Vérifiez à la première étape que la case à cocher Create a simple project (skip archetype selection) est bien décochée, puis cliquez sur Next.

Assurez vous que le catalogue sélectionné soit bien celui de Scub Foundation, sélectionnez l’archetype scub-foundation-archetype-batch puis cliquez sur Next.

Il reste ensuite à spécifier les paramètres suivants :

  • GroupId : scub-foundation.sample.my-contact-manager qui est commun à tous les projets de la même application
  • ArtifactId : scub-foundation-my-contact-manager-batch
  • version : 1.0
  • Package : org.scub.foundation.contact.manager

Cliquez ensuite sur Finish. Le nouveau projet apparaît alors dans la vue Package Explorer.

Développement de notre projet Batch contactManager

La configuration

Un projet batch repose principalement sur la configuration. En effet, cette configuration va vous permettre de gérer, organiser et structurer vos batchs.

Ouvrez le fichier applicationContext.xml contenu dans le répertoire conf/common/ressources.
Vous pouvez constatez que toute la partie gestion des batchs (ou job), c’est à dire JobManagment, est déjà configuré par défaut. Il est bien évidemment possible de modifier cette partie en fonction des besoins.

Création du job

Pour notre projet contactManager, nous souhaitons créer un job qui sera divisé en deux flow (flux d’étapes) qui s’exécuteront en parallèle. Le premier flow contiendra une étape permettant le traitement par lots. Le second flow contiendra une étape de type tasklet.
Les balises que nous allons utiliser sont :

  • <batch:job>avec comme paramètres :
    • id : contactJob
    • incrementer : simpleParameterIncrementer
  • <batch:split>avec comme paramètres :
    • id : contactSplit
    • task-executor : taskExecutor
  • <batch:flow>
  • Un premier <batch:step> avec comme paramètres :
    • id : contactJdbcStep
    • parent : contactJdbcParentStep (cette étape hérite des propriété de contactJdbcStep. contactJdbcStep sera défini plus loin dans ce fichier)
  • Un second <batch:step>avec comme paramètre :
    • id : contactRmiStep
  • Ce second step contiendra un <batch:tasklet>avec comme paramètre :
    • ref : contactRmiTasklet (c’est une référence à un bean défini plus loing dans ce fichier)

Le code à ajouter dans applicationContext.xml est donc :

		<!-- Contient des étapes à exécuter en parallèle -->
 
			<!-- Le premier flux d'étape -->
 
			<!-- Le second flux d'étape à exécuter en parallèle -->

Création des steps et composants des tasklet

Le premier step que l’on va créer est un chunk.

Un chunk regroupe un itemReader, itemWritter et itemProcessor nommés ici respectivement contactJdbcReader, contactJdbcWritter et contactJdbcProcessor. Ce sont des beans qui vont correspondre à des classes que nous implémenterons dans la partie suivante de notre tutoriel.

L’itemReader va posséder des propriétés qui vont permettre de sélectionner les données d’entrée.

A ces trois beans s’ajoute un quatrième qui va définir sous quelle forme les objets après traitement doivent être renvoyés.

Chunk possède également comme attribut commit-interval qui prend comme valeur un entier (ici 5). Cet attribut détermine le nombre d’éléments qui composent un lot à traiter.

Voici le code correspondant à la configuration du premmier step :

<batch:step id="contactJdbcParentStep">
	<batch:tasklet task-executor="taskExecutor" throttle-limit="2">
		<batch:chunk reader="contactJdbcReader" processor="contactJdbcProcessor"
			writer="contactJdbcWritter" commit-interval="5" />
	</batch:tasklet>
</batch:step>
 
<bean id="contactJdbcReader" class="org.springframework.batch.item.database.JdbcPagingItemReader">
   	<property name="dataSource" ref="dataSource" />
    	<property name="queryProvider">
    		<bean class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
    			<property name="dataSource" ref="dataSource" />
    			<property name="selectClause" value="select nom, prenom, date_naissance" />
    			<property name="fromClause" value="from contact" />
    			<property name="whereClause" value="where date_naissance is not null" />
    			<property name="sortKey" value="nom" />
    		</bean>
   	</property>
    	<property name="pageSize" value="3"/>
    	<property name="rowMapper" ref="contactJdbcRowMapper"/>
</bean>
 
<bean id="contactJdbcRowMapper" class="org.scub.foundation.contact.manager.batch.contact.jdbc.ContactJdbcRowMapper" />
 
<bean id="contactJdbcProcessor" class="org.scub.foundation.contact.manager.batch.contact.jdbc.ContactJdbcProcessor" autowire="byName" />
 
<bean id="contactJdbcWritter" class="org.scub.foundation.contact.manager.batch.contact.jdbc.ContactJdbcWritter" autowire="byName" />

Le second step que l’on va créer est un tasklet.

Il fait directement appel à une classe que nous allons implémenter dans la partie suivante.

Voici le code correspondant à la configuration du second step :

<bean id="contactRmiTasklet" class="org.scub.foundation.contact.manager.batch.contact.rmi.ContactRmiTasklet" autowire="byName" />

Export JMX

Export JMX
Pour pouvoir visualiser nos batchs, nous allons utiliser JMX. Pour celà nous devons exporter nos jobs via JMX.

Dans le bean explorer entre les balises map, nous devons ajouter une entrée par agent exposé (dans notre cas, il n’y a qu’un agent exposé qui est contactJobAgent).

La ligne à ajouter est :

<entry key="contactManagerBatch:agent=contactJob" value-ref="contactJobAgent" />

Ensuite nous devons déclarer le bean correspondant à l’agent (contactJobAgent). Il va prendre en paramètre différentes informations comme le jobExplorer, le jobLocator, etc …

Voici le code correspondant :

<bean id="contactJobAgent" class="org.scub.foundation.framework.batch.agent.JobAgentJmx" p:jobName="contactJob"	 
      p:jobExplorer-ref="jobExplorer" p:jobLocator-ref="jobRegistry"	 
      p:jobOperator-ref="jobOperator" autowire="byName" />

Configuration Quartz

Cette configuation va nous permettre de « programmer » le lancement de notre batch. Pour cela, on lui passe le nom de notre batch ainsi qu’une phrase quartz.
Ainsi, le batch se lancera tel jour à telle heure …

Pour cela, on ajoute la ligne suivante dans la balise list au bean qui liste tout les triggers.

<ref bean="contactCronTrigger" />

Ensuite, on récupère le code en commentaire dans la partie config quartz. Il nous permet de créer un trigger cron.
On modifie les id pour être cohérent avec le reste de notre projet.

Le code à ajouter est :

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
		<list>
			<ref bean="contactCronTrigger" />
		</list>
	</property>
</bean>
 
<bean id="contactCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
	<property name="jobDetail" ref="contactJobDetail" />
	<property name="cronExpression" value="${batch.contact.cron.expression}"/>
</bean>
 
<bean id="contactJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
	<property name="jobClass" value="org.scub.foundation.framework.batch.quartz.JobLauncherDetails" />
	<property name="group" value="quartz-batch" />
	<property name="jobDataAsMap">
		<map>
			<entry key="jobName" value="contactJob" />
			<entry key="jobLocator" value-ref="jobRegistry" />
			<entry key="jobOperator" value-ref="jobOperator" />
		</map>
	</property>
</bean>

Connexion avec le projet core-implementation

Le projet batch que nous créons va devoir utiliser des services implémentés dans le projet core-implémentation. Pour ce faire, nous devons importer les services.

Nous allons donc ajouter le code suivant au fichier ServiceImporterSpecContext.xml du répertoire conf/dev/ressources :

<bean id="contactService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
	<property name="serviceUrl" value="rmi://localhost:1099/contact-manager-core-implementations/contactService"/>
	<property name="serviceInterface" value="org.scub.foundation.contact.manager.core.service.interfaces.ContactService"/>
	<property name="refreshStubOnConnectFailure" value="true"/>
        <property name="lookupStubOnStartup" value="false"/>
</bean>

L’interface des services se situe dans le projet core-interface. Pour pouvoir fonctionner correctement, notre projet batch a besoin de core-implementation et de core-interface.

Vous devez alors les publier dans le référentiel local.

L’implémentation des classes

A ce stade, vous devez voir 4 erreurs dans votre fichier applicationContext.xml. Ces erreurs sont présentes car les beans font référence à des classes qui n’existent pas.

Le projet batch n’a pas de réelle architecture. Comme contactManager est divisé en 2 parties distinctes (step 1 via un chunk et sptep 2 via une tasklet), nous allons créer un package par step. Nous allons ajouter un autre package qui contiendra les classes utilitaires comme la gestion des messages.

Pour créer les packages :

  • Sur le répertoire src/main/java, faites un clic droit et dans le menu contextuel, cliquez sur New -> Package
  • Ajoutez pour l’attribut name, la valeur org.scub.foundation.contact.manager.batch.contact.jdbc (pour le premier step)
  • Cliquez sur Finish

Répétez l’opération deux fois mais avec comme valeurs org.scub.foundation.contact.manager.batch.contact.rmi (pour le second step) et org.scub.foundation.contact.manager.batch.util (pour le package utilitaire).

Package org.scub.foundation.contact.manager.batch.contact.jdbc

Ce package contient les classes du chunk. Le chunk affiche un message d’anniversaire pour toutes les personnes dont c’est l’anniversaire.

La premiere classe à créer est POJO représentant un contact. Pour la créer, placez vous sur le package, clic droit New -> Class et dans le champs name, entrez la valeur ContactJdbcDto.

Le code associé à cette classe est :

package org.scub.foundation.contact.manager.batch.contact.jdbc;
 
import java.util.Calendar;
 
/**
 * Un POJO représentant un contact.
 */
public class ContactJdbcDto {
 
    private String nom;
 
    private String prenom;
 
    private Calendar dateNaissance;
 
    private Long age;
 
    /**
     * Return the value of nom.
     * @return the nom
     */
    public String getNom() {
        return nom;
    }
 
    /**
     * Modify the value of nom.
     * @param nom the nom to set
     */
    public void setNom(String nom) {
        this.nom = nom;
    }
 
    /**
     * Return the value of prenom.
     * @return the prenom
     */
    public String getPrenom() {
        return prenom;
    }
 
    /**
     * Modify the value of prenom.
     * @param prenom the prenom to set
     */
    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }
 
    /**
     * Return the value of dateNaissance.
     * @return the dateNaissance
     */
    public Calendar getDateNaissance() {
        return dateNaissance;
    }
 
    /**
     * Modify the value of dateNaissance.
     * @param dateNaissance the dateNaissance to set
     */
    public void setDateNaissance(Calendar dateNaissance) {
        this.dateNaissance = dateNaissance;
    }
 
    /**
     * Return the value of age.
     * @return the age
     */
    public Long getAge() {
        return age;
    }
 
    /**
     * Modify the value of age.
     * @param age the age to set
     */
    public void setAge(Long age) {
        this.age = age;
    }
 
}

La classe suivante à créer permet de traiter un résultat passé en paramètre afin de le stocker dans le format souhaité (ici contactJdbcDto). Le nom de cette classe est ContactJdbcRowMapper.java .

Le code de cette classe est :

package org.scub.foundation.contact.manager.batch.contact.jdbc;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
 
import org.springframework.jdbc.core.RowMapper;
 
public class ContactJdbcRowMapper implements RowMapper {
 
    public static final String NOM_COLUMN = "nom";
 
    public static final String PRENOM_COLUMN = "prenom";
 
    public static final String DATE_NAISSANCE_COLUMN = "date_naissance";
 
    @Override
    public ContactJdbcDto mapRow(ResultSet rs, int rowNum) throws SQLException {
        final ContactJdbcDto contactDto = new ContactJdbcDto();
 
        contactDto.setNom(rs.getString(NOM_COLUMN));
        contactDto.setPrenom(rs.getString(PRENOM_COLUMN));
        contactDto.setDateNaissance(Calendar.getInstance());
        contactDto.getDateNaissance().setTime(rs.getDate(DATE_NAISSANCE_COLUMN));
 
        return contactDto;
    }
 
}

La troisième classe est ContactJdbcWritter.java. C’est cette classe qui va afficher le message. Elle récupère une liste de contacts et affiche le message d’anniversaire correspondant.

Le code de cette classe est :

package org.scub.foundation.contact.manager.batch.contact.jdbc;
 
import java.text.DateFormat;
import java.util.List;
 
import org.apache.log4j.Logger;
import org.apache.log4j.spi.RootLogger;
import org.scub.foundation.contact.manager.batch.util.MessageKeyUtil;
import org.scub.foundation.framework.base.messagesource.MessageSourceUtil;
import org.springframework.batch.item.ItemWriter;
 
public class ContactJdbcWritter implements ItemWriter {
 
    private Logger logger = RootLogger.getLogger(ContactJdbcWritter.class);
 
    private MessageSourceUtil messageSourceUtil;
 
    @Override
    public void write(List listeContactDto) throws Exception {
        if (listeContactDto != null) {
            for (ContactJdbcDto contactDto : listeContactDto) {
                if (contactDto != null) {
                    final String anniversaire = DateFormat.getInstance().format(contactDto.getDateNaissance().getTime());
                    final Object[] params = new Object[] {contactDto.getNom().toUpperCase(), contactDto.getPrenom(), anniversaire, contactDto.getAge()};
                    logger.info(messageSourceUtil.get(MessageKeyUtil.MESSAGE_JOYEUX_ANNIVERSAIRE, params));
                }
            }
        }
    }
 
    /**
     * Modify the value of messageSourceUtil.
     * @param messageSourceUtil the messageSourceUtil to set
     */
    public void setMessageSourceUtil(MessageSourceUtil messageSourceUtil) {
        this.messageSourceUtil = messageSourceUtil;
    }
 
}

La classe ContactJdbcWritter.java définit le traitement du step. C’est cette classe qui regarde pour chaque personne si c’est son anniversaire ou pas, donc qui détermine si elle doit avoir un message ou pas.

Le code de cette classe est :

package org.scub.foundation.contact.manager.batch.contact.jdbc;
 
import java.util.Calendar;
 
import org.scub.foundation.framework.base.mapping.util.MapperDozerBean;
import org.springframework.batch.item.ItemProcessor;
 
public class ContactJdbcProcessor implements ItemProcessor {
 
    private MapperDozerBean mapperDozerBean;
 
    @Override
    public ContactJdbcDto process(ContactJdbcDto contact) throws Exception {
        if (contact != null && contact.getDateNaissance() != null) {
            final Calendar today = Calendar.getInstance();
 
            // Si aujourd'hui c'est l'anniversaire de la personne
            if (today.get(Calendar.DATE) == contact.getDateNaissance().get(Calendar.DATE)
                    && today.get(Calendar.MONTH) == contact.getDateNaissance().get(Calendar.MONTH)) {
                final Integer age = today.get(Calendar.YEAR) - contact.getDateNaissance().get(Calendar.YEAR);
                final ContactJdbcDto contactDto = mapperDozerBean.map(contact, ContactJdbcDto.class);
                contactDto.setAge(age.longValue());
                return contactDto;
            }
        }
        return null;
    }
 
    /**
     * Modify the value of mapperDozerBean.
     * @param mapperDozerBean the mapperDozerBean to set
     */
    public void setMapperDozerBean(MapperDozerBean mapperDozerBean) {
        this.mapperDozerBean = mapperDozerBean;
    }
 
}

Package org.scub.foundation.contact.manager.batch.contact.rmi

Ce package contient les classes du tasklet. Le tasklet affiche un message de non anniversaire pour toutes les personnes dont ce n’est pas l’anniversaire.

Ce package contient une unique classe qui recherche toutes les personnes dont ce n’est pas l’anniversaire et qui leur affiche un message.

Pour cela, elle fait appel aux services du projet core-implementation.

Le code associé à cette classe est :

package org.scub.foundation.contact.manager.batch.contact.rmi;
 
import java.text.DateFormat;
import java.util.Calendar;
 
import org.apache.log4j.Logger;
import org.apache.log4j.spi.RootLogger;
import org.scub.foundation.contact.manager.batch.util.MessageKeyUtil;
import org.scub.foundation.contact.manager.core.dto.ContactCriteresRechercheDto;
import org.scub.foundation.contact.manager.core.dto.ContactDto;
import org.scub.foundation.contact.manager.core.service.interfaces.ContactService;
import org.scub.foundation.framework.base.paging.RemotePagingCriteriasDto;
import org.scub.foundation.framework.base.paging.RemotePagingResultsDto;
import org.scub.foundation.framework.base.messagesource.MessageSourceUtil;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
 
/**
 * Tasklet pour souhaiter un joyeux non-anniversaire.
 */
public class ContactRmiTasklet implements Tasklet {
 
    private ContactService contactService;
 
    private MessageSourceUtil messageSourceUtil;
 
    private Logger logger = RootLogger.getLogger(ContactRmiTasklet.class);
 
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        final ContactCriteresRechercheDto critere = new ContactCriteresRechercheDto();
        critere.setExclureAnniversaire(true);
        final RemotePagingCriteriasDto criteria =
            new RemotePagingCriteriasDto(critere, 0, Integer.MAX_VALUE);
        final RemotePagingResultsDto listeNonAnniversaire = contactService.getContactsByCriteria(criteria);
 
        if (listeNonAnniversaire != null) {
            final Calendar today = Calendar.getInstance();
            for (ContactDto contactDto : listeNonAnniversaire.getListResults()) {
                if (contactDto != null) {
                    final Calendar birthday = Calendar.getInstance();
                    birthday.setTime(contactDto.getDateNaissance());
                    int age;
                    int yearDiff = today.get(Calendar.YEAR) - birthday.get(Calendar.YEAR);
                    birthday.set(Calendar.YEAR, today.get(Calendar.YEAR));
                    if (!birthday.after(today)) {
                        age = yearDiff;
                    } else {
                        age = Math.max(0, yearDiff - 1);
                    }
                    final String anniversaire = DateFormat.getInstance().format(contactDto.getDateNaissance().getTime());
                    final Object[] params = new Object[] {contactDto.getNom().toUpperCase(), contactDto.getPrenom(), anniversaire, age};
                    logger.info(messageSourceUtil.get(MessageKeyUtil.MESSAGE_JOYEUX_NON_ANNIVERSAIRE, params));
                }
            }
        }
        return RepeatStatus.FINISHED;
    }
 
    /**
     * Modify the value of contactService.
     * @param contactService the contactService to set
     */
    public void setContactService(ContactService contactService) {
        this.contactService = contactService;
    }
 
    /**
     * Modify the value of messageSourceUtil.
     * @param messageSourceUtil the messageSourceUtil to set
     */
    public void setMessageSourceUtil(MessageSourceUtil messageSourceUtil) {
        this.messageSourceUtil = messageSourceUtil;
    }
 
}

Package org.scub.foundation.contact.manager.batch.util

Ce package contient une unique classe, qui permet de gérer les messages d’anniversaire et de non anniversaire, nommée MessageKeyUtil.

Le code associé à cette classe est :

package org.scub.foundation.contact.manager.batch.util;
 
/**
 * Objet pour la gestion des messages.
 */
public final class MessageKeyUtil {
 
    /** Default constructor. */
    private MessageKeyUtil() {
    }
 
    /** Un message pour souhaiter un joyeux anniversaire. */
    public static final String MESSAGE_JOYEUX_ANNIVERSAIRE = "message.joyeux.anniversaire";
 
    /** Un message pour souhaiter un joyeux non anniversaire. */
    public static final String MESSAGE_JOYEUX_NON_ANNIVERSAIRE = "message.joyeux.non.anniversaire";
 
}

Les messages sont stockés dans le fichier message_fr_FR.properties du répertoire src/main/ressources.

Dans notre cas, ce fichier contiendra les deux lignes suivantes :

message.joyeux.anniversaire=Joyeux anniversaire {0} {1}. Vous êtes né(e) le {2} et vous avez {3} an(s).
message.joyeux.non.anniversaire=Joyeux non anniversaire {0} {1}. Vous êtes né(e) le {2} et vous avez {3} an(s).

Test l’application

Batch a besoin de stocker des valeurs en base de données. Il est donc nécessaire de créer ces tables. Vous trouverez dans conf/dev/ressources un fichier nommé initdb.sql qui contient le script sql pour le projet batch. Vous devez donc jouer ce script dans la base de données.

    • Connecter vous à PostgreSQL en tapant dans une console :
$ sudo su postgres
    • Connectez vous à la base de données contact-manager que vous avez créé lors de la création de l’îlot de service
$ psql contact_manager
  • Jouez le contenu du script dans PostgreSQL

Vous avez maintenant une application batch fonctionnelle qui utilise votre application noyau (appel des services).

Pour les tester, il suffit maintenant de les déployer sur un serveur d’application :

  • Ouvrez une nouvelle console et démarrez à Jetty
  • Placez vous à la racine du projet scub-foundation-my-contact-manager-core-implementation
  • Puis déployez le dans Jetty comme l’indique la copie d’écran ci-dessous :

    • Placez vous à la racine du projet scub-foundation-my-contact-manager-batch et publiez dans Jetty de la même manière que décrit ci-dessus
    • Ouvrez une nouvelle console et entrez la commande suivante afin d’ouvrir une JConsole
 $ jconsole
  • Dans la JConsole, sélectionnez le local process Jetty, puis cliquez sur Connect

  • Ouvrez l’onglet Bean et ouvrez le bean contactManagerBatch

  • Dans l’onglet opération, cliquez sur start afin de lancer le batch

  • Vous devez désormais voir apparaître dans la console de Jetty, le message d’anniversaire ou de non anniversaire de votre batch

Télécharger les sources

Les sources du projet Eclipse pour ce tutoriel sont téléchargeables sur Source Forge à l’adresse suivante.