18 septembre 2008
Imprimer ce billet

AppFuse par l’exemple

Les IDE d'antan - on pense tout particulièrement au vénérable JBuilder - proposaient un ensemble de "wizards", permettant de générer en quelques clics le squelette de classes, de composants voir d'applications complètes. Rien à voir, bien sûr, avec les ambitions démesurées du MDA : les wizards se contentaient de générer un ensemble de sources plus ou moins prêtes à l'emploi dans l'objectif d'accélérer certaines tâches de développement répétitives et peu valorisantes. Ruby on Rails a remis cette approche au goût du jour, et l'a baptisé du nom barbare de "scaffolding".

Le scaffolding fait le bonheur des rubyistes en leur permettant, au prix de l'exécution de quelques lignes de commandes, de disposer d'une application web opérationnelle, autorisant la manipulation élémentaire des données de leur modèle au travers d'écrans de saisie et de listes - charge au développeur d'enrichir ensuite le code généré pour implémenter un comportement plus sophistiqué.

Le projet AppFuse, aujourd'hui dans sa version 2.0, s'appuie sur les archétypes Maven 2 pour offrir aux développeurs Java des fonctionnalités équivalentes. Dans cet article, nous vous proposons une introduction par l'exemple à AppFuse.

Maven : rappels

Pour ceux qui ne sont pas encore familiers avec Maven, voici quelques rappels de base pour pouvoir suivre cet article.

Maven est un outil open-source de build pour les projets Java. Il a été conçu pour simplifier les tâches difficiles du processus de build. Maven offre la possibilité de démarrer un projet en utilisant des squelettes d'application. Ces squelettes, dans la terminologie Maven, sont appelés "archétypes". Grâce à eux, une ligne de commande permet de démarrer avec une hiérarchie de répertoire standardisée (sources, resources, builds).

Tous les archétypes Maven comportent un fichier "pom.xml" contenant toutes les directives de construction du projet, ses dépendances et différents plugins accessibles. Les plugins Maven couvrent, entre autres, des outils d'analyse de code, de formatage et de génération de rapport.

Pour plus d'informations sur ces notions, je vous conseille de jeter un oeil sur les sites suivants :

AppFuse

Concept

Initialement créé par Matt Raible, AppFuse est un projet open source pour développer des applications web J2EE. Il permet aux développeurs de démarrer rapidement et facilement en utilisant des technologies open-source telles que Spring, Hibernate et différents frameworks web (Struts2, Spring MVC, Tapestry, ...).

AppFuse est un ensemble de scripts de génération de code permettant de faire le travail le plus pénible très rapidement, typiquement les écrans CRUD (Create, Read, Update, Delete) de votre modèle de données.

Utilisation

Initialement conçu avec Ant, Appfuse s'appuie, depuis sa version 2, sur Maven via des archétypes et des plugins de génération de code.

Archétypes Maven

Les différents archétypes fournis par le projet AppFuse couvrent différents frameworks Web, couplés avec Hibernate et Spring :

AppFuse propose pour tous ces outils de généré un projet d'application simple ou un projet multi-modules contenant un projet noyau et des projets modulaires. Il est également possible de générer uniquement le backend d'application. Vous pourrez brancher dessus votre framework préféré.

Chaque archétype AppFuse crée une application complète comprenant :

  • la gestion de la sécurité
  • une interface d'administration des utilisateurs et des rôles.
  • plusieurs thèmes CSS
  • la création automatique de la base de données
  • la population automatique de la base avec un jeu de données de test.

Plugins de génération de code

Une fois votre projet démarré avec l'archétype de votre choix, vous pourrez ajouter vos beans de données et les mapping Hibernate associés selon la méthode que vous préférez (annotations ou fichiers hbm.xml).

Une fois votre modèle posé, le plugin Maven "appfuse:gen" s'occupera pour vous de créer les classes de DAO, Managers et vues correspondantes pour obtenir les écrans de gestion élémentaires (CRUD).
Chaque lancement du plugin se fait sur une classe du modèle de données et fournit :

  • une vue avec un tableau triable contenant l'ensemble des instances persistées en base
  • un formulaire de saisie/suppression
  • un jeu de données pour les tests
  • les DAO et Managers associés

