Tutoriel Stripes MVC 1.5.7

Stripes MVC 1.5.7

1Présentation
2Mise en place
2.1DEPENDANCE MAVEN
2.2CONFIGURATION PRINCIPAL
2.3FICHIER DE RESSOURCES
3Page d’accueil
3.1CLASSE D’ACTION
3.1.1ANNOTATION URLBINDING
3.1.2ANNOTATION DEFAULTHANDLER
3.1.3ATTRIBUT DE LA CLASSE
3.1.4LES MÉTHODES SETCONTEXT() ET GETCONTEXT()
3.2LA PRÉSENTATION (JSP)
3.2.1PRÉSENTATION SIMPLE
3.2.2PRÉSENTATION EN UTILISANT LES LAYOUTS
4Gestion du context et personnalisation
4.1CLASSE DE BASE
4.2PERSONNALISATION DU CONTEXT
5Paramètres d’url et lien
6Gestion des évènements
6.1MISE EN PLACE D’ÉVÈNEMENT DANS LA CLASSE D’ACTION
6.2DÉCLENCHEMENT DES ÉVÈNEMENTS VIA LES JSP
7Formulaire
7.1VALIDATION
7.2CHAMPS
8Conclusion

1 Présentation

Stripes est un framework Java MVC comme l’est Struts ou Spring MVC. Il fonctionne sur le même principe. Il n’est donc pas orienté application avec des composants comme le définit la norme JSF (en utilisant le framework PrimeFaces), ni les alternatives type GWT ou Vaadin.

Stripes permet de construire des sites Web. L’objectif de Stripes est de proposer un framework :
- zéro configuration par défaut,
- possible de l’apprendre en 30 minutes.

Il propose pour cela :
- l’auto-discover des classes d’action et utilisation des annotations,
- un système de localisation,
- suppression des classes de bean,
- support transparent des téléchargements de fichiers,
- possibilité de réutilisation des écrans...

2 Mise en place

2.1 Dépendance maven

Créez un projet maven de type web. Editez le fichier « pom.xml » et ajoutez la dépendance suivante :

<dependency>
 <groupId>net.sourceforge.stripes</groupId>
 <artifactId>stripes</artifactId>
 <version>1.5.7</version>
</dependency>

2.2 Configuration principal

Stripes ne requière pas de fichier de configuration mais il faut tout de même configurer son application pour indiquer d’utiliser Stripes comme moteur MVC dans le fichier de configuration src/main/webapp/WEB-INF/web.xml. Voici un exemple :

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
    version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <filter>
        <filter-name>StripesFilter</filter-name>
        <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>org.test.exemple</param-value>
        </init-param>        
    </filter>

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <servlet-name>DispatcherServlet</servlet-name>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>Home.htm</welcome-file>
    </welcome-file-list>
</web-app>

On vient donc de mettre Stripes comme filtre de requête. On indique le package de base des classes d’action org.test.exemple pour permettre l’auto-discover.

2.3 Fichier de ressources

Stripes nécessite tout de même un fichier de ressources avec les messages d’erreurs src/main/resources/StripesResources.properties. Dont voici le contenu :

# Resource strings used by the <stripes:errors> tag when there are no nested tags
stripes.errors.header=<div style="color:#b72222; font-weight: bold">Please fix the following errors:</div><ol>
stripes.errors.beforeError=<li style="color: #b72222;">
stripes.errors.afterError=</li>
stripes.errors.footer=</ol>

# Resource strings used by the <stripes:errors> tag when displaying errors for a
# specific field (e.g. <stripes:errors field="password"/>). If not supplied the
# values above will be used instead. stripes.fieldErrors.header=
stripes.fieldErrors.beforeError=<span style="color: #b72222;">
stripes.fieldErrors.afterError=</span><br />
stripes.fieldErrors.footer=

# Resource strings used by the stripes:messages tag
stripes.messages.header=<ul class="messages">
stripes.messages.beforeMessage=<li>
stripes.messages.afterMessage=</li>
stripes.messages.footer=</ul>

# Validation error messages produced by Stripes' built-in converter classes. These
# are default error messages and can be overridden on per-field and per-form levels. # Using the 'invalidNumber' error for a field 'age' of a form posting to
# '/user/Profile.action', the keys looked for (in order) would be:
#      1: /user/Profile.action.age.invalidNumber
#      2: /user/Profile.action.age.errorMessage
#      3: age.errorMessage
#      4: /user/Profile.action.invalidNumber
#      5: converter.number.invalidNumber
converter.number.invalidNumber=The value ({1}) entered in field {0} must be a valid number
converter.byte.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3}
converter.short.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3}
converter.integer.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3}
converter.float.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3}
converter.enum.notAnEnumeratedValue=The value "{1}" is not a valid value for field {0}
converter.date.invalidDate=The value ({1}) entered in field {0} must be a valid date
converter.email.invalidEmail=The value ({1}) entered is not a valid email address
converter.creditCard.invalidCreditCard=The value ({1}) entered is not a valid credit card number

