I have joined Anti-IF Campaign

Struts 2, JPA 2, HTML5, Bootstrap Twitter en REST et CoC

Vous voulez développer une application Web orientée REST et Convention over configuration avec Struts 2, JPA 2, Twitter Bootstrap, alors suivez le guide.

Dans ce tutoriel, nous allons développer une application de gestion des livres d'une bibliothèque (Book).
Cet exemple est tiré du cas pratique utilisé comme base pour Telosys.
Ce cas paraît simpliste mais notre objectif est de créer une base technique, un socle permettant :
- de valider la stabilité et la compatibilité d'un ensemble d'outils (maven, struts2, JPA 2 ...)
- de mettre en pratique la philosophie REST en mettant à disposition des url orientées ressource
- de permettre à moindre coût d'ajouter d'autres éléments en un minimum de code et de configuration supplémentaires.



1ere étape : la mise en place de la structure du projet
Le projet sera géré par maven et on s'appuie sur l'archetype blank pour struts 2 :
http://struts.apache.org/2.2.3/docs/struts-2-blank-archetype.html

Le projet aura donc la structure suivante :
Chargement ....

On y retrouve les sources folders habituels ainsi que les répertoires des ressources de la webapp (css, js, img ...).

Nous allons modifier le pom.xml pour y ajouter les dépendances nécessaires au projet :
Chargement ....
Un petit maven update project et nous avons maintenant tout ce dont nous avons besoin.

Il ne nous reste qu'à configurer le fameux fichier web.xml pour y intégrer le filtre Struts :
Chargement ....
Bon franchement, rien à dire de particulier sur ce fichier.

2eme étape : la configuration de Struts
Nous nous sommes donné comme objectif de développer une application dans l'esprit REST et Convention over configuration.
La configuration devra donc être générique pour ajouter facilement d'autres éléments du domaine que les livres.
Pour interagir avec l'application, elle devra aussi mettre à disposition des url du type :
Chargement ....

Voici une première ébauche du struts.xml mettant en oeuvre une configuration générique :
Chargement ....
Ce qu'il faut noter :
L'essentiel de la configuration est en bas avec la déclaration d'une action générique (utilisation des Wildcard Mappings).
Un interceptor et de nombreuses options ont été définies pour que :
- les actions Struts soient appelées en http sans l'extension ".action" ou ".do"
- les autres ressources (css, js, images etc) soient toujours supportées
- une action inconnue aboutisse à une page "en construction" par défaut
- les exceptions soient catchées et traitées dans le result "Exception"
- un thème s'appuyant sur le twitter bootstrap soit appliqué (nous y reviendrons plus loin)
- une validation nous permettre de corriger certaines bugs de struts (nous y reviendrons plus loin)

Avec cette configuration, pour que votre action struts soit magiquement prise en compte et utilisable il suffira que :
- le package corresponde au domaine (book par exemple)
- que la classe action se nomme justement Actions

Si un élément de configuration vous semble flou, n'hésitez pas à m'envoyer un commentaire.

3eme étape : l'intégration du bootstrap twitter et l'adaptation du thème struts
Nous allons complèter l'arborescence du projet pour intégrer le bootstrap twitter
en ajoutant les fichiers css, javascript et les images comme suit :
Chargement ....

Il faut ensuite adapter le thème struts pour le bootstrap.
Pour cela, je n'ai pas pour l'instant trouver mieux que de dupliquer le thème de struts dans le
répertoire "/src/main/resources/template" et ensuite le modifier pour intégrer les ressources du bootstrap.
En intégrant le thème simple, on laisse ainsi le boostrap prendre en charge dynamiquement toute la mise en forme.

4eme étape : la persistance JPA 2
Notre projet va s'appuyer sur une base locale derby (that's why on a le derby.jar).
Dans les sources complètes du projet (voir en bas), vous trouverez un répertoire BookstoreDB_v2 qui contient la base de données ainsi que le launcher du serveur derby (start.bat).

La configuration de la persistance pour JPA 2 s'appuie sur un fichier : META-INF/persistence.xml.
Ce fichier définit le provider de persistance ainsi que les paramètres de connexion.
Pour cet exemple, nous utiliserons le provider d'Hibernate, et déclarerons la classe de notre domaine (org.demo.vo.bean.Book).
Voici ce que cela donne :
Chargement ....

5eme étape : les classes java de la couche de persistance
Maintenant que la configuration JPA 2 est en place, nous devons développer les quelques classes de la couche de persistance :
- VO : le bean org.demo.vo.bean.Book
Cette classe utilise les annotations JPA 2 pour rattacher le bean à la table.
Chargement ....
- GenericJpaDao : un ancêtre pour tous les DAO
Permet de centraliser les méthodes essentielles (crud) et ainsi de créer un Dao de domaine très light.
Chargement ....
- Book DAO : à l'ancienne, un utilitaire pour gérer la persistance de org.demo.vo.bean.Book
Rien à dire sur cette classe à part la méthode de recherche par l'exemple, le dao générique apporte plus de comportement.
Chargement ....
- EntityManagerHelper : un utilitaire basic pour la création de l'entityManager et la gestion des transactions
Le code se suffit à lui même.
Chargement ....

6eme étape : la couche service
Dans un esprit Separation of concerns, au dessus de la couche de persistance, nous allons mettre en place une couche service.
Ce sera typiquement la "coupe" de la transaction.
Cette couche sera composée d'une interface et d'une implémentation de service Book.
Chargement ....
Chargement ....
La couche service nous permet de mettre en oeuvre le crud minimal, elle peut être enrichie très facilement.

7eme étape : la couche de présentation
Pour la présentation, on doit créer deux pages en respectant les conditions suivantes :
- les pages doivent être placées dans un répertoire normalisé : /src/main/webapp/WEB-INF/pages/<domaine>/
- une page doit être créée pour afficher la liste des éléments : <domaine>List.jsp (exemple bookList.jsp )
- une page doit être créée pour afficher le détail d'un élément : <domaine>.jsp (exemple book.jsp )

Voici ce que cela donne pour Book.
bookList.jsp :
Chargement ....
book.jsp :
Chargement ....
Ce qu'il faut noter :
- les pages sont en HTML5
- le bootstrap twitter est utilisé pour la mise en forme
- les tags struts sont utilisés pour l'injection des données du Book
- pour corriger un bug sur les formats de nombres, nous forçons justement ce format (voir %{getText('my.format.number',{current.price})})
Les actions :
Toujours dans l'optique de rendre générique les comportements de base et permettre d'ajouter simplement un nouveau service, nous allons créer un ancêtre pour toutes les actions Struts :
Chargement ....
Cette classe, toujours dans l'esprit de Separation of concerns, implémente le crud et met en relation
la couche service et les pages créées précédemment en utilisant les génériques java.
Les différents messages sont internationalisés en faisant référence à un fichier properties (plus de détail ci-dessous).

Toutes les actions doivent suivre les conditions ci-dessous pour être prise en compte par la configuration générique :
- la classe action doit se nommer : Actions.java
- elle doit être dans un package nommé : org.demo.action.<domaine>
Exemple : org.demo.action.book.Actions

Cette action doit de plus hériter de la générique action détaillée ci-dessus afin de profiter des comportements déjà implémentés.
Voici l'action Struts de gestion des Book :
Chargement ....

8eme étape : derniers coups de cuillère à pot pour struts
Nous allons pour mettre en place la validation pour Book lors des actions d'enregistrement et de suppression.
Le fichier struts.xml permet d'activer cette fonction (voir interceptor-stack appliWebDefaultStack validation) mais une validation propre à chaque méthode de la classe action ne semble pas fonctionner correctement.
Pour corriger ce problème, j'ai dû surcharger le ValidatorManager (voir struts.actionValidatorManager dans le struts.xml).
La nouvelle classe (CustomActionValidatorManager) reprend le fonctionnement de la classe par défaut (com.opensymphony.xwork2.validator.DefaultActionValidatorManager) en adaptant le comportement de la méthode buildAliasValidatorConfigs(Class, String, boolean).
Nous n'avons alors plus qu'a créer des fichiers xml pour définir les conditions de cette validation.

Pour la suppression, nous allons créer le fichier : Actions-book-delete-validation.xml au même niveau de la classe Actions.java.
Ce nom est normalisé (voir http://struts.apache.org/2.x/docs/validation.html) ce qui permet par convention de le rattacher à un traitement (méthode delete de l'action).
Chargement ....
Pour la suppression donc, l'exemple est simple car on oblige seulement l'existence d'un id.

Pour la sauvegarde, nous allons créer le fichier : Actions-book-save-validation.xml au même niveau de la classe Actions.java.
Chargement ....
Je vous laisse découvrir les possibilités de validation.

9eme étape : configuration diverses
Nous conservons ici log4j en mettant en place un appender standard.
Chargement ....
Quant aux messages et libellés, nous avons deux niveaux de fichiers properties permettant l'internationalisation :
- un fichier propre à chaque action (exemple : org/demo/action/book/Actions.properties) contenant les messages pour l'action en cours - un fichier global (voir ci-dessous) contenant des messages et les formats de nombres pour toute l'application.
Chargement ....

Conclusion
Voila, il n'y a plus qu'à lancer l'application et profiter de la puissance des conventions et de la configuration générique pour ajouter à l'infini d'autres services.

Un grand merci à Laurent Guérin et Amine Bousta pour leurs précieux conseils.

Le code source complet :
https://github.com/asicfr/Struts2-Rest-Jpa-BootStap

Dans un prochain épisode, nous mettrons en place un générateur (basé sur Telosys Tools) pour automatiser tous ces développements et obtenir rapidement une application fonctionnelle.

Les références :
telosys
telosys-tools
telosys bookstore
twitter bootstrap/
struts
wildcard mappings
maven
JPA 2
Convention over configuration
Separation of concerns

Aucun commentaire:

Enregistrer un commentaire