Personnaliser Spring Batch Admin

spring-batch-admin-screenshotPour rappel, Spring Batch Admin est une console de supervision des traitements par lots implémentés avec Spring Batch. En plus d’un frontal web, elle offre une API JSON et expose des métriques via JMX.
Bien que dépendant du projet Spring Batch, Spring Batch Admin dispose de son propre repo GitHub et de son propre cycle de vie. Cet article se base sur la version 2.0.0.M1 sortie en janvier 2015.
Développé en Spring MVC et composé de 3 JARs, Spring Batch Admin peut aussi bien être intégrée dans une application existante que déployée dans son propre WAR.

Ouvert aux extensions, Spring Batch Admin a tout pour devenir un véritable serveur de batchs : monitoring, chargement et mise à jour à chaud de la configuration des jobs, ordonnancement, exécution de jobs sur réception de fichiers …
En 3 ans, c’est la seconde fois que je suis amené à personnaliser Spring Batch Admin. Le manuel de référence fourmille d’explications. Ce billet recense quelques informations complémentaires qui, je l’espère, pourront vous être utiles :

  • Transformer Spring Batch Admin en une application auto-exécutable embarquant sa propre base de données et son propre conteneur de servlet
  • Personnaliser l’interface d’admin
  • Adapter les templates FreeMarker au besoin métier
  • Exécuter un job suite à la réception d’un fichier
  • Router un message en fonction du résultat de l’exécution d’un job
  • Ajouter un contrôleur REST

Pré-requis

Certaines classes utilisées dans ce billet sont issues du projet spring-batch-toolkit hébergé sur GitHub. Disponible sur Maven Central, n’hésitez pas à l’utiliser sur vos projets.

Créer sa propre application

Pour créer from scratch une application Spring Batch Admin, le plus simple consiste à s’inspirer de l’application web d’exemple spring-batch-admin-sample : pom.xml maven, web.xml, index.jsp, fichiers de configuration XML et properties pourront être repris puis adaptés.

Pour stocker l’historique de l’exécution des jobs dans une base de données HSQLDB, la recopie des fichiers batch-hsql.properties et business-schema-hsqldb.sql s’avère nécessaire. Remplacer hsql par le nom d’une autre base supportée.

A noter que l’IHM devrait être retirée de la version finale de Spring Batch Admin 2.0.0 et déplacée dans un projet sample séparé. Il sera donc alors nécessaire de reprendre les templates FreeMarker, les ressources statiques et le code Java lié à la UI.

Personnaliser le nom de l’application et de la société

Les différents libellés affichés dans l’en-tête et le pied page de l’application Spring Batch Admin peuvent être chargés depuis un ressource bundle messages.

Pour se faire, créer un fichier messsages.properties dans le répertoire src/main/resources de votre projet. Puis ajouter et personnaliser les propriétés suivantes :

Créer  ensuite le fichier de configuration Spring src/main/resources/META-INF/spring/batch/servlet/override/manager-context.xml et déclarer le bean messageSource :

Personnaliser le logo

Déployer Spring Batch Admin avec le logo de SpringSource, c’est bien. Le déployer avec le logo de votre société ou de votre client, c’est mieux.
Pour changer de logo :

  1. Copier l’image spring-batch-admin-resources-2.0.0.M1.jar!/META-INF/images/header-right.png dans le répertoire webapp/images/header-right.png
  2. Ouvrir ce fichier avec votre éditeur préféré (Gimp, Paint.NET …) et remplacer le logo SpringSource par celui de votre choix. Attention aux bords arrondis.

Paramétrer le nombre de jobs exécutés en parallèle

Pour exécuter les jobs, Spring Batch Admin s’appuie sur la classe SimpleJobLauncher de Spring Batch. Son pool de threads est dimensionné à 6 threads. De ce fait, un maximum de 6 jobs peuvent être exécutés simultanément. Pour augmenter ou diminuer le nombre de thread, il est nécessaire de redéfinir le bean  jobLauncherTaskExecutor

Ajouter un fichier META-INF/spring/batch/override/execution-context.xml contenant la définition de de bean :

Puis ajouter la propriété batch.job.threadpool.size dans le fichier batch-<xxx>.properties :