# Validation error messages produced by Stripes' annotation based validations. These
# are default error messages and can be overridden on per-field and per-form levels. # Using the 'valueNotPresent' required field error for a field 'username' of a form
# posting to '/user/Register.action', the keys looked for (in order) would be:
#      1: /user/Register.action.username.valueNotPresent
#      2: /user/Register.action.username.errorMessage
#      3: username.errorMessage
#      4: /user/Register.action.valueNotPresent
#      5: validation.required.valueNotPresent
validation.required.valueNotPresent={0} is a required field
validation.minlength.valueTooShort={0} must be at least {2} characters long
validation.maxlength.valueTooLong={0} must be no more than {2} characters long
validation.minvalue.valueBelowMinimum=The minimum allowed value for {0} is {2}
validation.maxvalue.valueAboveMaximum=The maximum allowed value for {0} is {2}
validation.mask.valueDoesNotMatch=<em>{1}</em> is not a valid {0}
validation.expression.valueFailedExpression=The value supplied ({1}) for field {0} is invalid
validation.file.postBodyTooBig=Total upload size of {3} KB exceeds the maximum size of {2} KB

3 Page d’accueil

Dans l’exemple précédent, on indique que les pages .html sont gérées par Stripes. On indique aussi que la page d’accueil est Home.html.

Bien évidemment, il n’existe pas de fichier Home.html.

3.1 Classe d’action

La classe d’action sera donc dans le package org.test.exemple. Elle portera un nom non significatif.
En effet, c’est une annotation qui indiquera l’url de la classe HomeActionBean.java :

package org.test.exemple.action;

import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.UrlBinding;
import net.sourceforge.stripes.action.ActionBean;

@UrlBinding("/Home.htm")
public class HomeActionBean extends ActionBean {
    private ActionBeanContext context ;
    private String message ;
 
    @DefaultHandler
    public Resolution view() throws IOException {
     message = "Bonjour ! Ca s'affiche" ;
     
        return new ForwardResolution("/WEB-INF/jsp/home.jsp");
    }

    public String getMessage() {
        return message;
    }

    public void setContext(ActionBeanContext actionbeancontext) {
        this.context  = actionbeancontext ;
    }

    public abstract ActionBeanContext getContext() {
        return context ;
    }
}

Que constate-t-on ?
- La classe d’action est dans un sous package sans rien dire de plus,
- L’annotation UrlBinding + l’héritage de la classe ActionBean indique à Stripes qu’il s’agit d’une classe d’action,
- On a un attribut message avec un getter associe,
- On a une méthode view() avec une annotation et un retour,
- La nécessité d’implémenter setContext() et getContext().

3.1.1 Annotation UrlBinding

L’annotation UrlBinding fait le lien entre l’url récupérer par Stripes et la classe. Dans notre exemple, la page d’accueil est Home.html c’est donc cette classe qui va s’afficher.

3.1.2 Annotation DefaultHandler

Cette annotation indique à Stripes que par défaut, c’est cette méthode qui est appelée. En effet, Stripes permet à une même classe d’avoir plusieurs « évènement ». C’est-à-dire que dans la même classe d’action on peut regrouper les traitements qui seront déclenchés en fonction d’un paramètre de l’url.

3.1.3 Attribut de la classe

Stripes, à la différence de Struts, n’a pas de Bean. C’est la classe d’action (qui est instanciée à chaque requête) qui porte les données.
Cela permet de ne pas avoir une classe inutile et finalement c’est confortable et plutôt logique. Ces attributs sont accessibles depuis la JSP à condition d’avoir un getter (et un setter dans le cadre d’un formulaire).

3.1.4 Les méthodes setContext() et getContext()

Nous reviendrons sur ces deux méthodes plus tard dans le document. Ces méthodes sont appelées par Stripes pour passer le context (session) à la classe d’action et à la JSP.

3.2 La présentation (JSP)

3.2.1 Présentation simple

La Jsp d’affichage se trouve dans src/main/webapp/WEB-INF/jsp/home.jsp :

<%-- Stripes TLD --%>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="sdyn" uri="http://stripes.sourceforge.net/stripes-dynattr.tld" %>

<%-- JSTL TLDs --%>
<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core"      %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"       %>
<%@ taglib prefix="fn"  uri="http://java.sun.com/jsp/jstl/functions" %>

<%-- This is so that you can conveniently refer to the context path with ${contextPath} --%>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>