Conformément aux principes du scaffolding, la génération de code et de configuration possède ses limites et il est souvent nécessaire d'effectuer quelques corrections manuelles lorsque le modèle de données est un peu évolué.

Par exemple, dans le cas de relations ManyToOne, le formulaire de saisie d'un bean doit comporter une liste permettant de sélectionner avec quel objets faire le lien. Ce cas nécessite de faire des modifications dans l'Action Struts pour que la liste s'affiche effectivement dans le formulaire web. Nous allons voir ce genre de manipulation dans la partie pratique.

Hands-on : Hibernate, Spring, Struts2

Un bon exemple valant souvent mieux qu'un long discours, nous vous proposons de créer pas à pas une application web simple, permettant de saisir des constructeurs automobiles et des voitures liées à ces constructeurs.

L'environnement technique est le suivant :

Démarrage du projet

Création de notre squelette de projet avec l'archétype appfuse-basic-struts :

mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes
-DarchetypeArtifactId=appfuse-basic-struts
-DremoteRepositories=http://static.appfuse.org/releases
-DarchetypeVersion=2.0.1
-DgroupId=fr.xebia.blog.voiture -DartifactId=voiture-app

Un répertoire "voiture-app" contenant notre squelette applicatif vient d'apparaître. Les amateurs d'Eclipse pourront ajouter :

cd voiture-app

mvn eclipse:eclipse

... pour que Maven créé les fichiers indispensables à Eclipse pour importer le projet.

Les archétype AppFuse placent la majeure partie du code de l'application en dépendances dans le pom.xml.
Pour pouvoir modifier le comportement de l'application et avoir tout sous les yeux, nous devons couper ces dépendances avec AppFuse et rapatrier le code.

Cela se fait très simplement en lançant :

mvn appfuse:full-source

Écriture du modèle

Ajoutons maintenant notre modèle de données. Pour plus de simplicité, nous allons y intégrer directement les annotations utiles à la persistance. Les classes que l'on souhaite passer au générateur de code doivent se trouver directement dans le package "model" du projet. Dans notre cas il s'agit de "fr.xebia.blog.voiture.model".

Classe : ConstructeurAutomobile

src/main/java/fr/xebia/blog/voiture/model/ConstructeurAutomobile.java

package fr.xebia.blog.voiture.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class ConstructeurAutomobile {

  private Long id;
  private String nom;

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(length=50)
  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

Classe : Voiture

src/main/java/fr/xebia/blog/voiture/model/Voiture.java

package fr.xebia.blog.voiture.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Voiture {

  private Long id;
  private String nom;
  private ConstructeurAutomobile constructeur;

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(length=50)
  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  @ManyToOne
  @JoinColumn(name="constructeur_id")
  public ConstructeurAutomobile getConstructeur() {
    return constructeur;
  }

  public void setConstructeur(ConstructeurAutomobile constructeur) {
    this.constructeur = constructeur;
  }
}

Génération des CRUD

Maintenant que nous disposons des classes, nous pouvons lancer la génération des écrans CRUD :

mvn appfuse:gen -Dentity=ConstructeurAutomobile

mvn appfuse:gen -Dentity=Voiture

N.B. : Si vous omettez l'option -Dentity le nom de la classe ciblée est demandé en prompt.

Le générateur de code comporte cependant une lacune. Les classes sont bien ajoutées aux mappings Hibernate, mais pas pour les tests unitaires. Par conséquent, un mvn test tombera en erreur.

Le palliatif à cela est de copier les lignes de mapping dans :

src/main/resources/hibernate.cfg.xml

<hibernate-configuration>
    <session-factory>
        <mapping class="fr.xebia.blog.model.User"/>
        <mapping class="fr.xebia.blog.model.Role"/>
<!-- Les deux lignes suivantes nous intéressent -->
        <mapping class="fr.xebia.blog.voiture.model.ConstructeurAutomobile"/>
        <mapping class="fr.xebia.blog.voiture.model.Voiture"/>
<!-- Stop, pas plus loin, deux lignes seulement -->
    </session-factory>
</hibernate-configuration>

et de les coller dans le fichier src/test/resources/hibernate.cfg.xml pour garder ces deux fichiers en phase.

Vous pouvez le vérifier par vous même en lançant :

mvn clean test -P hsqldb

{quote}
Plusieurs profils de base de données sont disponibles dans le pom.xml fourni par AppFuse (MySQL, PostgreSQL, Oracle, etc). Par souci de simplicité nous utilisons ici HSQLDB.
{quote}

Support du lien ManyToOne

Le générateur AppFuse, bien que pratique, n'est pas omnipotent. Nous avons donc quelques manipulations à réaliser manuellement pour que le formulaire de saisie d'une Voiture soit complet.

Dans le fichier src/main/webapp/WEB-INF/pages/voitureForm.jsp vous pouvez voir cette ligne :

<s:select name="voiture.constructeur.id" list="constructeurList" listKey="id" listValue="id"></s:select>

Il s'agit du tag Struts qui affiche la liste de sélection des constructeurs. Cependant, la classe VoitureAction, générée, ne fournit pas cette liste à la JSP, qui restera en conséquence désespérément vide à l'exécution. Notons également que par défaut, AppFuse affiche l'identifiant de l'entité dans la liste - ce qui ne correspond qu'exceptionnellement au besoin. Remplaçons la valeur de l'attribut listValue du tag select par "nom". Ainsi, le nom du constructeur sera présenté dans la liste de sélection, plutôt que son identifiant.

Attaquons-nous au support de cette fameuse liste. Nous allons rajouter ceci au fichier :

src/main/java/fr/xebia/blog/voiture/webapp/action/VoitureAction.java

[...]

import fr.xebia.blog.voiture.model.ConstructeurAutomobile;

[...]

  private GenericManager constructeurAutomobileManager;

  public void setConstructeurAutomobileManager(GenericManager constructeurAutomobileManager) {
    this.constructeurAutomobileManager = constructeurAutomobileManager;
  }

  public List getConstructeurList () {
    return (List) constructeurAutomobileManager.getAll();
  }

[...]

Comme l'attribut que nous rajoutons respecte les conventions de nommage et que la configuration Spring générée par AppFuse est correcte, l'injection de dépendances se fait automatiquement pour le constructeur AutomobileManager.

La méthode getConstructeurList, quant à elle, sera appelée par la JSP pour obtenir le contenu de la liste de sélection.

Démarrage

Nous pouvons maintenant effectuer un premier lancement de notre application grâce au plugin Jetty de Maven. :

mvn jetty:run-war -P hsqldb

Je vous laisse faire le tour du propriétaire en allant avec votre navigateur web sur localhost:8080 pour vous mettre l'eau à la bouche, voici une liste non exhaustive de tout ce que nous avons obtenu en une demi-heure de travail :

  • filtres de sécurité
  • gestion des utilisateurs en base
  • envois de mail en cas d'oubli d'un mot de passe ou de création de compte
  • écrans CRUD pour les constructeurs automobiles
  • écrans CRUD pour les voitures
  • un jeu de données inséré à chaque lancement des tests
  • export des listes de constructeurs et de voitures en CSV, XLS, PDF ou XML (Merci à DisplayTag)
  • une CSS propre (ça fait toujours plaisir)
  • une foule de plugins très utiles placés dans le pom.xml : Cobertura, PMD, DBunit et d'autres
  • le support de l'internationalisation

Et j'en oublie.

Conclusions

AppFuse est, à mon avis, un excellent plugin Maven pour démarrer un projet d'application Web classique. Il n'est pas très polyvalent mais permet de se libérer d'une part assez fastidieuse du développement web : l'écriture des CRUD.

Le squelette d'application standardisé permet de s'y retrouver rapidement quand on gère plusieurs applications et de respecter un bon découpage des fichiers de configuration.

En fait ce plugin en fournit presque trop : trois semaines après je trouvais encore des morceaux de code inutilisés provenant du squelette, et qui m'ont facilité la vie, notamment pour l'exposition de WebServices grâce à XFire.

À mon avis un très bon outil, bien ciblé, à essayer.

Quelques ressources complémentaires :