Bien débuter avec Struts 2

K. E. Kaci

Dans cet article, il sera question de décrire les fonctionnalités du framework Struts 2.x. Tout au long des chapitres qui suivent, l'aspect pratique sera mis en avant. Ainsi, nous allons nous baser sur un exemple concret pour mieux débuter. Même si nous n'aborderons pas tous les niveaux du framework, nous allons détailler certains points et expliquer comment les mettre en œuvre. ♪♪
Commentez

- Titre : Bien débuter avec Struts 2

- Auteur : K. E. Kaci

- Parution : 4 juillet 2010

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Historique de Struts 2

- III. Installation de Struts 2

- IV. Fonctionnement de Struts 2

- V. Mise en pratique

- VI. Débogage et traçage de l'application

- VI-A. La balise <:debug/>

- VI-B. La méthode setActive()

- VI-C. L'option debug

- VII. Fichier de properties ou ne rien écrire en dur

- VIII. Écrire des applications multilingues

- IX. Validation des entrées

- IX-A. Validation de base de Struts 2

- IX-B. Validation avec conversion

- IX-C. Bien gérer ses messages d'erreur et de succès :

- IX-D. Écrire son propre validator

- X. Les intercepteurs

- XI. Interaction de Struts 2 avec une base de données

- XI-A. Installation de HSQLDB

- XI-B. Création du modèle DAO

- XII. Struts 2 et Ajax

- XIII. Téléchargements

- XIV. Pour aller plus loin

- XV. Remerciements

I - Introduction

Tout au long de l'article, je vais présenter un exemple concret, mais basique, de gestion des développeurs. À travers cet exemple, je vais décrire comment mettre à jour les données des développeurs en utilisant Struts 2. Les exemples seront décrits d'une manière progressive, allant d'un simple message de Struts jusqu'à la mise en place d'un design pattern MVC en passant par la mise en pratique des principes du fonctionnement, notamment les actions, le paramétrage, l'internationalisation, la validation et la mise en place des intercepteurs et enfin l'interaction de Struts avec la technologie Ajax.

II - Historique de Struts 2

Même si Struts 1.X demeure un standard dans les développements, Struts 2.X reste un framework plus puissant en offrant plus de fonctionnalités et de souplesse. Struts 2.x n'a de commun avec Struts 1.x qu'une partie du nom. Contrairement à ce qu'on peut penser, Struts 2 n'est pas une extension ou une autre version de Struts 1, mais une refonte profonde des bases du framework.
Struts 2 est un résultat d'association entre Struts 1 et Webwork. En effet, les deux frameworks ont été fusionnés pour créer le framework Struts Titanium qui devient rapidement Struts 2 apportant la puissance du Webwork et la popularité de Struts 1.

III - Installation de Struts 2

Pour installer Struts 2, il suffit de télécharger le zip sur le site d'Apache : http://struts.apache.org/download.cgi#struts2181 puis de le dézipper dans un répertoire et récupérer le contenu du répertoire lib/ :

Librairies Struts 2

Les outils utilisés dans cet article sont :
IDE Eclipse ;
Tomcat 6.0 ;
Struts 2.1.8.1 ;
Ubuntu Karmic.

Nous allons commencer par créer un Dynamic Web Project sous Eclipse nommé GestionDeveloppeur.
Créez les éléments suivants :

- package com.developpez.action ;

- répertoire classes dans WEB-INF/ ;

- répertoire jsp dans WebContent ;

- répertoire lib dans WEB-INF (s'il n'existe pas).

Nous n'allons pas détailler dans cet article comment installer les outils nécessaires notamment Eclipse et Tomcat ou l'environnement Java. Il y a beaucoup d'articles sur le site qui évoquent ces points.

Copiez l'ensemble des librairies de Struts 2 dans le répertoire lib, puis configurez Build Path en procédant par le menu Projet/Propriétés/Java Build Path/Librairies/Ajouter les jars. Enfin, parcourez la structure du projet jusqu'au répertoire lib.

IV - Fonctionnement de Struts 2

Struts fonctionne par configuration via des fichiers XML. Les actions sont décrites dans un fichier réservé aux actions qui porte par défaut le nom struts.xml. Nous allons parler d'autres fichiers dans les chapitres suivants.
Struts fonctionne aussi sans ces fichiers, c'est ce qu'on nomme zéro configuration. Tout se base sur des annotations dans les classes. Ce principe ne sera pas décrit dans cet article. Les chapitres suivants seront consacrés aux développements via des fichiers de configuration.

Fonctionnement de Struts 2

Vous pouvez démarrer les développements en vous basant sur le contenu du répertoire « struts2-blank-<version> ».

On commence par créer un fichier nommé struts.xml dans le répertoire WEB-INF/classes, puis copier-coller ces quelques lignes.

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />

<package name="com.developpez.actions"  namespace="/" extends="struts-default">
<!-- Action de l'action de référence -->
<default-action-ref name="saisir_Developpeur"/>

<action name="saisir_Developpeur">
<result>/jsp/saisir_Developpeur.jsp</result>
</action>
</package>
</struts>

L'en-tête de struts.xml décrit la déclaration du fichier, comme tout format xml. Ensuite, vient la balise struts qui contient d'autres balises. Struts utilise la notion de package comme les projets Java.
L'action est déclarée par le mot clé action, suivi du nom de la classe qui l'interprète, puis du nom de la méthode de la classe d'action, et enfin des JSP où seront redirigés les résultats. La balise action peut aussi contenir d'autres éléments, nous allons voir cela au fur et à mesure.

Dans notre exemple, l'action saisir_Developpeur n'a pas de classe d'action, cela est possible. Le résultat de l'action sera redirigé vers la JSP saisir_Developpeur.jsp
La balise result peut avoir plusieurs valeurs pour l'attribut name, comme success, input, none… Si l'élément n'est pas précisé, ce sera success par défaut.

Saisir_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix ="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Saisir un Développeur</title>
</head>
<body>
<center><h2>It works...</h2>

</center>
</body>
</html>

Maintenant il faut que le conteneur retrouve le fichier struts.xml, pour cela il faut ajouter les lignes suivantes dans web.xml. Il s'agit des balises filter et filter-mapping.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>GestionDeveloppeur</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

Démarrez Tomcat et lancez le lien suivant : http://localhost:8080/GestionDeveloppeur/saisir_Developpeur.action

Aperçu dans le navigateur

V - Mise en pratique

Dans ce chapitre, nous allons mettre en pratique les éléments essentiels du framework. Nous allons simuler la gestion des développeurs sur un site.
L'administrateur du site pourra saisir les informations concernant un développeur, notamment : l'identifiant, le pseudo, le mail, le code postal et la date d'inscription. Pour cela, nous avons besoin de créer une page JSP saisir_Developpeur.jsp pour contenir le formulaire d'identification, et enregistrer_Developpeur.jsp pour afficher le résultat après une saisie.

saisir_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix ="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Saisir Developpeur</title>
</head>
<body>
<center><h2>Bienvnue sur le site developpez.com</h2>

<div id="formulaire">
<s:form method ="post" action="enregistrer_Developpeur">

<s:textfield name="identifiant" id="identifiant"
label="Identifiant" labelposition="left">
</s:textfield>

<s:textfield name="pseudo" id="pseudo"
label="Pseudo" labelposition="left">
</s:textfield>

<s:textfield name="mail" id="mail"
label="Email" labelposition="left">
</s:textfield>

<s:textfield name="codePostal" id="codePostal"
label="Code Postal" labelposition="left">
</s:textfield>

<s:textfield name="dateInscription" id="dateInceription"
label="Date Inscription" labelposition="left">
</s:textfield>

<s:submit value = "Envoyer"></s:submit>

</s:form>
</div>
</center>
</body>
</html>

Nous remarquons les tags de struts comme textfield et property. Ces tags sont précédés par <s:…> cela signifie que c'est un tag Struts.

<s:textfield name="identifiant" id="identifiant"
label="Identifiant" labelposition="left">
</s:textfield>

Le paramètre label affiche l'étiquette du champ et labelposition positionne cette étiquette par rapport à ce champ. La balise property affiche le contenu sauvegardé dans le champ identifiant.

<s:property value ="identifiant"/>

Pour que ces balises soient prises en compte, ajoutez la déclaration suivante au début de la JSP :

<%@ taglib prefix="s" uri="/struts-tags" %>