<html>
  <head>
    <title>Test</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" type="text/css" href="${contextPath}/css/style.css">
  </head>
  <body>
    ${actionBean.message}
  </body>
</html>

Pour afficher notre message, il suffit de mettre dans la Jsp : ${actionBean.message}.

3.2.2 Présentation en utilisant les layouts

Stripes permet d’utiliser des « calques » de présentation. Cela se rapproche un peu des thèmes.

Nous allons donc créer un « thème » dans src/main/webapp/WEB-INF/jsp/layout.jsp :

<%@ include file="/WEB-INF/jsp/taglibs.jsp" %>

<s:layout-definition>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>${title}</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" type="text/css" href="${contextPath}/css/style.css">
    <s:layout-component name="head">
    </s:layout-component>
  </head>
  <body>
      <s:layout-component name="body">
      </s:layout-component>
  </body>
</html>

</s:layout-definition>

La Jsp home aura alors ce contenu src/main/webapp/WEB-INF/jsp/home.jsp :

<%@ include file="/WEB-INF/jsp/taglibs.jsp" %>

<s:layout-render name="/WEB-INF/jsp/layout.jsp" title="Demo Stripes">
  <s:layout-component name="body">
    ${actionBean.message}
  </s:layout-component>
</s:layout-render>

Vous avez peut-être remarqué que le layout prend un paramètre ${home} qui ne vient pas de notre classe d’action mais de la Jsp qui l’inclus.

4 Gestion du context et personnalisation

Précédemment, nous avons vu qu’il fallait implémenter un getter et un setter de context. Ce context correspond au context de la requête/session, avec des éléments fournis par Stripes.

4.1 Classe de base

On peut créer déjà une classe de base afin d’éviter de ré-implémenter à chaque fois les deux méthodes:

public class BaseActionBean implements ActionBean {
    protected ActionBeanContext context;
    
    public ActionBeanContext getContext() {
        return context;
    }
    public void setContext(ActionBeanContext context) {
        this.context = context;
    }
}

4.2 Personnalisation du context

Il pourrait être intéressant de personnaliser le context. Par exemple, lorsqu’un utilisateur s’identifie, on met en session son nom et ses préférences (langue, couleur….).

Il serait judicieux d’ajouter à ce context des méthodes qui font cela. Et bien Stripes le permet.

Il suffit de changer la classe de base :

public class BaseActionBean implements ActionBean {
    protected MonActionBeanContext context;
    
    public ActionBeanContext getContext() {
        return context;
    }
    public void setContext(ActionBeanContext context) {
        this.context = (MonActionBeanContext) context;
    }
}

Mais comment Stripes va savoir qu’il faut instancier cette classe ?

Cela se fait dans le web.xml comme ceci :

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
    version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <filter>
        <filter-name>StripesFilter</filter-name>
        <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>org.test.exemple</param-value>
        </init-param>
 <init-param>
            <param-name>ActionBeanContext.Class</param-name>
            <param-value>org.test.exemple.action.MonActionBeanContext</param-value>
        </init-param>
    </filter>

    ...
</web-app>

Et voilà, on indique à Stripes quelle classe instancier. A présent voici la classe :

import net.sourceforge.stripes.action.ActionBeanContext;

public class MonActionBeanContext extends ActionBeanContext {
 
}

Ici elle est vide pour l’exemple, mais s’il n’y a pas de ClassCastException, c’est que cela fonctionne. Vous pouvez vérifier en ajoutant quelque chose dans la classe ou via un debugger.

5 Paramètres d’url et lien

Stripes permet de récupérer dans l’url directement des valeurs à l’aide de l’annotation UrlBinding. Cela permet entre autre de faire comme de l’url rewriting qui est souvent mis en place avec PHP.

Imaginons que nous avons une liste de produit récupéré en base de données. Nous voulons lorsqu’on clique sur le lien passer l’id.

Plutôt que d’avoir une url de type http://localhost/test_stripes/produit.html?id=1 nous souhaiterions avoir http://localhost/test_stripes/produit-1.html.

Voilà comment y parvenir très simplement :

@UrlBinding("/produit-{idProduit}.htm")
public class DisplayProduitActionBean extends BaseActionBean {
    private int idProduit ;
 
    @DefaultHandler
    public Resolution view() throws IOException {     
        return new ForwardResolution("/WEB-INF/jsp/produit.jsp");
    }

    public int getIdProduit() {
        return idProduit;
    }

    public setIdProduit(id) {
        idProduit = id ;
    }
}

On indique à Stripes qu’un des attributs que notre classe est alimenté par l’url. Attention ne pas oublier le getter pour récupérer la valeur depuis la Jsp.
Attention de ne pas oublier le setter pour récupérer la valeur depuis l’url !

Note : la classe BaseActionBean est une classe qui implémente setContext() et getContext() pour plus de facilité. Dans la Jsp qui affiche la liste de produit, on fait le lien sur le produit comme ceci :

<s:link beanclass="org.test.exemple.action.DisplayProduitActionBean" >
 <s:param name="idProduit" value="${item.id}"></s:param>
 Afficher
</s:link>     

La balise Stripes link permet de retrouve automatiquement l’url d’appel via le nom de la classe d’action. On indique la valeur du paramètre.

Note : ${item.id} correspond à la variable contenant l’id du produit. Nous verrons par la suite comment itérer sur des listes.

6 Gestion des évènements

Stripes permet à une classe d’action à répondre à plusieurs « événement ». Par exemple, la classe d’action ProduitActionBean doit gérer l’ajout, suppression, modification.

Traditionnellement, cela se fait avec un paramètre dans l’url. Ce paramètre est récupéré dans la classe et il y a un if ou un swith. Pas très élégant.

Stripes permet de gérer cela de la même façon (via un paramètre) mais nous affranchi de lire ce paramètre.

6.1 Mise en place d’évènement dans la classe d’action

Nous avons vu l’annotation @DefaultHandler, elle indique que s’il n’y a pas de paramètre d’évènement passer dans l’url, c’est la méthode dessous qui sera appelé. Il est possible d’utiliser l’annotation @HandlesEvent("xxxx") pour ajouter un évènement.

Imaginons la classe suivante :

@UrlBinding("/produit-maj.htm{$event}")
public class MajProduitActionBean extends BaseActionBean {
    private int idProduit ;
 
    @DefaultHandler
    public Resolution view() throws IOException {     
        idProduit = 1 ;
        return new ForwardResolution("/WEB-INF/jsp/produit.jsp");
    }

    @HandlesEvent("update")
    public Resolution add() {  
     return new RedirectResolution(HomeActionBean.class); 
    }

    public int getIdProduit() {
        return idProduit;
    }

    public setIdProduit(id) {
        idProduit = id ;
    }
}

On a donc une classe qui affiche un produit par défaut. Lorsqu’on l’évènement « update » est déclenché, il retourne vers la page d’accueil.

6.2 Déclenchement des évènements via les Jsp

Via un formulaire le déclenchement se fera :

<s:form beanclass="org.test.exemple.MajProduitActionBean">
 <s:text name="idProduit " value="${actionBean.idProduit }" />
 <s:submit name="update" value="Mettre a jour"/>
</s:form>

Via un lien :

<s:link beanclass="org.test.exemple.action.DisplayProduitActionBean" event="update">
 <s:param name="idProduit" value="${item.id}"></s:param>
 Afficher
</s:link>    

7 Formulaire

7.1 Validation

Stripes propose une validation de formulaire par annotation. Voici un exemple simple :

@Validate(required = true, on = "view")
private int idAnounce ;

@ValidateNestedProperties({
 @Validate(
  required = true,
  on = "update",
  field= "title"),
 @Validate(
   required = true,
   on = "update",
   field= "price")   
})
private ItemBean itemBean = new ItemBean() ;

Comme on peut le remarquer la validation peut être différente selon l’évènement.

7.2 Champs

Stripes reprend les principes de ses précédesseurs. Pour déclarer un formulaire :

<s:form beanclass="org.test.exemple.action.DisplayProduitActionBean">
</s:form>

Pour un champ caché :

<s:hidden name="itemBean.id" value="${actionBean.itemBean.id}" />

Pour une zone de texte simple ligne :

Titre : <s:text name="itemBean.title" value="${actionBean.itemBean.title}" />

De type nombre :

Prix : <s:text name="itemBean.price" value="${actionBean.itemBean.price}" formatType="number" />

Il aussi possible de faire des listes déroulantes :

Categorie : <s:select name="itemBean.category">
              <s:option value="">Select One</s:option>
              <s:options-map map="${actionBean.mapCategory}"/>
           </s:select>

Dans la classe d’action la catégorie est initialisée :

mapCategory = new HashMap() ;
mapCategory.put("Voiture", "Voiture") ;
mapCategory.put("Informatique", "Informatique") ;

La clef sera la value de la ligne et la donnée sera le libellé affiché.

8 Conclusion

Bien que nous ayons parcourus rapidement les possibilités de Stripes, il ressort néanmoins une grande facilité de mise en oeuvre. La documentation est assez bien fournie. Il manque seulement quelques fois des petites précisions.
Néanmoins, comparée à la documentation très volumineuse de Spring MVC, la documentation de Stripes permet un accès simple et rapide.

Aucun commentaire:

Publier un commentaire