Remarque : un autre moyen de contrôler le nombre de traitements réalisés en parallèle est d’utiliser le poolTaskExecutor déclaré par Spring Batch Admin (mais non utilisé par ce dernier). C’est particulièrement vrai si vos Jobs utilisent des techniques de parallélisation tels le partitionnement ou la parallélisation de steps. Mutualiser le pool de threads sur plusieurs jobs permet un dimensionnement optimal : les ressources serveur seront ainsi réparties en fonction de la charge globale. Lorsqu’un seul job est exécuté, ce dernier pourra profiter de l’intégralité des threads mis à disposition du serveur de batch (600 par défaut).

Une base de données auto-installable

Pour fonctionner, Spring Batch Admin nécessite une base de données. C’est la base qui lui permet de suivre l’exécution des batchs. Tous les jobs à monitorer, qu’ils soient exécutés dans Spring Batch Admin ou depuis un autre serveur, doivent utiliser un JobRepository persistant. Et ceci, même si vos jobs ne font que de la manipulation de fichiers.

Si vos batchs n’ont pas besoin de base de données pour fonctionner, la création de la base peut être confiée à Spring Batch Admin lors de son démarrage.
Nativement, Spring Batch Admin ne sait pas automatiquement détecter si la base existe. L’utilisateur doit lui spécifier ou non de (re)créer la base via la propriété batch.data.source.init exploitée dans le fichier /META-INF/spring/batch/bootstrap/manager/data-source-context.xml de spring-batch-admin-manager-2.0.0.M1.jar

En redéfinissant le bean initialize-database, Spring Batch Admin peut être configuré pour ne créer le schéma que s’il n’existe pas. L’exécution du script de destruction du schéma est retirée et on précise à Spring d’ignorer les erreurs. Ainsi, si une table existe, l’exécution du CREATE TABLE ne fera pas échouer l’exécution du script.

En pratique, créer dans dans votre web app un fichier META-INF/spring/batch/override/data-source-context.xml contenant le bean suivant :

Pour supprimer une base, supprimer le répertoire racine contenant ses fichiers.

Packager Spring Batch Admin

Au début de ce billet, nous avons vu comment distribuer Spring Batch Admin dans son propre WAR ou comment l’inclure dans une application web existante.
Se posera ensuite la question de l’installation. L’installation de la base de données a déjà été abordée dans un paragraphe précédent. Concentrons nous à présent sur le conteneur de servlet. Au lieu de devoir installer préalablement sur le serveur un conteneur web comme Jetty ou Tomcat, je vous propose de l’embarquer directement dans le binaire de Spring Batch Admin. A la manière de Spring Boot, il est possible de démarrer Jetty depuis un simple main.
Pour y arriver, je vous invite à suivre le tutoriel Embarquer Jetty dans une web app récemment publié sur ce blog.

Exécuter un job suite à la réception d’un fichier

Spring Batch Admin offre une intégration poussée de Spring Integration avec Spring Batch. Le chargement à chaud de la configuration XML d’un nouveau job utilise précisément un adaptateur de type file (<file:inbound-channel-adapter>) pour détecter la mise à disposition d’un nouveau fichier. Pour davantage de détails, je vous invite à consulter le fichier META-INF/spring/batch/bootstrap/integration/configuration-context.xml du module spring-batch-admin-manager.

La UI et les endpoints REST de Spring Batch Admin offrent la possibilité d’uploader un fichier qui sera déposé dans le pusblish-subscribe-channel nommé input-file et déclaré dans le fichier META-INF/spring/batch/bootstrap/integration/file-context.xml du module spring-batch-admin-manager. Charge au développeur de s’abonner au channel pour, par exemple, déclencher un job.

En combinant ces 2 fonctionnalités, il est possible de déclencher l’exécution d’un job Spring Batch lors de la réception d’un fichier dans un répertoire donné. Ce cas d’utilisation est particulièrement intéressant lorsque le job exécuté prend en entrée le fichier reçu.
Pour exemple, on peut imaginer un batch chargé de prendre un fichier CSV et d’insérer chaque ligne dans une base de données NoSQL. Le chemin complet du fichier est passé au batch à l’aide du paramètre input.file. Le chemin du fichier est préfixé par file:// Le nom du job à déclencher est déduit du nom du fichier à partir, par exemple, d’une convention de nommage.

La première étape consiste à créer le fichier META-INF/spring/batch/override/admin-context.xml et à déclarer toute une série d’espaces de nom :

La seconde étape consiste à brancher un <file:inbound-channel-adapter> sur le channel input-files existant. Ainsi, que ce soit par un upload de fichier via HTTP ou un transfert de fichier par SFTP, CFT ou rsync, la suite du traitement du fichier est identique.

Le corps du message déposé dans le channel input-files est de type File.