Maintenant que nous avons vu la partie Vue de notre exemple, nous allons aborder la partie contrôleur en créant une classe d'action nommée DeveloppeurAction.java. Il est préférable d'ajouter le mot « Action » aux classes d'action pour respecter la norme Struts2.
Cette classe est l'image du formulaire. Elle contient tous les champs saisis avec les setters et getters ainsi que la méthode d'action qui va traiter le formulaire.

Dans le package com.developpez.actions, créez la classe DeveloppeurAction.java et copier-coller le code suivant :

DeveloppeurAction

package com.developpez.actions;

import com.opensymphony.xwork2.ActionSupport;

public class DeveloppeurAction extends ActionSupport{

private static final long serialVersionUID = 1L;

private int identifiant;
private String pseudo;
private String mail;
private String codePostal;
private java.util.Date dateInscription;

public int getIdentifiant() {
return identifiant;
}
public void setIdentifiant(int identifiant) {
this.identifiant = identifiant;
}

public String getPseudo() {
return pseudo;
}
public void setPseudo(String pseudo) {
this.pseudo = pseudo;
}

public String getMail() {
return mail;
}

public void setMail(String mail) {
this.mail = mail;
}

public String getCodePostal(){
return codePostal;
}

public void setCodePostal(String codePostal){
this.codePostal=codePostal;
}
public java.util.Date getDateInscription() {
return dateInscription;
}

public void setDateInscription(java.util.Date dateInscription) {
this.dateInscription = dateInscription;
}

public String enregistrer() {
System.out.println("Dans la méthode enregistrer...");

if(this.pseudo.equals("")) {
System.out.println("le champ identifiant ne doit pas être vide...");
return "input";
}
System.out.println("Sucess.........");
return "success";
}

}

Le fichier de struts.xml correspondant est comme suit :

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />

<package name="com.developpez.actions"  namespace="/" extends="struts-default">

<!-- Action de l'action de référence -->
<default-action-ref name="saisir_Developpeur"/>

<action name="saisir_Developpeur">
<result>/jsp/saisir_Developpeur.jsp</result>
</action>

<action name="enregistrer_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="enregistrer">
<result name="success" >/jsp/enregistrer_Developpeur.jsp</result>
<result name="input">/jsp/saisir_Developpeur.jsp</result>
</action>

</package>
</struts>

Nous allons saisir le même lien que précédemment : http://localhost:8080/GestionDeveloppeur/saisir_Developpeur.action.
Dans le fichier de configuration struts.xml est écrit que lorsqu'on évoque l'action saisir_Developpeur, le résultat sera redirigé vers la vue /jsp/saisir_Developpeur.jsp.
En revanche, le formulaire décrit dans cette vue indique que l'action à exécuter lors du submit est enregistrer_Developpeur. Cette action sera traitée par la classe DeveloppeurAction.java et le résultat sera redirigé vers la vue /jsp/enregistrer_Developpeur.jsp en cas de succès, mais elle sera redirigée vers la même vue (/jsp/saisir_Developpeur.jsp) en cas d'échec afin de renseigner correctement le formulaire.

Mais qui définit que l'action a été traitée avec succès ou non ? La réponse est dans la méthode enregistrer de la classe d'action.

if(this.pseudo.equals("")) {
System.out.println("le champ pseudo ne doit pas être vide...");
return "input";
}
System.out.println("Sucess.........");
return "success";

Si on laisse le champ pseudo vide, nous remarquons que le formulaire se recharge. Dans le cas où l'action est exécutée avec succès, la vue /jsp/enregistrer_Developpeur.jsp est affichée.
La méthode enregistrer est de type String, elle nous renvoie une chaîne de caractères qui va être comparée avec celles déclarées dans le fichier struts.xml.
Les chaînes « success » et « input » peuvent être remplacées par leurs valeurs déclarées respectivement dans la classe mère SUCCESS et INPUT.

Formulaires de saisie et de résultat

Avant de voir les autres points forts de Struts2, nous allons essayer d'approfondir et de grossir un peu nos lignes de code en ajoutant d'autres actions, notamment celles qui vont nous permettre de lister les développeurs et de les supprimer. En effet, un administrateur du site aura la main pour ajouter plusieurs développeurs, puis les lister ou les supprimer.

On ajoute les lignes suivantes qui définissent les actions concernées :

ajout des Actions : lister_Developpeur et supprimer_Developpeur

<action name="lister_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="lister">
<result name="success">/jsp/lister_Developpeur.jsp</result>
</action>

<action name="supprimer_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="supprimer">
<result name="success">/jsp/lister_Developpeur.jsp</result>
</action>

Notre nouveau fichier struts.xml contient quatre actions, dont trois seront traitées par la classe d'action.

Afin de stocker la liste des développeurs, ce qui va nous servir pour les afficher, nous allons utiliser une classe JavaBean qui va déclarer les informations saisies dans le formulaire.

Créez un package com.developpez.beans, puis créez la classe Developpeur.java et copier-coller le code suivant :

classe javabean : Developpeur.java

package com.developpez.beans;

public class Developpeur{

private static final long serialVersionUID = 1L;

private int identifiant;
private String pseudo;
private String mail;
private String codePostal;
private java.util.Date dateInscription;

public int getIdentifiant() {
return identifiant;
}
public void setIdentifiant(int identifiant) {
this.identifiant = identifiant;
}

public String getPseudo() {
return pseudo;
}
public void setPseudo(String pseudo) {
this.pseudo = pseudo;
}

public String getMail() {
return mail;
}

public void setMail(String mail) {
this.mail = mail;
}

public String getCodePostal(){
return codePostal;
}

public void setCodePostal(String codePostal){
this.codePostal=codePostal;
}
public java.util.Date getDateInscription() {
return dateInscription;
}

public void setDateInscription(java.util.Date dateInscription) {
this.dateInscription = dateInscription;
}

}

Les actions qui permettent de gérer l'ajout, l'affichage et la suppression des développeurs seront traitées par les méthodes suivantes :

Méthodes d'actions : enregistrer(), lister() et supprimer()

public String enregistrer() {

System.out.println("dans la méthode enregistrer()......");

Developpeur developpeur = new Developpeur ();
developpeur.setIdentifiant(identifiant);
developpeur.setPseudo(pseudo);
developpeur.setMail(mail);
developpeur.setCodePostal(codePostal);
developpeur.setDateInscription(dateInscription);

listDeveloppeurs.add(developpeur);

if(this.pseudo.equals("")) {
return "input";
}
return "success";

}

public String lister(){
System.out.println("dans la méthode lister().....");

return "success";
}

public String supprimer(){
System.out.println("dans la méthode supprimer().....");
listDeveloppeurs.removeAll(getListDeveloppeurs());
return "success";
}

Enfin, voici la vue lister.jsp qui permet d'afficher la liste des développeurs sauvegardée.

lister_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"  %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Lister les developpeurs</title>
</head>
<body>
<center><div>
<center><h2>Liste des développeurs sur developpez.com</h2></center>

<s:if test="%{listDeveloppeurs.size()>0}">
<s:iterator value="listDeveloppeurs"><br/>

Identifiant : <s:property value="identifiant"/><br/>
Pseudo : <s:property value="pseudo"/><br/>
Mail : <s:property value="mail"/><br/>
Code postal : <s:property value="codePostal"/><br/>
Date d'inscription : <s:property value="dateInscription"/><br/>
</s:iterator>
</s:if>
<s:else>
Aucun développeur dans la liste
</s:else>

</div>
<p></p>
<a href="saisir_Developpeur.action">Ajouter un développeur</a><br/>
<a href="supprimer_Developpeur.action">Supprimer les développeurs</a>

</center>

</body>
</html>

Du nouveau ? Oui !
Vous l'avez deviné, c'est la balise iterator. Cette balise est très utile, car elle permet d'afficher des listes d'objets. La balise <s:if>/<s:else> permet d'effectuer des tests, dans notre cas; la taille de la liste listDeveloppeurs est testée pour déterminer si la liste n'est pas vide.

Voici le lien pour voir tous les tags utilisés par Struts2: http://struts.apache.org/2.0.14/docs/tag-reference.html

Résultats de la balise Iterator

Maintenant notre application est « opérationnelle »… mais il nous reste encore du chemin à parcourir pour utiliser d'autres fonctionnalités de Struts2.

Voici une première approche de l'arborescence de notre application :

Arborescence de notre application

VI - Débogage et traçage de l'application

Si vous avez du mal à récupérer certains paramètres ou votre application met plus de temps que prévu, vous pouvez utiliser certains tags ou intercepteurs mis à disposition par Struts 2.

VI-A - La balise <:debug/>

Cette balise est importante pour afficher toutes les variables du contexte, et permet de suivre l'application et de la tracer. Pour cela, il suffit de mettre la balise <s:debug> dans les pages de vue à l'endroit voulu.

Résultat de la balise <:debug/>

VI-B - La méthode setActive()

Afin d'afficher les traces de l'application avec le temps d'exécution de chaque étape, utilisez la méthode setActive(boolean) de la classe UtilTimerStack avec la valeur du paramètre true.
Voici une vue partielle des différents intercepteurs évoqués et le temps d'exécution de chaque étape lors de l'appel de la méthode lister() de notre application.

Résultat de la méthode setActive()

dans la méthode lister()…
- [76ms] - FilterDispatcher_doFilter:
[76ms] - Handling request from Dispatcher
[0ms] - create DefaultActionProxy:
[0ms] - actionCreate: lister_Developpeur
[76ms] - invoke:
[76ms] - interceptor: exception
[76ms] - invoke:
[76ms] - interceptor: alias
……..
[75ms] - interceptor: multiselect
[75ms] - invoke:
[75ms] - interceptor: staticParams
[75ms] - invoke:
[75ms] - interceptor: actionMappingParams
[75ms] - invoke:
[75ms] - interceptor: params
[75ms] - invoke:
[75ms] - interceptor: conversionError
[75ms] - invoke:
[75ms] - interceptor: validation
[67ms] - invoke:
[67ms] - interceptor: workflow
[67ms] - invoke:
[2ms] - invokeAction: lister_Developpeur
[65ms] - executeResult: success

VI-C - L'option debug

L'option debug est associée à l'URL avec l'un des paramètres suivants : xml, console et browser peut aussi tracer l'application. L'URL suivante renvoie un fichier XML de debug. http://localhost:8080/GestionDeveloppeur/lister_Developpeur.action?debug=xml

VII - Fichier de properties ou ne rien écrire en dur

Imaginez que le responsable du site nous dit : « il ne faut pas écrire « développeur », mais « programmeur ». » Hum ! c'est un problème ! Nous allons modifier tous nos fichiers(JSP, classes, fichier de configuration) et traquer tous les mots « développeur » pour les remplacer, puis compiler et déployer à nouveau… Ouf c'est énorme pour une simple modification.
Heureusement Struts 2 se base sur le fichier de properties qui est fréquemment utilisé en Java.

Un fichier de properties n'est ni plus ni moins qu'un fichier qui contient les couples de propriétés sous la forme (clé, valeur). Nous allons utiliser ce type de fichier et faire appel à chaque fois qu'on veut afficher une valeur d'une chaîne.

Nous allons créer un fichier package.properties qui sera reconnu dans tout le package par défaut du projet. Nous allons le renseigner avec les clés et les valeurs correspondantes. À titre d'exemple, au lieu d'écrire le message suivant « Bienvenue sur le site Developpez.com » dans nos JSP, nous allons l'insérer dans le fichier package.properties de la façon suivante :

developpez.message.bienvenue=Bienvenue sur le site developpez.com

Voici quelques façons d'appeler les valeurs de ce fichier :

Dans les JSP :
Avec la balise <s:property> et la méthode getText.

<s:property value="%{getText('developpez.message.bienveue')}"/>

<s:submit value = "%{getText('developpez.form.submit')}"></s:submit>

Ou avec la balise <s:text>

<s:text name="developpez.message.bienveue"></s:text>

Dans les classes d'action :

getText("developpez.message.bienveue");

Voici la vue saisir_Developpeur.jsp après utilisation du fichier de properties:

saisir_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix ="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
<s:property value="%{getText('developpez.page.saisir')}"/>
</title>
</head>
<body>
<center>

<h2><s:property value="%{getText('developpez.message.bienveue')}"/></h2>

<div id="formulaire">
<s:form method ="post" action="enregistrer_Developpeur">

<s:textfield name="identifiant" id="identifiant"
label="%{getText('developpez.form.identifiant')}" labelposition="left">
</s:textfield>

<s:textfield name="pseudo" id="pseudo"
label="%{getText('developpez.form.pseudo')}" labelposition="left">
</s:textfield>

<s:textfield name="mail" id="mail"
label="%{getText('developpez.form.email')}" labelposition="left">
</s:textfield>

<s:textfield name="codePostal" id="codePostal"
label="%{getText('developpez.form.codepostal')}" labelposition="left">
</s:textfield>

<s:textfield name="dateInscription" id="dateInceription"
label="%{getText('developpez.form.dateinscription')}" labelposition="left">
</s:textfield>

<s:submit value = "%{getText('developpez.form.submit')}"></s:submit>

</s:form>
</div></center>
</body>
</html>

Dans la JSP précédente, les messages et les libellés sont tous affichés via un appel au fichier de properties.

En plus que ce fichier de properties sert à rendre l'application plus souple, il nous permettra aussi d'écrire des applications multilingues. C'est ce qui décrira le chapitre suivant.

VIII - Écrire des applications multilingues

Il n'y a pas plus simple que d'écrire des applications à plusieurs langues avec Struts 2. Pour cela, il suffit de définir un fichier properties ayant le signe de la locale de la langue ou du pays correspondant.
Droit au but, voici quelques exemples de fichiers de properties : package_fr.properties, package_de.properties, package_fr_CA.properties.

Nous allons continuer avec notre application et la rendre multilangue, du moins avec deux langues.
Notre application sera à l'image d'ARTE ou de l'amitié francoallemande. Nous aurons deux fichiers de properties : package_fr.properties et package_de.properties

Voici quelques lignes décrites dans les fichiers de properties :

package_fr.properties

#textes divers
developpez.message.bienveue=Bienvenue sur developpez.com

#textes formulaires
developpez.form.identifiant=Identifiant
developpez.form.pseudo=Pseudo
developpez.form.email=Email
developpez.form.codepostal=Code Postal
developpez.form.dateinscription=Date d''inscription
developpez.form.submit=Envoyer

et voici son équivalent avec la langue de Thomas Man :

package_de.properties

#textes divers
developpez.message.bienveue=Herzlich Willkommen auf developpez.com

#textes formulaires
developpez.form.identifiant=Anmeldung
developpez.form.pseudo=Pseudonym
developpez.form.email=Email
developpez.form.codepostal=Postleitzahl
developpez.form.dateinscription=Anmeldedatum
developpez.form.submit=Senden

Afin de tester notre code, nous allons modifier les propriétés de notre navigateur et de mettre par défaut la langue allemande.

Paramétrage de la langue par défaut dans le naviguateur

Il ne reste qu'à faire appel à notre page d'ajout de développeurs : http://localhost:8080/GestionDeveloppeur/saisir_Developpeur

Formulaire de saisie en allemand

La figure précédente montre que la gestion de la locale a bien fonctionné, mais en pratique le développeur doit offrir la possibilité à l'internaute de choisir lui-même sa langue. Pour cela, Struts 2 dispose d'une balise qui permet de gérer facilement la locale.

Le format de la date allemande est : jj.mm.aaaa (attention aux points).

Reprenons l'exemple précédent avec la saisie des informations du développeur. Afin de gérer plusieurs langues, il suffit d'avoir autant de langues que de fichiers de properties, et enfin ajouter le paramètre request_local à la balise url comme suit :

<s:url action="saisir_Developpeur" id="langueFr">
<s:param name="request_locale">fr</s:param>
</s:url>

<s:url action="saisir_Developpeur" id="langueDe">
<s:param name="request_locale">de</s:param>
</s:url>

La balise <s:url> permet de créer un lien dynamique avec le paramètre request_local qui va être utilisé via un lien href comme suit :

<s:a href="%{langueFr}">Fr</IMG> </s:a>
<s:a href="%{langueDe}">En</IMG> </s:a>

Nous avons créé deux liens dynamiques langueFr et langueDe associés au paramètre request_local, puis nous l'avons utilisé dans le lien href.

De manière plus esthétique, nous allons ajouter deux petites images des drapeaux des deux pays qui vont nous servir de liens pour cliquer à chaque fois qu'on veut choisir une langue. Pour cela ajoutez un répertoire images dans webContent et mettez deux images correspondantes. À nouveau, voici le contenu de la JSP

saisir_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix ="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
<s:property value="%{getText('developpez.page.saisir')}"/>
</title>
</head>
<body>
<center>

<s:url action="saisir_Developpeur" id="langueFr">
<s:param name="request_locale">fr</s:param>
</s:url>

<s:url action="saisir_Developpeur" id="langueDe">
<s:param name="request_locale">de</s:param>
</s:url>

<s:a href="%{langueFr}"><IMG SRC="./images/pngfr.gif" border="0"></IMG> </s:a>
<s:a href="%{langueDe}"><IMG SRC="./images/pngde.gif" border="0"></IMG> </s:a>

<h2><s:property value="%{getText('developpez.message.bienveue')}"/></h2>

<div id="formulaire">
<s:form method ="post" action="enregistrer_Developpeur">

<s:textfield name="identifiant" id="identifiant"
label="%{getText('developpez.form.identifiant')}" labelposition="left">
</s:textfield>

<s:textfield name="pseudo" id="pseudo"
label="%{getText('developpez.form.pseudo')}" labelposition="left">
</s:textfield>

<s:textfield name="mail" id="mail"
label="%{getText('developpez.form.email')}" labelposition="left">
</s:textfield>

<s:textfield name="codePostal" id="codePostal"
label="%{getText('developpez.form.codepostal')}" labelposition="left">
</s:textfield>

<s:textfield name="dateInscription" id="dateInceription"
label="%{getText('developpez.form.dateinscription')}" labelposition="left">
</s:textfield>

<s:submit value = "%{getText('developpez.form.submit')}"></s:submit>

</s:form>
</div></center>
</body>
</html>

Et voici le résultat :

Formulaire multilingue

Quel bonheur ! Grâce à Struts 2, les développements d'une application peuvent servir à toute la planète, moyennant quelques efforts de traduction.

IX - Validation des entrées

Contrôler les données saisies via des formulaires est une étape vitale pour l'application, tant sur le plan de fiabilité, que sur le plan sécuritaire.
En effet pour éviter que l'internaute ne saisisse des données erronées, des injections SQL ou du code (voir l'article : Sécurisez vos formulaires sur le web) Struts 2 offre un moyen dit validation des entrées pour remédier à cela.

Les validations basiques sous Struts 2 consistent à vérifier le type de la saisie dans le champ du formulaire en le comparant avec celle déclarée dans la classe d'action. Imaginons qu'on va saisir toto dans le champ identifiant que lui est déclaré de type int dans la classe DeveloppeurAction.java. Nous obtiendrons l'erreur suivante :

Erreur de conversion String vers int

Et dans la console, on obtient l'erreur suivante :

