• 10 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 03/09/2019

Affinez le flot d'exécution avec les interceptors

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Reprenons le schéma de fonctionnement général de Struts :

Schéma général
Fonctionnement général de Struts

Nous avons manipulé, les Actions, les Results, les JSP. Il ne nous reste plus qu'à voir un dernier élément intervenant dans le fonctionnement de Struts : la pile d'Interceptors.

Bon en fait, ça fait 2 éléments : les Interceptors et la pile d'Interceptors (appelée interceptor stack).

La pile d'Interceptors repose sur le design pattern chaîne de responsabilité. Le principe est le suivant : quand une requête arrive :

  1. après quelques vérifications et récupération de la configuration qui s'applique,

  2. Struts passe la requête au premier Interceptor de la stack.

  3. Celui-ci effectue le traitement qui lui incombe et, en fonction du contexte, décide soit d'invoquer l'Interceptor suivant soit d'interrompre la chaîne d'exécution.

Configurer la pile d'interceptors

Nous avons déjà manipulé la pile d'interceptors dans le chapitre traitant de l'interrogation de l'utilisateur. Nous avions ajouté un interceptor au dessus de la stack par défaut pour enregistrer et restituer les messages d'information à travers une redirection.

La configuration de la pile d'interceptors se fait dans le fichier struts.xml, en listant avec la balise interceptor-ref les interceptors dans l'ordre souhaité.

Par exemple :

<action name="demo">
    <interceptor-ref name="interceptorA"/>
    <interceptor-ref name="interceptorB"/>
    <interceptor-ref name="interceptorC"/>
</action>

La pile de l'action demo sera alors consituée la chaîne d'interceptors :

  1. interceptorA

  2. interceptorB

  3. interceptorC

Il est possible de créer une pile nommée (appelée une stack) et de la référencer comme pile d'interceptors :

<interceptor-stack name="stackZ">
    <interceptor-ref name="interceptorA"/>
    <interceptor-ref name="interceptorB"/>
    <interceptor-ref name="interceptorC"/>
</interceptor-stack>

<action name="demo">
    <interceptor-ref name="stackZ"/>
</action>

Il est même possible de référencer une stack existante dans une autre stack :

<interceptor-stack name="stackY">
    <interceptor-ref name="interceptorA"/>
    <interceptor-ref name="interceptorB"/>
</interceptor-stack>

<interceptor-stack name="stackZ">
    <interceptor-ref name="stackY"/>
    <interceptor-ref name="interceptorC"/>
</interceptor-stack>

<action name="demoA">
    <interceptor-ref name="stackZ"/>
</action>

<action name="demoB">
    <interceptor-ref name="interceptorH"/>
    <interceptor-ref name="stackY"/>
</action>

La notion de package

OK, on a vu comment définir la pile d'interceptor au niveau de l'action, mais il est possible de généraliser les choses en mettant ces définitions au niveau package.

Package ?

Dans Struts, un package permet de regrouper des actions, results... de manière logique d'un point de vue de la configuration Struts.

Par exemple, imaginez que vous vouliez que votre application soit composée d'une partie publique et d'une partie privée.

Vous pouvez créer 2 packages :

  • le package public regroupera les actions accessibles à tout le monde,

  • le package private regroupera les actions accessibles uniquement aux utilisateurs connectés.

Dans le package private vous définirez alors une pile d'interceptors contenant en plus un interceptor vérifiant que l'utilisateur envoyant la requête est bien connecté.

<struts>
    <!-- ===== Package pour les actions publiques ===== -->
    <package name="public" extends="struts-default">
        ...
        <action name="projet_list" class="org.example.demo.ticket.webapp.action.GestionProjetAction" method="doList">
            <result>/jsp/projet/list.jsp</result>
        </action>
    </package>


    <!-- ===== Package pour les actions privées ===== -->
    <package name="private" extends="struts-default">
        <interceptors>
            <interceptor-stack name="authenticatedStack">
                <interceptor-ref name="auth" />
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>

        <default-interceptor-ref name="authenticatedStack" />

        <action name="projet_new" class="org.example.demo.ticket.webapp.action.GestionProjetAction" method="doCreate">
            <result name="input">/jsp/projet/new.jsp</result>
            <result name="success" type="redirectAction">
                <param name="actionName">projet_detail</param>
                <param name="id">${projet.id}</param>
            </result>
        </action>

        ...
    </package>
</struts>

L'extension de package

Vous avez maintenant 2 packages séparés. Ce qui serait bien, ce serait que le package private bénéficie des éléments de configuration de public comme les global results par exemple.

Eh bien cela est possible en utilisant le mécanisme d'extension.

On peut, par exemple faire comme ceci :

  • le packagepublic étend le packagestruts-default ;

  • et le packageprivate va étendre le packagepublic

<struts>
    <!-- ===== Package pour les actions publiques ===== -->
    <package name="public" extends="struts-default">
        <global-results>
            <result name="error">/jsp/error.jsp</result>
        </global-results>
        ...
    </package>


    <!-- ===== Package pour les actions privées ===== -->
    <package name="private" extends="public">
        ...
    </package>