Une 3ième étape consiste à transformer ce fichier en une demande d’exécution de job, à savoir un objet de type JobLaunchRequest (appartenant au module spring-batch-integration).

Une chaîne de 2 endpoints est nécessaire :

L’auto-wiring est activé pour faciliter l’injection de beans dans la classe FilenameToJobLaunchRequestAdapter. En interne, cet adaptateur fait appel à un bean implémentant l’interface FileToJobNameConverter qui est capable de déduire le nom du job à exécuter en fonction du nom du fichier.

Voici un exemple d’implémentation :

A l’issu de l’exécution du FilenameToJobLaunchRequestAdapter, une instance de JobLaunchRequest est créée et envoyée sur le channel. Fourni par Spring Batch Admin, le transformeur LastJobParametersJobLaunchRequestEnhancer complète les paramètres de lancement du job en reprenant ceux utilisés lors de la dernière exécution du job.
L’infrastructure de Spring Batch Admin prend ensuite la relève : récupérant le JobLaunchRequest depuis le channel job-requests, elle fait appel à un SimpleJobLauncher pour exécuter immédiatement le job. Une instance de JobExecution est alors déposée dans le channel job-operator.

Attendre la fin de l’exécution d’un batch

La classe SimpleJobLauncher délègue l’exécution  des jobs à un pool de threads. Elle rend donc la main avant la fin de l’exécution du job.
Dans la milestone 2.0.0-M1 de Spring Batch Admin, les messages déposés dans le channel job-operator sont simplement loggés. Un TODO présage que, dans une prochaine version, Spring Batch Admin proposera de réaliser des traitements en fonction de l’exécution du batch. Extrait de la configuration META-INF/spring/batch/bootstrap/integration/launcher-context.xml :

En attendant, la classe JobExitStatusRouter de spring-batch-toolkit permet de router le message dans 2 channels en fonction du code de retour du job (ExitStatus) :

Pour accéder au code de retour du job, la classe JobExitStatusRouter attend la fin de son exécution. L’implémentation est très sommaire puisqu’elle utilise la technique du pooling pour interroger à intervalle réguler le statut du job.
Un mécanisme de notification aurait été préférable. Mais à ma connaissance, Spring Bach n’offre pas nativement une telle possibilité.

Envoi d’un mail en cas d’erreur

Lorsque le batch tombe en erreur, si ce dernier ne propose pas déjà un système d’alertes, il est possible d’envoyer un mail à l’équipe en charge de sa supervision.
Disponible dans le projet spring-batch-toolkit, la classe JobExecutionToMailOutTransformer permet de construire le corps du mail à partir du JobExecution récupérée dans le channel job-error. Est ensuite utilisé les endpoints du module spring-integration-mail pour compléter le mail puis l’envoyer :

Personnaliser un template JSON

En fonction des besoins métiers, il est parfois nécessaire de devoir modifier ou compléter la réponse d’un service REST de Spring Batch Admin.
Qu’elles soient en RSS, XML ou JSON, les réponses sont templatisées avec FreeMarker.
En attendant la prise en compte du ticket BATCHADM-223, j’ai par exemple été contraint de transformer une map en un array. Issu du JAR spring-batch-admin-manager-2.0.0-M1.jar, le fichier org/springframework/batch/admin/web/manager/jobs/json/executions.ftl a été dupliqué puis renommé en executions-custom.ftl. Il a été placé dans un package identique.

Une fois le template modifié, la redéfinition du bean jobs/executions.json a été réalisé dans le fichier /META-INF/spring/batch/servlet/override/manager-context.xml :

Empêcher l’exécution simultanée d’un même job

Précédemment, nous avons vu comment la réception d’un fichier peut déclencher l’exécution d’un job. Mais que se passe-t-il lorsque 2 fichiers sont reçus et que ces 2 fichiers déclenchent le même job ? Et bien 2 instances du job sont créés puis exécutées en parallèle. Ce comportement peut introduire des effets de bord. Il peut alors être nécessaire de sérialiser le traitements de ces fichiers.

Comme son nom l’indique, la classe AcceptOnceFilePerJobListFilter du projet spring-batch-toolkit permet de n’exécuter à la fois qu’une seule instance du même job. Elle s’appuie sur l’interface FileToJobNameConverter utilisée précédemment. Le nom du job que le fichier va déclencher est conservé en mémoire.

L’attribut filter du <file:inbound-channel-adapter> doit alors être paramétré de la manière suivante :