java.lang.NoSuchMethodException: com.developpez.actions.DeveloppeurAction.setIdentifiant([Ljava.lang.String;)

À l'origine, dans le formulaire, le champ identifiant est de type String. Struts 2 va convertir ce champ vers le type déclaré dans la classe d'action, en l'occurrence vers le type int.
L'erreur précédente signifie que Struts 2 a tenté de convertir le champ identifiant, mais il se trouve que la valeur saisie n'a pas un format adéquat pour le type int.

Notre formulaire contient des champs qui vont nous permettre de saisir plusieurs types de données, mais aussi des formats différents. Nous avons les types entier, date, String avec les formats mail et code postal.

Pour réaliser les validations, Struts 2 se base sur un fichier XML qui permet de stocker les informations de validation du formulaire. Ce fichier sera associé à l'action, de ce fait à une méthode dans la classe d'action.

Voyons les choses par des cas concrets. Reprenons notre exemple de saisie du développeur.
Le formulaire est envoyé via la méthode post vers l'action enregistrer_Developpeur qui va être traitée par la méthode enregistrer dans la classe d'action. Pour cela, il nous faut créer un fichier de validation XML qui portera le nom de la forme suivante : <ClasseAction>-<Action>-validation.xml. Ce fichier doit être créé dans le même package que la classe d'action. Dans notre cas, le fichier va s'appeler : DeveloppeurAction-enregistrer_Developpeur-validation.xml et sera créé dans le package com.developpez.actions

Voici le fichier de validation DeveloppeurAction-enregistrer_Developpeur-validation.xml :

DeveloppeurAction-enregistrer_Developpeur-validation.xml

<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">

<validators>
<field name="identifiant">
<field-validator type="int">
<message key="developpez.erreur.validate.identifiant"/>
</field-validator>
</field>

<field name="pseudo">
<field-validator type="requiredstring">
<message key="developpez.erreur.pseudovide"/>
</field-validator>
</field>

<field name="pseudo">
<field-validator type="stringlength">
<param name="minLength">5</param>
<message key="developpez.erreur.validate.pseudo"/>
</field-validator>
</field>

<field name="mail">
<field-validator type="email">
<message key="developpez.erreur.validate.mail"/>
</field-validator>
</field>

<field name="codePostal">
<field-validator type="regex">
<param name="expression"><![CDATA[^\d{5}$]]</param>
<message key ="developpez.erreur.validate.codepostal"/>
</field-validator>
</field>

<field name="dateInscription">
<field-validator type="date">
<param name="min">01/01/1990</param>
<param name="max">31/12/2010</param>
<message key="developpez.erreur.validate.dateinscription"></message>
</field-validator>
</field>

</validators>

Expliquons les détails de ce fameux fichier XML :

La balise validators doit être la racine du fichier. Les champs du formulaire sont représentés par la balise field, elle comporte le nom du champ, et la balise field-validator avec le type du champ, qui peut comporter à son tour la balise param pour spécifier les paramètres que peut avoir le champ. Et enfin la balise message pour afficher le message associé à l'erreur.

Reprenons la partie suivante qui permet de valider le champ identifiant.

Validation du champ identifiant

<field name="identifiant">
<field-validator type="int">
<param name="min">100</param>
<param name="max">999999</param>
<message key="developpez.erreur.validate.identifiant"/>
</field-validator>
</field>

Le champ identifiant doit être du type int, et doit être compris entre les valeurs <min, max> soit <100,999999>. En cas d'erreur, le message stocké dans le fichier de properties avec la clé developpez.erreur.validate.identifiant sera affiché.

IX-A - Validation de base de Struts 2

Voici quelques types de validation de base de Struts 2 :
int : vérifie si la valeur saisie est de type entier. Elle vérifie aussi si une valeur est comprise dans une plage indiquée via la balise param et les paramètres min et max.

double : même que la validation int, sauf qu'elle vérifie un champ de type double.

required : vérifie si le champ a la valeur nulle. Attention String est un objet, une chaîne qui représente un String vide n'est pas nulle.

requiredstring : en complément du type required, requiredstring vérifie si le champ n'est pas vide.

stringlength : vérifie si la taille du champ est située entre les paramètres min et max passés dans la balise param.

mail : vérifie si un champ non vide a le format d'un mail. Attention, on ne vérifie que le format et non la validité du mail.

Regex : vérifie si le champ est conforme à une expression régulière passée comme paramètre dans la balise param. Exemple du code postal.

date : vérifie si la valeur d'un champ date est située entre une plage donnée via la balise param avec les paramètres min et max. La date est vérifiée selon la locale.

url : vérifie si le champ a un format d'une URL correcte.

Pour voir toutes les balises de validation et leurs détails, cliquez ici.

Lancez l'action http://localhost:8080/GestionDeveloppeur/saisir_Developpeur.action et saisissez des données erronées dans les champs du formulaire. La figure suivante montre les messages d'erreurs générés par Struts 2 :

Validation des données en entrée

Génial ! Les messages sont affichés au-dessus de chaque champ de formulaire respectivement. Mais avouons-le, l'affichage est un peu confus, on distingue mal le libellé du champ de celui du message d'erreur. Afin de mieux présenter notre formulaire, nous allons ajouter un fichier CSS qui sera appelé directement dans la JSP correspondante.

Pour cela, créez un dossier dans webContent, css dans notre cas, puis ajoutez cette ligne entre les balises <head> et </head> de la JSP saisir_Developpeur.jsp.

Déclaration du fichier css

<style type="text/css">@import url(css/styles.css);</style>

Et enfin créez un fichier nommé style.css et copier-coller ces quelques lignes.

style.css

* { margin: 2; padding: 2;}

#formulaire
{
position: relative;
margin-left: 2%;
margin-top: 2%;
width: 600px;
text-align: left;
background:#fefefe;
border-style:solid;
border-width:2px;
border-color:#848484;
padding: 14px;
}

.errorMessage
{
color:#EF0F0F;
font-family:tahoma, verdana, arial, sans-serif;
font-size:13px;
}

Et enfin, voici le résultat tant attendu :

Affichage des erreurs avec le fichier CSS

IX-B - Validation avec conversion

Comme nous l'avons déjà vu auparavant quand on a saisi « toto » dans le champ identifiant, nous avons obtenu un message : « invalid fiel value for field 'Identifiant' » malgré que le champ identifiant soit déclaré de type int dans le fichier de validation.

Si nous ajoutons la balise <param> au validateur int pour lui préciser une plage de valeurs comme suit :

<field name="identifiant">
<field-validator type="int">
<param name="min">100</param>
<param name="max">999999</param>
<message key="developpez.erreur.validate.identifiant"/>
</field-validator>
</field>

nous aurons alors le message suivant :

Validation du champ identifiant

Le message en anglais est le message par défaut affiché par Struts 2. On peut surcharger la méthode qui affiche ce message dans le fichier de properties afin de choisir notre message à afficher.

Saisissez la ligne suivante dans le fichier de properties français, puis reportez la même ligne avec traduction pour les autres langues.

invalid.fieldvalue.identifiant=Le format du nombre n''est pas valide

Ajoutez une validation pour le champ identifiant dans le fichier DeveloppeurAction-enregistrer_Developpeur-validation.xml comme suit :

validateur de type 'conversion'

<field name="identifiant">
<field-validator type="conversion">
<message key ="invalid.fieldvalue.identifiant"/>
</field-validator>
</field>

Vous pouvez aussi surcharger le message invalid.fieldvalue.identifiant avec une chaîne vide et écrire votre propre message.

IX-C - Bien gérer ses messages d'erreur et de succès :

La méthode getText()
Examinons de plus près le message qu'on a saisi pour éviter un pseudo vide :

developpez.erreur.pseudovide=Le champ Pseudo ne doit pas être vide

Avec la méthode getText(<paramètre>), on peut éviter d'écrire même les noms des champs en dur, car elle permet de récupérer les paramètres passés via la balise <param>.
Le message suivant nous permet de réaliser cette opération :

developpez.erreur.pseudovide=Le champ ${getText(fieldName)} ne doit pas être vide

On peut même écrire les paramètres du validateur d'une manière dynamique. L'expression suivante est valable pour valider le champ identifiant :

Le champ ${getText(fieldName)} doit être un nombre compris entre ${getText(min)} et ${getText(max)}

Afin de gérer les messages d'action et des erreurs, Struts 2 offre plusieurs méthodes dans la classe d'action. Reprenons le code de la méthode enregistrer et ajoutons ces appels :

gestion des messages d'erreur dans la classe d'action

public String enregistrer() {
System.out.println("dans la méthode enregistrer()......");

Developpeur developpeur = new Developpeur ();
developpeur.setIdentifiant(identifiant);
developpeur.setPseudo(pseudo);
developpeur.setMail(mail);
developpeur.setCodePostal(codePostal);
developpeur.setDateInscription(dateInscription);

listDeveloppeurs.add(developpeur);

if(this.pseudo.equals("")) {
addFieldError("pseudo", "le pseudo ne doit pas être vide");
return "input";
}else if(!this.pseudo.equals("javafan")){
addActionError("Le pseudo saisi n'est pas correct");
return "input";
}else
{
addActionMessage("Le développeur est ajouté avec success");
return "success";
}

}

Pour afficher les messages suivants dans les vues correspondantes, ajoutez les balises suivantes :

<s:actionerror> permet d'afficher les messages de la méthode addActionError().

<s:if test="errorMessages.size()>0">
<div id="msg_erreur">
<label><s:property value="%{getText('developpez.erreur.msgerror')}"/></label>
<s:actionerror/>
</div>
</s:if>

<s:fielderror> permet d'afficher les messages de la méthode addFieldError().

<s:if test="errors.size()>0">
<div id="msg_erreur">
<label>
<s:property value="%{getText('developpez.erreur.msgerror')}"/>
</label>
<s:fielderror/>
</div>
</s:if>

Et enfin la balise <s:actionmessage/> à ajouter dans la vue correspondante au succès, enregistrer_Developpeur.jsp dans notre cas. Cette balise permet d'afficher les messages de la méthode addActionMessage().

<s:if test="actionMessages.size()>0">
<div id="msg_infos">
<s:actionmessage/>
</div>
</s:if>

IX-D - Écrire son propre validator

Même si Struts 2 offre suffisamment de validators qu'on peut utiliser largement dans des applications professionnelles, il reste toujours des cas où l'on dira « Si Struts 2 offre ce type de validator, ça sera bien. » Mais les développeurs de Struts 2 sont allés plus loin en offrant la possibilité aux développeurs d'applications de créer leurs propres validators.

Nous allons simuler cette situation en créant une validation complexe pour notre champ Pseudo. En effet, après une analyse profonde de notre application, nous avons jugé utile que le champ Pseudo doit respecter les règles suivantes :

- ne doit pas être vide, et être une chaîne dont le nombre est compris entre 6 et 24 caractères ;

- doit obligatoirement comporter au moins un caractère majuscule, un caractère minuscule et un chiffre ; ne doit pas être présent en base.

Jusqu'à ce chapitre, nous n'avons pas utilisé de base de données. Nous allons remplacer notre base par des données statiques écrites en dur.

Créez un package com.developpez.validators ensuite créez la classe PseudoValidator.java. Copiez-collez le code suivant :

PseudoValidator.java

package com.developpez.validators;

import java.util.ArrayList;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;

public class PseudoValidator extends FieldValidatorSupport{

public void validate(Object obj) throws ValidationException
{
System.out.println("dans la méthode validate de PseudoValidator....");
String champ = this.getFieldName();
String valChamp = (String) this.getFieldValue(champ, obj);

String regex = "^(?=.*[a-z])(?=.*[0-9])(?=.*[A-Z])[a-zA-Z0-9]{6,24}$";

if(!valChamp.matches(regex) || isExist(valChamp)){
addFieldError(champ, obj);
}

}

private boolean isExist(String pseudo)
{
ArrayList<String> listPseudo = new ArrayList<String>();
listPseudo.add("javafan".toUpperCase());
listPseudo.add("linuxfan".toUpperCase());
listPseudo.add("phpfan".toUpperCase());
return listPseudo.contains(pseudo.toUpperCase());

}

}

Si la valeur du champ Pseudo correspond à l'une des chaînes (javafan, linuxfan,phpfan), elle ne sera pas acceptée. De même si elle ne correspond pas à l'expression régulière suivante : « ^(?=.*[a-z])(?=.*[0-9])(?=.*[A-Z])[a-zA-Z0-9]{6,24}$ ».

Maintenant, il reste à réaliser le lien entre notre classe et le fichier de validation de la classe d'action.

Créez un fichier validators.xml(encore un fichier XML !) dans le répertoire WEB-INF/classes et copiez-collez le code suivant :

validators.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator Config 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">

<validators>
<validator name="pseudoValidateur"
class="com.developpez.validators.PseudoValidator"/>
</validators>

Puis remplacez la validation du champ pseudo dans le fichier DeveloppeurAction-enregistrer_Developpeur-validation.xml par le code suivant :

le champ pseudo est de type pseudoValidateur

<field name="pseudo">
<field-validator type="pseudoValidateur">
<message key="developpez.erreur.validate.pseudo"/>
</field-validator>
</field>

Notre propre validator a bien fonctionné. Grâce à cette méthode, vous pouvez créer autant de validators que vous voulez. Toutefois, avant de penser à créer son propre validator, pensez à chercher si Struts 2 ne l'a pas déjà fait pour vous.

Validation du champ Pseudo après création du validateur : pseudoValidateur

Téléchargez le war correspondant ici.
Ce war comprend tous les chapitres précédents notamment, les actions, les fichiers de properties, l'internationalisation et la validation.
Il est préférable de renommer les fichiers war après téléchargement en GestionDeveloppeur.war

X - Les intercepteurs

Au risque d'abus de langage, nous disons que dans Struts 2, « tout est un intercepteur ». En effet, avant de donner la main à la classe d'action, Struts 2 lance les intercepteurs adéquats. Nous l'avons vu quand on a utilisé la méthode setActive() de la classe UtilTimerStack. Plusieurs intercepteurs sont lancés dans la méthode lister().

Un intercepteur est une classe Java qui utilise les méthodes init() et destroy() ainsi que intercept().
À titre d'exemple, afin de gérer l'upload des fichiers vers le serveur, Struts 2 utilise l'intercepteur fileUpload et TockenInterceptor pour le double clic, etc.

Pour voir la liste des intercepteurs utilisés par Struts 2, voici le lien : Liste des intercepteurs Struts 2.

Dans ce chapitre, nous allons essayer de créer notre interceptor. Il s'agit d'une classe Java qui nous permet de réaliser une authentification pour l'internaute qui arrive sur la page saisir_Developpeur.jsp.

Au début, un formulaire d'authentification lui sera proposé afin de s'authentifier. Puis, il pourra ajouter des développeurs dans la liste autant de fois qu'il le voudra. Mais dès qu'il se déconnecte, il sera dans l'obligation de se reconnecter s'il veut saisir encore des développeurs.

Pour cela, nous aurons besoin d'un formulaire d'authentification avec deux champs login et mot de passe.

Dans webContent/jsp/, créez la vue authentifier_Developpeur.jsp et copiez-collez le code suivant :

authentifier.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix ="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">@import url(css/styles.css);</style>
<title>
<s:property value="%{getText('developpez.page.authentifier')}"/>
</title>
</head>
<body>
<center>

<s:url action="authentifier_Developpeur" id="langueFr">
<s:param name="request_locale">fr</s:param>
</s:url>

<s:url action="authentifier_Developpeur" id="langueDe">
<s:param name="request_locale">de</s:param>
</s:url>

<s:a href="%{langueFr}"><IMG SRC="./images/pngfr.gif" border="0"></IMG> </s:a>
<s:a href="%{langueDe}"><IMG SRC="./images/pngde.gif" border="0"></IMG> </s:a>

<h2><s:property value="%{getText('developpez.message.authentifier')}"/></h2>

<s:if test="errorMessages.size()>0">
<div id="msg_erreur">
<label><s:property value="%{getText('developpez.erreur.msgerror')}"/></label>
<s:actionerror/>
</div>
</s:if>
<s:if test="errors.size()>0">
<div id="msg_erreur">
<label><s:property value="%{getText('developpez.erreur.msgerror')}"/></label>
<s:fielderror/>
</div>
</s:if>

<div id="formulaire">
<s:form method ="post" action="authetifier_Developpeur">

<s:textfield name="login" id="login"
label="%{getText('developpez.form.login')}" labelposition="left">
</s:textfield>

<s:password name="password" id="password"
label="%{getText('developpez.form.password')}" labelposition="left">
</s:password>

<s:submit value = "%{getText('developpez.form.authentifier')}"></s:submit>

</s:form>

</div>
<s:debug/>
</center>
</body>
</html>

Selon la logique de Struts 2, nous allons créer une classe image de notre formulaire. Dans le package com.developpez.actions, créez la classe AllowAccessAction.java et mettez le code suivant :

AllowAccessAction.java

package com.developpez.actions;

import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionSupport;

public class AllowAccessAction extends ActionSupport implements SessionAware{

private static final long serialVersionUID = 1L;

private String login;
private String password;

private Map<String, Object> session ;

public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

public void setSession(Map<String, Object> map){
this.session=map;
}

public Map<String, Object> getSession(){
return session;
}

public String authentifier(){

System.out.println("dans la methode authentifer........");

if(this.login!=null && this.password!=null)
if(this.login.equalsIgnoreCase("struts2") && this.password.equals("struts2")){

this.session=getSession();
this.session.put("allowAccess", "true");
return "success";
}

addFieldError("login", getText("developpez.message.authentechouee"));
return "input";
}

public String deconnecter (){
System.out.println("dans la méthode deconnecter.......");
this.session.clear();
return "success";

}
}

Dans cette classe, nous allons traiter les deux champs login/password ainsi que la session d'authentification, car nous allons ajouter un lien déconnecter qui va permettre de vider la session en cours et de revenir au formulaire d'authentification. Ce lien va être traité par la méthode deconnecter() de la classe AllowAccessAction.java.

La méthode authentifier() permet de tester l'authentification de l'internaute, c'est-à-dire les champs login/password avec les valeurs Struts2/Struts2

Bien sûr ! Il ne faut pas oublier de valider les champs du formulaire. Pour cela, nous allons ajouter un fichier de validation AllowAccessAction-authentifier_Developpeur-validation.xml.

AllowAccessAction-authentifier_Developpeur-validation.xml

<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">

<validators>
<field name="login">
<field-validator type="requiredstring">
<message key="developpez.message.authentechouee"></message>
</field-validator>
</field>

<field name="password">
<field-validator type="requiredstring">
<message key="developpez.message.authentechouee"></message>
</field-validator>
</field>

</validators>

Maintenant, il ne nous reste que la création de la classe de l'intercepteur. Pour cela, créez un package com.developpez.interceptor, ensuite créez la classe AllowAccessInterceptor.java et copiez-collez le code suivant :

AllowAccessInterceptor.java

package com.developpez.interceptor;

import java.util.Map;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

public class AllowAccessInterceptor extends AbstractInterceptor {

private static final long serialVersionUID = 1L;

public void init(){
System.out.println("dans la méthode init.......");
}

public String intercept(ActionInvocation arg0) throws Exception {

System.out.println("dans la méthode intercept.......");

Map<String, Object> session = arg0.getInvocationContext().getSession();

if(session.get("allowAccess")==null)
{
return "allowAccess";
}else
{
if (!session.isEmpty())
{
return arg0.invoke();
}
else
{
return "allowAccess";
}
}
}
}

Si la session est nulle ou l'authentification n'est pas effectuée, la chaîne de caractères allowAccess est retournée.

Modifiez le fichier d'action struts.xml en intégrant la déclaration d'intercepteur et celles des actions deconnecter_Developpeur et authentifier_Developpeur.

struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />
<constant name="struts.custom.i18n.resources" value="package"  />

<package name="com.developpez.actions"  namespace="/" extends="struts-default">

<interceptors>
<interceptor name="allowAccessIntercept"
class="com.developpez.interceptors.AllowAccessInterceptor">
</interceptor>
</interceptors>

<!-- Action de l'action de référence -->
<default-action-ref name="saisir_Developpeur"/>

<action name="saisir_Developpeur">
<interceptor-ref name="createSession"/>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="allowAccessIntercept"/>
<result name="allowAccess" >/jsp/authentifier_Developpeur.jsp</result>
<result name="success" >/jsp/saisir_Developpeur.jsp</result>
</action>

<action name="enregistrer_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="enregistrer">
<result name="success" >/jsp/enregistrer_Developpeur.jsp</result>
<result name="input">/jsp/saisir_Developpeur.jsp</result>
</action>

<action name="lister_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="lister">
<result name="success">/jsp/lister_Developpeur.jsp</result>
</action>

<action name="supprimer_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="supprimer">
<result name="success">/jsp/lister_Developpeur.jsp</result>
</action>

<action name="authetifier_Developpeur" class="com.developpez.actions.AllowAccessAction"
method="authentifier">
<result name="success">/jsp/saisir_Developpeur.jsp</result>
<result name="input">/jsp/authentifier_Developpeur.jsp</result>
</action>

<action name="deconnecter_Developpeur" class="com.developpez.actions.AllowAccessAction"
method="deconnecter">
<result type="redirectAction">authentifier_Developpeur</result>
</action>

</package>
</struts>

On remarque la déclaration de l'interceptor avec sa classe d'action AllowAccessInterceptor.

déclaration de l'intercepteur AllowAccessIntercept

<interceptors>
<interceptor name="allowAccessIntercept"
class="com.developpez.interceptors.AllowAccessInterceptor">
</interceptor>
</interceptors>

Puis l'utilisation de la référence de l'intercepteur dans l'action correspondante : saisir_Developpeur.

<interceptor-ref name="allowAccessIntercept"/>
<result name="allowAccess">/jsp/authentifier_Developpeur.jsp</result>

La déclaration de l'interceptor createSession nous permet de garder les valeurs de session, notamment la locale. DefaultStack permet l'invocation par défaut des intercepteurs.

<interceptor-ref name="createSession"/>
<interceptor-ref name="defaultStack"/>

Puis dans les autres vues, ajoutez le lien pour l'action deconnecter_Developpeur comme suit :

<a href="lister_Developpeur.action"><s:text name="developpez.lien.lister"/></a>
<a href="supprimer_Developpeur.action"><s:text name="developpez.lien.supprimer"/></a>

<a href="deconnecter_Developpeur.action"><s:text name="developpez.lien.deconnecter"/></a>

Authentification obligatoire

On remarque dans la figure précédente que lors de la saisie de l'action saisir_Developpeur, l'utilisateur est redirigé vers le formulaire d'authentification. En revanche, une fois authentifié, il ne sera pas redirigé vers le formulaire d'authentification, sauf après déconnexion.

Téléchargez le war correspondant ici.
Ce war comprend tous les chapitres précédents notamment, les actions, les fichiers de properties, l'internationalisation, la validation et la création d'intercepteur.
Il est préférable de renommer les fichiers war après téléchargement en GestionDeveloppeur.war

XI - Interaction de Struts 2 avec une base de données

Avant d'entamer ce chapitre, il faut savoir que Struts 2 ne propose pas de modèle, il a laissé le libre choix au développeur sur une technique à choisir pour la persistance de données. Toutefois dans ce chapitre, nous allons présenter à travers un exemple pratique quelques règles à respecter afin de garder un système évolutif et performant.

L'exemple suivant complète le précédent en se basant sur un modèle MVC et DAO pour la partie modèle.
Il permet notamment de réaliser les opérations suivantes :

- authentification ;

- déconnexion ;

- ajout d'un développeur ;

- suppression d'un développeur ;

- suppression de l'ensemble des développeurs ;

- lister l'ensemble des développeurs.

Pour cela, nous allons utiliser une base de données de type HSQLDB ainsi que le modèle DAO pour interagir avec cette base.

XI-A - Installation de HSQLDB

HSQLDB est très légère. Afin de l'installer, il suffit de télécharger le zip à l'adresse suivante : http://sourceforge.net/projects/hsqldb/files/ puis de le dézipper dans un répertoire.

Lancement du serveur hsqldb :
Une fois le zip décompressé, positionnez-vous dans le répertoire lib/ de HSQLDB et lancez la commande :

java -cp hsqldb org.hsqldb.server.Server

Cette commande lancera le serveur, et pour l'arrêter il suffit de lancer la commande SHUTDOWN après connexion ou d'appuyer sur Ctrl + C dans le terminal correspondant.

Maintenant que le serveur est lancé, nous allons nous connecter pour créer un utilisateur et nos tables correspondantes.
Dans un autre terminal, lancez la commande suivante :

java -cp hsqldb org.hsqldb.util.DatabaseManager

Une fenêtre apparaît, choisissez HSQL Database Engine Server dans la rubrique Type, puis laissez le User par défaut SA et le champ password vide. Cliquer sur OK afin d'établir une connexion.

Dans la fenêtre de saisie, créez un autre utilisateur doté de privilèges d'administrateur :

CREATE USER 'developpez.com' PASSWORD 'java' ADMIN

Maintenant, nous allons nous déconnecter et nous reconnecter avec les nouveaux user/password.

Connexion à HSQLDB avec login/password = developpez.com/java

Continuons notre chemin par la création des tables developpeur et utilisateur.

CREATE TABLE developpeur(
identifiant INT NOT NULL,
pseudo VARCHAR(20) NOT NULL,
mail VARCHAR(30),
codePostal VARCHAR(5),
dateInscription DATE,
compte VARCHAR(30))

CREATE TABLE utilsateur(
login VARCHAR(30) NOT NULL,
password VARCHAR(30) NOT NULL)

Vue des tables dans HSQLDB

Pour plus d'informations sur HSQLDB, consultez ce tutoriel : Présentation et utilisation d'HSQLDB.

XI-B - Création du modèle DAO

Le modèle DAO permet de modéliser les classes qui peuvent interagir avec les données, quelle que soit leur forme (DB, XML…). Dans notre cas, nous aurons besoin de deux classes modèles, DeveloppeurModel et AllowAccessModel et la super classe ModelDAO qui contient la méthode getConnection() qui nous renvoie la connexion en cours.

Créez un package com.developpez.dao et créez ces trois classes.

Pour plus d'informations sur le modèle DAO, consultez les tutoriels suivants :

Mapper sa base de données avec le pattern DAO et Site du Zéro : Le pattern DAO.

La nouvelle conception de notre application devient comme suit :

Schéma général de notre application

Arborescence de l'application

La classe ModelDAO contient les deux méthodes de gestion de la connexion, getConnection() de type Connection, ainsi que setConnection(). Si vous optez pour l'utilisation de la connexion via le fichier context.xml du serveur, il faut ajouter les informations suivantes dans ce fichier :

Déclaration de la connexion dans context.xml

<Resource name="jdbc/ConnectDB"
auth="Container"
type="javax.sql.DataSource"
username="developpez.com"
password="java"
driverClassName="org.hsqldb.jdbcDriver"
url="jdbc:hsqldb:hsql://localhost"
maxActive="8"
maxIdle="4"/>

Ce code nous montre comment renseigner les informations de la connexion, notamment le type de driver et login/password.

Puis la méthode getConnection() de la classe ModelDAO.java devient :

getConnection()

public Connection getConnection(){

try  {

Context initCtx =   new   InitialContext();
Context envCtx = (Context) initCtx.lookup( "java:comp/env"  ) ;
DataSource ds  = (DataSource) envCtx.lookup("jdbc/ConnectDB");
conn = ds.getConnection();

}  catch  (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}

Si vous voulez éviter d'utiliser la connexion via context.xml du serveur Tomcat, vous pouvez modifier la méthode getConnection() pour utiliser directement les informations de connexion.

Voici deux exemples de connexion avec HSQLDB et MYSQL.

Utilisation du Drivermanager dans getConnection() ex. HSQLDB

//Utilisation de la connexion via DriverManager ex. HSQLDB
public Connection getConnection(){
try
{
// Chargement du pilote OK
Class.forName("org.hsqldb.jdbcDriver");
}
catch(Exception ex)
{
System.out.println(" Erreur pilote de "+ ex.getMessage());
}

try
{
conn = (Connection) DriverManager.getConnection("jdbc:hsqldb:hsql://localhost","developpez.com","java");
}
catch(SQLException exc)
{
System.out.println("Erreur de connexion " + exc.toString());
}

return conn;
}

Utilisation du Drivermanager dans getConnection() ex. mysql

//Utilisation de la connexion via DriverManager ex. mysql
public Connection getConnection(){
try
{
// Chargement du pilote OK
Class.forName("com.mysql.jdbc.Driver");
}
catch(Exception ex)
{
System.out.println(" Erreur pilote de "+ ex.getMessage());
}

try
{
conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/developpez","developpez.com","java");
}
catch(SQLException exc)
{
System.out.println("Erreur de connexion " + exc.toString());
}

return conn;
}

Téléchargez le war de l'application ici. Ce war comprend tous les chapitres précédents notamment, les actions, les fichiers de properties, l'internationalisation, la validation, la création d'intercepteur et l'interaction avec une base de données avec le modèle DAO.
Il est préférable de renommer les fichiers war après téléchargement en GestionDeveloppeur.war

XII - Struts 2 et Ajax

Le couple Struts 2 et Ajax est idéal pour développer des applications Web 2.0 avec des interfaces réactives et ergonomiques.
Struts 2 mixé avec des plug-ins JavaScript facilite la mise en place de ces applications.

Dans ce chapitre, nous allons mettre en place l'affichage des développeurs en utilisant la technologie Ajax. Le principe consiste à afficher les détails d'un développeur en cliquant sur son pseudo. L'appel se fera d'une manière asynchrone entre le composant HTML et la base de données.

Pour cela, nous allons créer deux actions demoajax_Developpeur et listerAjax_Developpeur qui nous permettent d'afficher la liste des pseudos et le détail d'un pseudo sélectionné respectivement. Ces deux actions seront traitées par les méthodes listerAllPseudo qui renvoie la liste des pseudos disponibles en base et listerByPseudo qui renvoie les informations liées au Pseudo.
Puis nous aurons besoin de deux vues supplémentaires pour afficher la liste des pseudos et les détails des développeurs.

Avant toute écriture du code, il nous faut une librairie de génération de JavaScript. Nous allons utiliser la librairie dojo struts2-dojo-plugin-2.1.8.jar. Commencez par la télécharger ici : http://repo1.maven.org/maven2/org/apache/struts/struts2-dojo-plugin/2.1.8/struts2-dojo-plugin-2.1.8.jar.
Si le lien est obsolète, vous pouvez trouver la librairie dans le war correspondant à ce chapitre.

Ajoutez ces deux actions dans struts.xml.

<action name="demoajax_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="listerAllPseudo">
<result name="success">/jsp/ajaxdemo_Developpeur.jsp</result>
</action>

<action name="listerAjax_Developpeur" class="com.developpez.actions.DeveloppeurAction"
method="listerByPseudo">
<result name="success">/jsp/detailPseudo_Developpeur.jsp</result>
</action>

Dans le dossier webContent/jsp/, créez ces deux vues :

ajaxdemo_Developpeur.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="sx" uri="/struts-dojo-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<sx:head/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">@import url(css/styles.css);</style>
<title><s:property value="%{getText('developpez.page.ajaxdemo')}"></s:property>
</title>
</head>
<script>
function show_details() {
dojo.event.topic.publish("show_detail");
}
</script>
<body>

<center>
<div>
<s:url action="demoajax_Developpeur" id="langueFr">
<s:param name="request_locale">fr</s:param>
</s:url>

<s:url action="demoajax_Developpeur" id="langueDe">
<s:param name="request_locale">de</s:param>
</s:url>

<s:a href="%{langueFr}"><IMG SRC="./images/pngfr.gif" border="0"></IMG> </s:a>
<s:a href="%{langueDe}"><IMG SRC="./images/pngde.gif" border="0"></IMG> </s:a>

<center>
<h3><s:property value="%{getText('developpez.page.ajaxdemo')}"></s:property></h3>
</center>

<s:if test="errorMessages.size()>0">
<div id="msg_erreur">

<s:actionerror/>
</div>
</s:if>

<s:form id="listPseudo_form" name="lst" theme="simple">
<table border="0">
<tr>
<td>
<s:updownselect list="listPseudo" name="pseudo"
label="Liste des dÃ©veloppeurs" allowMoveDown="false" allowMoveUp="false"
allowSelectAll="false" multiple="false"
onchange="javascript:show_details();return false;"></s:updownselect>
</td>

<td><s:url id="details_url" action="listerAjax_Developpeur" /></td>

<td>
<sx:div href="%{details_url}" listenTopics="show_detail"
theme="ajax" formId="listPseudo_form"></sx:div>
</td>

</tr>

</table>

</s:form>
</div>

<p></p>
<a href="saisir_Developpeur"><s:text name="developpez.lien.ajouter"/></a><br />
<a href="supprimer_Developpeur.action"><s:text name="developpez.lien.supprimer"/></a> <br/>
<a href="lister_Developpeur.action"><s:text name="developpez.lien.lister"/></a><br/>
<a href="rechercher_Developpeur.action"><s:text name="developpez.lien.rechercher"/></a><br/>
<a href="deconnecter_Developpeur.action"><s:text name="developpez.lien.deconnecter"/></a><br/>

<s:debug />
</center>

</body>
</html>

detailPseudo_Developpeur.jsp

<%@ taglib prefix="s" uri="/struts-tags"%>
<table  border="1" cellpadding="5" cellspacing="2">
<tr bgcolor="#DEA254">
<td><s:text name="developpez.form.identifiant"></s:text></td>
<td><s:text name="developpez.form.pseudo"></s:text></td>
<td><s:text name="developpez.form.email"></s:text></td>
<td><s:text name="developpez.form.codepostal"></s:text></td>
<td><s:text name="developpez.form.dateinscription"></s:text></td>
</tr>
<s:if test="listDeveloppeurs.size()>0">
<s:iterator value="listDeveloppeurs">
<td><s:property value="identifiant" /><br/>
</td>
<td><s:property value="pseudo" /><br/>
</td>
<td><s:property value="mail" /><br/>
</td>
<td><s:property value="codePostal" /><br/>
</td>
<td><s:property value="dateInscription" /><br/>
</td>
</s:iterator>
</s:if>

</table>
<p></p>
<s:if test="!listDeveloppeurs.size()>0">
<center><s:text name="developpez.message.selectajaxlist"></s:text></center>
</s:if>

Expliquons les grandes lignes de ces deux vues :
ajaxdemo_Developpeur.jsp : dans cette vue, nous avons utilisé le composant updownselect qui nous permet d'avoir la liste des pseudos chargée lors de chargement de la vue, après l'évocation de l'action ajaxdemo_developpeur. Puis, nous avons créé un lien dynamique avec la balise URL :

s:url id="details_url" action="listerAjax_Developpeur" /></td>

Ce lien fait appel à l'action listerAjax_Developpeur pour afficher les détails du développeur.

Dans la vue detailPseudo_developpeur.jsp, on remarque la balise iterator qui permet de parcourir la liste listdeveloppeurs. Cette liste est mise à jour dans la classe d'action, précisément dans la méthode listerByPseudo(). En revanche, la liste des pseudos est mise à jour dans la méthode listerAllPseudo().

Voyons ce que donnent ces deux méthodes ajoutées dans la classe d'action DeveloppeurAction.java.

méthodes : listerByPseudo() et listerAllPseudo()

//Lister tous les pseudo pour les utiliser dans ajax demo
public String listerAllPseudo(){
System.out.println("dans la méthode listerAllPseudo().....");

DeveloppeurModel developpeurModel = new DeveloppeurModel();
if(listPseudo.size()==0)
addActionError(getText("developpez.message.listevideajax"));

return "success";
}

//retourne un développeur ayant le pseudo sélectionné
public String listerByPseudo(){
System.out.println("dans la méthode listerByPseudo().....");

if(getPseudo()!=null && !getPseudo().equals(""))
{
DeveloppeurModel developpeurModel = new DeveloppeurModel();
listDeveloppeurs =  developpeurModel.getDeveloppeurByPseudo(getPseudo());
}

return "success";

}

Ajax Demo

Téléchargez le war de l'application ici. Ce war comprend tous les chapitres précédents notamment, les actions, les fichiers de properties, l'internationalisation, la validation, la création d'intercepteur, l'interaction avec une base de données avec le modèle DAO et Ajax Demo Il est préférable de renommer les fichiers war après téléchargement en GestionDeveloppeur.war

XIII - Téléchargements

Voici la liste des war composant cet article. Chaque war correspond à un ou plusieurs chapitres.
Il est préférable de renommer les fichiers war après téléchargement en GestionDeveloppeur.war.

- 1-GestionDeveloppeur.war : le premier war explique le fonctionnement de base de Struts 2, notamment les actions et les classes correspondantes ;

- 2-GestionDeveloppeur_validator.war : en plus des chapitres précédents, ce war comprend Les fichiers de properties, l'internationalisation et la validation des entrées et gestion des erreurs ;

- 3-GestionDeveloppeur-interceptor.war : ce war comprend un chapitre en plus, la mise en place d'un intercepteur ;

- 4-GestionDeveloppeur_DB.war : ce war est un exemple complet, il ajoute aux chapitres précédents la mise en place d'un modèle MVC et le modèle DAO pour interagir avec une base de données HSQLDB ;

- 5-GestionDeveloppeur-ajax.war : ce war ajoute un exemple d'utilisation de la technologie AJAX avec Struts 2.

XIV - Pour aller plus loin

Pour aller plus loin dans Struts 2, voici quelques liens qui pourront vous être utiles.

Struts 2 le framework de développement d'applications Java EE.
par Jérôme LAFOSSE
Idéal pour la mise en pratique et pour bien débuter

Struts 2 In action.
Pour les courageux, livre en anglais de Donald Brown, Chad Michael Davis, and Scott Stanlick

Practical Apache Struts 2 Web 2.0 Projects.
Aussi pour les courageux, livre en anglais de Ian Roughley

Voici quelques liens Internet :
le site officiel d'Apache ;
le site roseindia.net ;
le site vaannila.com.

XV - Remerciements

Un grand merci à Laurent Gully pour la première relecture et Wachter pour la correction orthographique et sa patience, ainsi que Ricky81 pour la relecture technique et ses conseils.