</struts>

Une autre organisation possible est de regrouper toute la configuration générale de l'application dans un package de base abstrait (c-à-d qui n'aura aucune action) et ensuite étendre ce package dans les packagespublic et private :

<struts>
    <!-- ===== Package de configuration de base ===== -->
    <package name="base" abstract="true" extends="struts-default">
        <interceptors>
            <interceptor-stack name="authenticatedStack">
                <interceptor-ref name="auth" />
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>

        <global-results>
            <result name="error">/jsp/error.jsp</result>
        </global-results>
        ...
    </package>


    <!-- ===== Package pour les actions publiques ===== -->
    <package name="public" extends="base">
        <action name="projet_list" class="org.example.demo.ticket.webapp.action.GestionProjetAction" method="doList">
            <result>/jsp/projet/list.jsp</result>
        </action>
        ...
    </package>


    <!-- ===== Package pour les actions privées ===== -->
    <package name="private" extends="base">
        <default-interceptor-ref name="authenticatedStack" />

        <action name="projet_new" class="org.example.demo.ticket.webapp.action.GestionProjetAction" method="doCreate">
            <result name="input">/jsp/projet/new.jsp</result>
            <result name="success" type="redirectAction">
                <param name="actionName">projet_detail</param>
                <param name="id">${projet.id}</param>
            </result>
        </action>
        ...
    </package>
</struts>

Créer un interceptor

Nous avons vu comment personnaliser les piles d'interceptors applicables aux actions, voyons maintenant comment créer un Intercaptor.

Je vous propose d'implémenter l'interceptor en charge de vérifier qu'un utilisateur est bien connecté.

Implémenter un interceptor

Pour cela je crée un package org.example.demo.ticket.webapp.interceptor et y crée une classe AuthInterceptor héritant de la classe AbstractInterceptor et implémentant la méthode abstraite intercept(ActionInvocation) :

package org.example.demo.ticket.webapp.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

/**
 * Interceptor permettant de vérifier qu'utilisateur est bien connecté
 */
public class AuthInterceptor extends AbstractInterceptor {

    private static final long serialVersionUID = 7995153741671857846L;

    @Override
    public String intercept(ActionInvocation pInvocation) throws Exception {
        String vResult;
        if (pInvocation.getInvocationContext().getSession().get("user") != null) {
            vResult = pInvocation.invoke();
        } else {
            vResult = "error-forbidden";
        }
        return vResult;
    }
}

La méthode intercept(ActionInvocation) renvoie un String qui, comme pour les méthodes des Actions, correspond au nom du Result qui devra être utilisé par Struts pour la réponse.

Dans cet interceptor, je m'assure simplement qu'il y ait un utilisateur en session. Si c'est bien le cas, j'appelle l'interceptor suivant via pInvocation.invoke(). Sinon, je ne vais pas plus loin dans la chaîne des interceptors et renvoie le nom de Resulterror-forbidden.

Déclarer un interceptor

Maintenant que l'interceptor est implémenté, il faut le déclarer. Cela se fait à l'intérieur d'un package dans la balise <interceptors> :

<struts>
    <package name="base" abstract="true" extends="struts-default">
        <interceptors>
            <interceptor name="auth" class="org.example.demo.ticket.webapp.interceptor.AuthInterceptor" />
            ...

            <interceptor-stack name="authenticatedStack">
                <interceptor-ref name="auth" />
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>

        ...
    </package>
</struts>

Et enfin le plus important : la serviette

Si vous avez suivi, il manque un dernier petit détail : la configuration du resulterror-forbidden.

Celui-ci n'étant pas lié à une action en particulier, je vais le déclarer en global result dans le package de base. Ce result doit renvoyer une erreur HTTP de code 403 (qui est le code pour une erreur de type accès interdit).

Le type de result à utiliser pour cela est le type httpheader :

<struts>
    <package name="base" abstract="true" extends="struts-default">
        <global-results>
            <result name="error-forbidden" type="httpheader">
                <param name="error">403</param>
            </result>
            ...
        </global-results>
        ...
    </package>
</struts>

Encore merci pour le poisson

Vous vous sentez peut-être un peu perdu à la fin de ce chapitre. Don't panic !

Maintenant que vous avez vu les notions de package, stack, interceptor... je vous conseille de le relire calmement.

En effet, tous ces éléments sont liés et il est difficile de parler de chaque notion de manière séparée, les unes après les autres. Une vision globale étant nécessaire pour bien comprendre.

Ensuite, rassurez-vous. Struts fournit déjà un certain nombre d'interceptors et de stacks par défaut qui conviennent dans la plupart des cas. N'hésitez pas à y regarder de plus près. Dans les faits vous n'aurez que très peu d'interceptors à implémenter, hormis l'interceptor d'authentification !

Pour compléter ce chapitre, je vous invite à consulter la documentation officielle sur les interceptors. Vous y trouverez des informations supplémentaires et des explications avec une approche différente.

Exemple de certificat de réussite
Exemple de certificat de réussite