Une fois l’exécution du job terminée, il est nécessaire de notifier le bean acceptOnceCatalogListFilter afin qu’il puisse de nouveau laisser passer les fichiers traités par ce job. C’est le rôle de la classe RollbackProcessedFileServiceActivator.

Attendre la fin du chargement de la configuration XML des Jobs

Lorsque Spring Batch Admin démarre, les fichiers préalablement déposés dans le répertoire /data/sas-in sont analysés par l’inbound-channel-adapter alors que la configuration XML du job chargé de les traiter n’est pas encore chargé. Le fichier tombe alors en erreur et est déplacé dans le répertoire /data/sas-error

Pour remédier à ce problème, une solution consiste à démarrer manuellement le bean de type inbound-channel-adapter du « Root WebApplicationContext » initié par le ContextLoaderListener.
Pour se faire, la propriété auto-startup doit être positionnée à false et un id doit être renseigné :

Pour chaque job, Spring Batch Admin crée un contexte Spring. Qui plus est, le DispatcherServlet de Spring MVC déclaré dans le web.xml crée également un contexte applicatif enfant du « Root WebApplicationContext » Au total, N+2 contextes Spring sont créés.

On démarre le bean inbound-channel-adapter une fois l’ensemble des contextes initialisés.  Le bean ServerStartEventHandler s’abonne aux évènements de type ContextRefreshedEvent émis par le conteneur Spring à chaque fois qu’un contexte applicatif est initialisé ou rafraichit :

Au cours du démarrage, la méthode onApplicationEvent est appelée autant de fois que de contextes. Le nom du contexte Spring MVC qui est le dernier chargé contient le nom du servlet « Batch Servlet ». 

Ajouter un contrôleur REST

Spring Batch Admin propose un frontal REST permettant d’accéder à des ressources au format HTML, RSS et JSON. Par exemple, un GET sur le chemin /jobs/{jobName}/executions.json listera l’historique des exécutions d’un job. De par l’extension, les données échangées sont au format JSON.
Ouvert aux extensions, Spring Batch Admin permet d’ajouter ses propres ressources REST.

La première étape consiste à ajouter un contrôleur Spring MVC respectant les propriétés suivantes :

  • Hériter de la classe abstraite AbstractBatchJobsController
  • Etre déclaré en tant que contrôleur REST via l’annotation @RestController
  • Définir un chemin d’accès racine par l’annotation @RequestMapping(« /<nom ressource> »)
  • Ajouter autant de handlers de requêtes HTTP que souhaité

Bien que tous les contrôleurs REST de Spring Batch Admin les utilisent, l’utilisation de Spring Data et Spring HATEOS est optionnelle.

Afin que ces nouvelles API soient connues des utilisateurs et apparaissent sur la page d’accueil, une seconde étape consiste à les déclarer dans un fichier properties normalisé. La clé contient le verbe HTTP et l’URI de la ressource. La valeur correspond au commentaire affiché sur la page d’accueil.
Voici un exemple de fichier mycustom-json-resources.properties :

La 3ième et dernière étape consiste à déclarer le contrôleur et le fichier properties dans le fichier META-INF/spring/batch/servlet/override/controller-context.xml :

Le bean jsonResources fournit par Spring Batch Admin est ici redéfini afin de prendre en compte notre fichier properties personnalisé.
Le nom du répertoire META-INF/spring/batch/servlet/override est prédéfini par Spring Batch Admin. Ce dernier assure que les fichiers de configuration Spring s’y trouvant seront chargés après les siens, permettant ainsi au développeur de redéfinir des beans et/ou d’en ajouter.

Conclusion

Afin de pouvoir personnaliser de manière avancée Spring Batch Admin, une appropriation de son code source est nécessaire. Il est en effet fréquent de devoir identifier les beans qui devront être redéfinis.

Pour la version 2.0.0, l’équipe de Spring Batch Admin ambitionne de déplacer le frontal web dans une application démo. Une fois réalisée, certaines explications données dans ce billet seront obsolètes. En contre partie, ce changement d’architecture devrait simplifier la personnalisation de l’interface utilisateur et permettre, par exemple, l’internationalisation des IHM.

Depuis janvier 2015 et la release de la 2.0.0-M1, le repo github Spring Batch recense peu d’activités. Le chemin vers la version 2.0.0 semble donc encore loin. Au cours de mes développements, j’ai soumis 7 pull request et 8 tikets Jira. Certaines ont dors et déjà acceptées pour la version 2.0.0. D’autres restent à valider et à planifier. J’ai hâte de les retrouver et de pouvoir ainsi simplifier mon code.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *