SeamFramework.orgCommunity Documentation
En complément du modèle composant contextuel, il y a deux autres concepts de base qui facilitent le couplage extrèmement faible qui est une fonctionnalité distincte des applications Seam. Le premier est un modèle d'évènement solide où les évènements peuvent être liés à des écouteurs d'évènements via des expressions reliant des méthodes ressemblant à JSF. Le second est une utilisation efficace des annotations et des intercepteurs pour appliquer des coupes/incisions dans les composants qui implémentent la logique métier.
Le modèle de composant de Seam a été développé pour l'utiliser avec les applications conducteur-d-évènement, en particulier disponibles pour développer des composants à grains fins et faiblement couplés dans un modèle évènementiel finement dimensionné. Les évènements dans Seam viennent de plusieurs types, la plus part ont déjà été vu:
les évènements JSF
les évènements de transition jBPM
les actions de page de Seam
les évènements conducteur-de-composant de Seam
les évènements contextuel de Seam
Tous ces différents types d'évènements sont reliés à des composants Seam via des expressions relian une méthode JSF EL. Pour un évènement JSF, c'est défini dans un modèle JSF:
<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/>
Pour un évènement de transition jBPM, il est spécifié dans la définition de processus jBPM ou dans une définition d'enchainement de page:
<start-page name="hello" view-id="/hello.jsp">
<transition to="hello">
<action expression="#{helloWorld.sayHello}"/>
</transition>
</start-page
>
Vous pouvez découvrir beaucoup d'information à propos des évènements JSF et des évènements jBPM plus loin. Concentrons nous sur les deux autres types d'évènements additionnel défini par Seam.
Une action de page de Seam est un évènement qui intervient juste avant que nous rendions la page. Nous déclarons les actions de pages dans WEB-INF/pages.xml
. Nous pouvons définir une action de page à la fois comme un identifiant de vue JSF particulier:
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}"/>
</pages
>
Ou nous pouvons utiliser un jocker *
comme suffixe à view-id
pour spécifier une action qui s'applique à tous les identifiants de vue qui corresponde à ce patron:
<pages>
<page view-id="/hello/*" action="#{helloWorld.sayHello}"/>
</pages
>
Gardez à l'esprit que l'élément <page>
est définie dans un descipteur de page à granularité fine, l'attribut view-id
peux être laissé de côté avant d'avoir besoin de l'appliquer.
Si des pages d'action avec de multiples jockers correspondent à l'identifiant d'une vue, Seam va appeler touutes les actions dans l'ordre du moins spécifique au plus spécifique.
La méthode d'action de page retourne une sortie JSF. Si la sortie est non-nulle, Seam va l'utiliser pour définir les règles de navigation pour naviguer vers une vue.
Par la suite, l'identifiant de vue est mentionné dans l'élément <page>
qui n'a pas besoin de correspondre à une vrai JSP ou une page Facelets! Donc, nous pouvons reproduire la fonctionnalité d'une serveur de squellette d'application orienté action comme Struts ou WebWork en utilisant des actions de page. Ceci est bien utile si vous voulez faire des choses complexes en réponse à des reqquêtes non-faces (par exemple, des requêtes HTTP GET).
Des actions de pages multiples ou conditionnelles peuvent être spécifié en utilisant le tag <action>
:
<pages>
<page view-id="/hello.jsp">
<action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
<action execute="#{hitCount.increment}"/>
</page>
</pages
>
Les actions de page sont exécuté à la fois dans un requête initiale (non-faces) et dans une requête postérieure (faces). Si vous utilisez les actions de page pour charger des données, cette opération peut rentrer en conflit avec les action(s) standard(s) de JSF en cours d'éxécution dans la phase postérieure. Une façon de désactiver l'action de la page est de configurer une condition qui se résoud à vrai seulement avec une requête initiale.
<pages>
<page view-id="/dashboard.xhtml">
<action execute="#{dashboard.loadData}"
if="#{not facesContext.renderKit.responseStateManager.isPostback(facesContext)}"/>
</page>
</pages
>
Cette condition consulte le ResponseStateManager#isPostback(FacesContext)
pour déterminer si la requête est une postérieure. Le ResponseStateManager est accédé en utilisant FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager()
.
Pour vous préserver de ce côté verbeux des API de JSF, Seam vous offre une condition livrée qui vous permet d'accomplir la même chose avec un gain en quantité à taper. Vous pouvez désactiver une action de page sur la phase postérieure simplement en définiant le on-postback
à false
:
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}"/>
</pages
>
Pour des raisons de compatiblité descendante, la valeur par défaut de l'attribut on-postback
est vrai, pensez donc que vous allez finir par utiliser le réglage inverse de plus en plus souvent.
Une requête faces JSF(une soumission de formulaire) est encapsulée à la fois dans une "action" (par relation de méthode) et des "paramètres" (par relation de valeur entrées). Une action de page peut aussi avoir besoin de paramètres!
Avec les requêtes GET qui sont en marque-page, les paramètres de page sont passés comme des paramètres de requêtes lisibles par un être humain. (A la différence des entrées de formulaire, qui sont tout autrement!)
Vous pouvez utiliser les paramètres de page avec ou sans une méthode d'action.
Seam nous permet de fournir une valeur liée qui est en relation avec le paramètre de la requête d'un attribut de l'objet modèle.
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
<param name="firstName" value="#{person.firstName}"/>
<param name="lastName" value="#{person.lastName}"/>
</page>
</pages
>
La déclaration de <param>
est bidirectionnel, tout comme la valeur liée à une entrée de JSF:
Quand une requête non-faces (GET) pour l'identifiant de la vue intervient, Seam affecte la valeur du paramètre de la requête dénomée après avoir réaliser les conversions de types approriées.
Tous les <s:link>
ou <s:button>
vont inclure le paramètre de la requête de manière transparentre. La valeur du paramètre est détérminée en évaluant la valeur liée pendant la phase de rendu (quand le <s:link>
est rendu).
Chaque règle de navigation avec un <redirect/>
vers l'identifiant de la vue inclus de manière transparente le paramètre de la requête. La valeur du paramètre est déterminée en évaluant la valeur liée à la fin de la phase d'invocation de l'application.
La valeur est de manière transparent propagée avec chaque soumission de formulaire JSF pour la page avec l'identifiant de vue donné. (Cela signifie que les paramètres de la vue fonctionne comme des variables de contexte d'étendue PAGE
pour les requêtes de faces).
L'idée essentielle derrière tout cela est que depuis n'importe quelle page dont nous venions en allant vers /hello.jsp
( ou depuis /hello.jsp
vers /hello.jsp
), la valeur de l'attribut du modèle est référencée dans la valeur liée qui est "mémorisée", sans avoir le besoin d'une conversation (ou d'un autre état côté serveur).
Si seulement l'attribut name
est spécifié alors le paramètre de requête est propagé en utilisant le contexte de PAGE
(s'il n'est pas lié avec une propriété du model).
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
<param name="firstName" />
<param name="lastName" />
</page>
</pages
>
La propagation des paramètres de page est particulièrement utile si vous voulez construire des pages CRUD à multiples couches de type maitre-détails. Vous pouvez l'utiliser pour pour vous "souvenir" quelle vue vous etiez précédemment (par exemple en appuyant sur le bouton Sauver) et quelle entité vous etiez en train d'éditer.
Tout <s:link>
ou <s:button>
propage de manière transparente le paramètre de requête si le paramètre est listé comme un paramètre de page de la vue.
La valeur est propagée de manière transparente avec une soumission de formulaire JSF de la page avec l'identifiant de vue donné. (Ce qui signifie que les paramètres de vue fonctionne comme les variables de contexte d'étendue PAGE
pour les requêtes faces).
Tout ce bazar à l'air assez comple, et vous allez probablement vous inquiéter d'une telle construction exotique vaut réellement l'effort. Pour l'instant, l'idée est très naturelle une fois que vous "l'avez". C'est définitivement beaucoup mieux de prendre son temps pour comprendre ce truc. Les paramètres de pages sont la façon la plus élégante de propager un état au travers de requêtes non-faces. C'est spécialement sympa pour les problèmes comme des écrans de recherches avec des pages de résultats stocké en favoris, quand vous voudrier être capable d'écrire le code de votre application pour gérer à la fois les requêtes GET et POST avec le même code. Les paramètres de page éliminent la vérification répétitive des paramètres de requêtes dans la définition de la vue et rend la redirection plus facile à coder.
La ré-écriture intervient se basant sur des patrons de ré-écriture trouvés pour les vues dans pages.xml
. La ré-écriture d'URL de Seamfait à la fois de la ré-écriture entrante et sortante se bansant sur les mêmes patrons. Voici un patron simple:
<page view-id="/home.xhtml">
<rewrite pattern="/home" />
</page>
Dans ce cas, une requête entrante pour /home
sera envoyé vers /home.xhtml
. Plus intéréssant, tout lien généré qui devrait normallement pointer vers /home.seam
sera alors ré-écrit comme /home
. Les patrons de ré-écriture font correspondrent seulement la portion de l'URL avant les paramètres de requêtes. Donc, /home.seam?conversationId=13
et /home.seam?color=red
seront tout deux détecté par la règle de ré-écriture.
Les règles de ré-écritures peuvent prendre des paramètres de requêtes en considération, comme indiqué dans les règles suivantes.
<page view-id="/home.xhtml">
<rewrite pattern="/home/{color}" />
<rewrite pattern="/home" />
</page>
Dans ce cas, une requête entrante pour /home/red
sera servie comme si elle était une requête pour /home.seam?color=red
. De manière similaire, si la couleur est un paramètre de page d'une URL sortante qui devrait normallement se voir comme /home.seam?color=blue
sera plutôt écrite comme /home/blue
. Les règles sont exécutées dans l'odre, donc il est importante de les lister les règle les plus spécifiques avant les règles les plus généralistes.
Les paramètres par défaut de requêtes de Seam peuvent aussi être mis en correspondance en utilisant la ré-écriture d'URL, permettant d'avoir l'option de cacher les données techniques de Seam. Dans l'exemple suivant, /search.seam?conversationId=13
sera ré-écrit comme /search-13
.
<page view-id="/search.xhtml">
<rewrite pattern="/search-{conversationId}" />
<rewrite pattern="/search" />
</page>
La ré-écriture d'URL de Seam fourni une ré-écriture bi-directionnelle et simple basée pour chaque vue. Pour des règles de ré-écritures plus complexes qui couvrent des composants non-sean, les applications de Seam peuvent continuer à utiliser org.tuckey URLRewriteFilter
ou appliquer des règles de ré-écritures dans le serveur web.
La ré-écriture d'URL nécéssite que le filtre de ré-écriture de Seam soit actif. La configuration du filtre de ré-écriture sera vue dans Section 30.1.4.3, « URL rewriting ».
Vous pouvez indiquer un convertisseur JSF pour les propriétés du modèles complexes:
<pages>
<page view-id="/calculator.jsp" action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converterId="com.my.calculator.OperatorConverter" value="#{calculator.op}"/>
</page>
</pages
>
Ou autrement:
<pages>
<page view-id="/calculator.jsp" action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
</page>
</pages
>
Les validateurs de JSF, etrequired="true"
peuvent être utilisés:
<pages>
<page view-id="/blog.xhtml">
<param name="date"
value="#{blog.date}"
validatorId="com.my.blog.PastDate"
required="true"/>
</page>
</pages
>
Ou autrement:
<pages>
<page view-id="/blog.xhtml">
<param name="date"
value="#{blog.date}"
validator="#{pastDateValidator}"
required="true"/>
</page>
</pages
>
Même mieux, les annotations de validation de modèles basés sur Hibernate sont automatiquement reconnues et validées. Seam peut aussi fournir un convertisseur de date par défaut pour convertir une valeur de type chaine de caractère vers une date et dans l'autre sens.
Quand une conversion de type ou une validation échoue, un FacesMessage
global est ajouté au FacesContext
.
Vous pouvez utiliser les règles de navigation JSF standard définies dans le faces-config.xml
pour une application Seam. Malgré tout cela, les règles de navigation JSF ont de nombreuses limitations génantes:
Il n'est pas possible de spécifier les paramètres de requêtes pouvant être utilisés dans la redirection.
Il n'est pas possible de démarrer ou de finir des conversations depuis une règle.
Les règles fonctionnent en évaluant le retour de valeur des méthode d'action; il n'est pas possible d'évaluer une expression EL arbitraire.
Un problème plus important est que la logique "d'orchestration" doit être éclaté entre pages.xml
et faces-config.xml
. C'est mieux d'unifier cette logique dans pages.xml
.
Cette règle de navigation JSF:
<navigation-rule>
<from-view-id
>/editDocument.xhtml</from-view-id>
<navigation-case>
<from-action
>#{documentEditor.update}</from-action>
<from-outcome
>success</from-outcome>
<to-view-id
>/viewDocument.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
Can be rewritten as follows:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if-outcome="success">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
Mais c'est franchement mieux si nous pouvons éviter de poluer notre composant DocumentEditor
avec une valeur retournée de type string (la sortie JSF). Ainsi Seam nous permet d'écrire:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}"
evaluate="#{documentEditor.errors.size}">
<rule if-outcome="0">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
Ou même:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
La première forme évalue une valeur liée pour déterminer la valeur de la sortie qui doit être utilisée par les règles subséquentes. La seconde approche ignore la sortie et evalue une valeur liée pour chaque règle possible.
Bien sûr, quand une mise à jour réussie, nous voulons probablement finir la conversation courante. Nous pouvons faire cela comme ceci:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<end-conversation/>
<redirect view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
En terminant la conversation toutes les requêtes soujacentes ne vont pas savoir quel document nous nous sommes intéréssé. Nous pouvons passer l'identifiant du document comme un paramètre de requête ce qui rend la vue disponible pour être mise en favoris:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule if="#{documentEditor.errors.empty}">
<end-conversation/>
<redirect view-id="/viewDocument.xhtml">
<param name="documentId" value="#{documentEditor.documentId}"/>
</redirect>
</rule>
</navigation>
</page
>
La sortie Null est un cas spécial en JSF. La sortie null est interprétée comme un "réaffiche la page". La règle de navigation suivante correspond à une sortie non-null, mais pas à la sortie null:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule>
<render view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
Si vous voulez exécutez l'enchainement de page quand une sortie null intervient, utilisez plutôt la forme suivante:
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<render view-id="/viewDocument.xhtml"/>
</navigation>
</page
>
L'identifiant de vue peut être donné comme une expression EL de JSF:
<page view-id="/editDocument.xhtml">
<navigation>
<rule if-outcome="success">
<redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
</rule>
</navigation>
</page
>
Si vous avez beaucoup d'actions de page différentes et de paramêtres de pages, ou même juste beaucoup de règles de navigation, vous voudriez certainement diviser leurs déclarations dans plusieurs fichiers. Vous pouvez définir les actions et les paramêtres dans une page avec l'identifiant de vue /calc/calculator.jsp
dans une ressource nommée calc/calculator.page.xml
. L'élément root dans ce cas est un élément <page>
, et l'identifiant la vue est tacite:
<page action="#{calculator.calculate}">
<param name="x" value="#{calculator.lhs}"/>
<param name="y" value="#{calculator.rhs}"/>
<param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
</page
>
Les composants de Seam peuvent interagir en appelant simplement les méthodes des autres. Les composants avec état peuvent implémenter le patron de conception observeur/observable. Ainsi pour activer les composants à intéragir d'une façon faiblement couplée c'est possible quand le composant appele les méthodes des autres composants directement, Seam fournie des évènement conducteur de composants.
Vous pouvez spécifier un écouteur d'évènement (observateurs) dans components.xml
.
<components>
<event type="hello">
<action execute="#{helloListener.sayHelloBack}"/>
<action execute="#{logger.logHello}"/>
</event>
</components
>
Ici le type d'évènement est juste une chaine de caractères arbitraire.
Quand un évènement se produit, les actions enregistrées pour cet évènement vont être appelés dans l'ordre dans lequel ils apparaissent dans components.xml
. Comment fait un composant pour déclencher un évènement ? Seam fourni un composant livré pour cela.
@Name("helloWorld")
public class HelloWorld {
public void sayHello() {
FacesMessages.instance().add("Hello World!");
Events.instance().raiseEvent("hello");
}
}
Or you can use an annotation.
@Name("helloWorld")
public class HelloWorld {
@RaiseEvent("hello")
public void sayHello() {
FacesMessages.instance().add("Hello World!");
}
}
Notez que ce producteur d'évènement n'a aucune dépendence vis-à-vis des consommateurs d'évènements. L'écouteur d'évènement devrait maintenant être implémenté sans absolument aucune dépendence avec le producteur:
@Name("helloListener")
public class HelloListener {
public void sayHelloBack() {
FacesMessages.instance().add("Hello to you too!");
}
}
La méthode liante défnie dans components.xml
ci-dessus prend garde à la liaison de l'évènement vers le consommateur. Si vous n'aimez pas perdre du temps avec le fichier components.xml
, vous pouvez à la place utiliser une annotation:
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack() {
FacesMessages.instance().add("Hello to you too!");
}
}
Vous devez vous interroger pourquoi je n'ai rien mentionné à propos des objets évènements dans cette discution. Dans Seam, il n'y a pas besoin d'un objet évènement pour propager l'état entre un producteur d'évènement et un écouteur. L'état est contenu dans les contextes de Seam, et il est partagé entre les composants. Malgrès tout si vous voulez passer un objet évènement, vous pouvez:
@Name("helloWorld")
public class HelloWorld {
private String name;
public void sayHello() {
FacesMessages.instance().add("Hello World, my name is #0.", name);
Events.instance().raiseEvent("hello", name);
}
}
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack(String name) {
FacesMessages.instance().add("Hello #0!", name);
}
}
Seam définie un nombre d'évèvement livrés que l'application peut utiliser pour réaliser des trucs spéciaux de l'intégration du serveur d'application. Les évènements sont:
org.jboss.seam.validationFailed
— appelé quand la validation JSF échoue
org.jboss.seam.noConversation
— appelé quand il n'y a pas de conversation à éxécution longue et qu'une conversation à éxécution longue est requise
org.jboss.seam.preSetVariable.<name>
— appelé quand la variable de contexte <name> est définie
org.jboss.seam.postSetVariable.<name>
— appelé quand la variable de contexte <name> est définie
org.jboss.seam.preRemoveVariable.<name>
— appelé quand la variable de contexte <name> est non-définie
org.jboss.seam.postRemoveVariable.<name>
— appelé quand la variable de contexte <name> est non-définie
org.jboss.seam.preDestroyContext.<SCOPE>
— appelé avant que le contexte <SCOPE> ne soit détruit
org.jboss.seam.postDestroyContext.<SCOPE>
— appelé après que le contexte <SCOPE> est détruit
org.jboss.seam.beginConversation
— appelé à chaque fois qu'une conversation à éxécution longue commence
org.jboss.seam.endConversation
— appelé à chaque fois qu'une conversation à éxécution longue finie
org.jboss.seam.conversationTimeout
— appelé quand une conversation à éxécution longue se périme. L'identifiant de conversation est en paramètre.
org.jboss.seam.beginPageflow
— appelé quand un echainement de page commence
org.jboss.seam.beginPageflow.<name>
— appelé quand l'enchainement de page <name> commence
org.jboss.seam.endPageflow
— appelé quand un enchainement de page se termine
org.jboss.seam.endPageflow.<name>
— appelé quand l'enchainement de pages <name> se termine
org.jboss.seam.createProcess.<name>
— appelé quand le processus <name> est créé
org.jboss.seam.endProcess.<name>
— appelé quand le processus <name> se termine
org.jboss.seam.initProcess.<name>
— appelé quand le processus <name> est associé avec la conversation
org.jboss.seam.initTask.<name>
— appelé quand la tâche <name> est associée avec la conversation
org.jboss.seam.startTask.<name>
— appelé quand la tâche <name> est démarrée
org.jboss.seam.endTask.<name>
— appelé quand la tâche <name> est terminée
org.jboss.seam.postCreate.<name>
— appelée quand le composant <name> est créé
org.jboss.seam.preDestroy.<name>
— appelé quand le composant <name> est détruit
org.jboss.seam.beforePhase
— appelé avant le démarrage d'une phase JSF
org.jboss.seam.afterPhase
— appelé après la fin d'une phase JSF
org.jboss.seam.postInitialization
— appelé quand Seam est initialisé et démarre tous les composants
org.jboss.seam.postReInitialization
— appelé quand Seam a été ré-initialisé et démarre tous les composants après un redéploiement
org.jboss.seam.exceptionHandled.<type>
— appelé quand une exception non traitée est gérée par Seam
org.jboss.seam.exceptionHandled
— appelé quand une exception non traitée est gérée par Seam
org.jboss.seam.exceptionNotHandled
— appelé quand il n'y a pas de gestionnaire pour une exception non traitée
org.jboss.seam.afterTransactionSuccess
— appelée quand une transaction est un succès dans Seam Application Framework
org.jboss.seam.afterTransactionSuccess.<name>
— appelé quand une transaction est un succès dans Seam Application Framework gérant une entité appelée <name>
org.jboss.seam.security.loggedOut
— appelé quand un utilisateur se déconnecte
org.jboss.seam.security.loginFailed
— appelé quand l'authentification d'un utilisateur échoue
org.jboss.seam.security.loginSuccessful
— appelé quand un utilisateur réussit à s'authentifier
org.jboss.seam.security.notAuthorized
— appelé quand une vérification de l'authentification échoue
org.jboss.seam.security.notLoggedIn
— appelé quand il n'y a pas d'utilisateur authentifié et que l'authentification est requise
org.jboss.seam.security.postAuthenticate.
— appelé quand un utilisateur est authentifié
org.jboss.seam.security.preAuthenticate
— appelé avant d'essayer d'authentifier un utilisateur
Les composants de Seam peuvent observer chacun de ces évènements de la même manière qu'ils peuvent observer tous les évènements de conducteurs de composants.
EJB 3.0 introduit un modèle d'intercepteur standard pour les composants bean de session. Pour ajouter un intercepteur à un bean, vous avez besoin d'écrire une classe avec une méthode annotée @AroundInvoke
et annoter le bean avec une annotation @Interceptors
qui spécifie le nom de classe d'interception. Par exemple, l'intercepteur suivant vérifie que l'utilisateur est connecté avant d'invoquer une méthode de l'écouteur d'action:
public class LoggedInInterceptor {
@AroundInvoke
public Object checkLoggedIn(InvocationContext invocation) throws Exception {
boolean isLoggedIn = Contexts.getSessionContext().get("loggedIn")!=null;
if (isLoggedIn) {
//the user is already logged in
return invocation.proceed();
}
else {
//the user is not logged in, fwd to login page
return "login";
}
}
}
Pour appliquer un intercepteur à un bean de session qui va agir comme un écouteur d'action, vous devez annoter le bean de session @Interceptors(LoggedInInterceptor.class)
. C'est un peu annoté salement. Seam construit par dessus l'intercepteur du serveur d'application en EJB3 en vous autorisant à utiliser @Interceptors
comme une méta-annotation pour les intercepteurs de niveau classe (ceux annoté @Target(TYPE)
). Dans notre exemple, nous voudrions créer une annotation @LoggedIn
, comme ci-dessous:
@Target(TYPE)
@Retention(RUNTIME)
@Interceptors(LoggedInInterceptor.class)
public @interface LoggedIn {}
Nous pouvons maintenant simplement annoter notre bean d'écoute d'action avec @LoggedIn
pour appliquer l'intercepteur.
@Stateless
@Name("changePasswordAction")
@LoggedIn
@Interceptors(SeamInterceptor.class)
public class ChangePasswordAction implements ChangePassword {
...
public String changePassword() { ... }
}
Si l'ordonnancement de l'intercepteur est important (habituellement c'est le cas), vous pouvez ajouter les annotations @Interceptor
à vos classes d'intercepteur pour indiquer un ordre partiel des intercepteurs.
@Interceptor(around={BijectionInterceptor.class,
ValidationInterceptor.class,
ConversationInterceptor.class},
within=RemoveInterceptor.class)
public class LoggedInInterceptor
{
...
}
Vous pouvez même avoir un intercepteur "côté client", qui s'exécute avec toute la fonctionnalité livrée d'EJB3:
@Interceptor(type=CLIENT)
public class LoggedInInterceptor
{
...
}
Les intercepteurs EJB sont avec état, avec un cycle de vie qui est le même que le composant qu'ils interceptent. Pour les intercepteurs qui n'ont pas besoin de maintenir un état, Seam vous permet d'avoir une optimisation de la performance en indiquant @Interceptor(stateless=true)
.
La plus grande part de la fonctionnalité de Seam est implémentée comme un groupe d'intercepteurs livré par Seam, incluant les intercepteurs nommés dans l'exemple précédent. Vous n'avez pas à spécifier explicitement ces intercepteurs en annotants vos composants; ils existent pour tous les composants Seam interceptables.
Vous pouvez même utiliser les intercepteurs de Seam avec les composants JavaBean components, pas seulement avec les beans EJB3 !
EJB defines interception not only for business methods (using @AroundInvoke
), but also for the lifecycle methods @PostConstruct
, @PreDestroy
, @PrePassivate
and @PostActive
. Seam supports all these lifecycle methods on both component and interceptor not only for EJB3 beans, but also for JavaBean components (except @PreDestroy
which is not meaningful for JavaBean components).EJB défini l'interception pas seuelement pour les méthodes métier (en utilsiant @AroundInvoke
)), mais aussi pour les méthodes du cycle de vie @PostConstruct
,@PreDestroy
, @PrePassivate
et @PostActive
. Seam supporte toutes les méthodes du cycle de vie sur à la fois les composants et les intercepteurs pas seulement pour les beans EJB3, mais aussi pour les composants JavaBean (exception avec @PreDestroy
qui n'est pas significatif pour les composantsJavaBean).
JSF est étonnamant limité quand on vient sur la gestion des exceptions. Comme contournement partiel de ce problème, Seam vous permet de définir comment une classe particulière d'exception doit être traitée par l'annotation de la classe d'exception, ou en déclarant la classe d'exception dans un fichier XML. Cette facilité implique d'être combinée avec l'annotation standardisée EJB 3.0 @ApplicationException
qui spécifie quand l'exception devrait entrainer une annulation de la transaction.
EJB spécifie des règles bien définies qui nous permette de contrôler quand une exception marque immédiatement la transaction courante pour l'annulation quand elle est déclenchée par une méthode métier du bean: les exceptions systèmes entrainent toujours une annulation de la transaction, les exceptions d'application n'entrainent par une annulation par défaut, mais elles le font si @ApplicationException(rollback=true)
est spécifiée. (Une exception d'application est toujours une exception vérifiée, ou chaque exception non vérifiée est annotée par @ApplicationException
. Une exception du système est toujours une exception non vérifiée sans une annotation @ApplicationException
.)
Notez qu'il y a une différence entre marquer une transaction pour annulation, et réellement faire l'annulation. Les règles d'exceptions indiquent seulement que la transaction devrait être marquée pour annulation, mais elle peut toujours être active après que l'exception soit déclenchée.
Seam applique les règles d'annulation d'exception EJB 3.0 aussi que pour les composants JavaBean de Seam.
Mais ces règles s'appliquent seulement dans la couche composant de Seam. Que se passe t'il avec une exception qui n'est pas attrapé et qui se propage à l'extérieur de la couche composant de Seam, et à l'extérieur de la couche JSF ? Et bien, il est toujours mauvais de lever une transation brainbalante ouverte, donc Seam rejoue en marche arrière chaque transaction active quand une exception apparait et ce n'est pas gérer dans la couche composant de Seam.
Pour activer la gestion d'exception de Seam, nous devons être sûr que nous avons un filtre de servlet maitre déclaré dans web.xml
:
<filter>
<filter-name
>Seam Filter</filter-name>
<filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name
>Seam Filter</filter-name>
<url-pattern
>*.seam</url-pattern>
</filter-mapping
>
Vous devriez aussi avoir besoin de désactiver le mode de développement Facelets dans web.xml
et le mode de déboguage de Seam dans components.xml
si vous voulez que vos gestionnaire d'exception se déclenchent.
L'exception suivant résulte d'une erreur HTTP 404 qui à tout moment peut se propager à l'exterieur de la couche composant de Seam. Elle n'annule pas la transaction courante immédiatement à son déclenchement, mais la transaction va être annulé si l'exception n'est pas prise en compte par un autre composant de Seam.
@HttpError(errorCode=404)
public class ApplicationException extends Exception { ... }
Cette exception résulte dans une redirection du navigateur au moment où elle est propagée à l'extérieur de la couche composant de Seam. Elle finit aussi la conversation courante. Elle déclenche une annulation immédiate de la transaction courante.
@Redirect(viewId="/failure.xhtml", end=true)
@ApplicationException(rollback=true)
public class UnrecoverableApplicationException extends RuntimeException { ... }
Vous pouvez aussi utiliser EL pour indiquer le viewId
à être rediriger vers.
Cette exception déclenche une redirection, avec un message pour l'utilisateur, quand il se propage hors de la couche composant de Seam. Cela aussi invalide immédiatement la transaction courante.
@Redirect(viewId="/error.xhtml", message="Unexpected error")
public class SystemException extends RuntimeException { ... }
Comme nous ne pouvons pas ajouter les annotations pour toutes les classes d'exceptions auquelles nous nous intéressons, Seam nous permet aussi d'indiquer cette fonctionnalité dans pages.xml
.
<pages>
<exception class="javax.persistence.EntityNotFoundException">
<http-error error-code="404"/>
</exception>
<exception class="javax.persistence.PersistenceException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message
>Database access failed</message>
</redirect>
</exception>
<exception>
<end-conversation/>
<redirect view-id="/error.xhtml">
<message
>Unexpected failure</message>
</redirect>
</exception>
</pages
>
La dernière déclaration <exception>
ne spécifie pas une classe, mais c'est un fourre-tout pour chaque exception pour laquelle une traitement n'est pas autrement spécifié via les annotation ou pages.xml
.
Vous pouvez aussi utiliser EL pour indiquer le view-id
à être rediriger vers.
Vous pouvez aussi accéder à l'instance de l'exception gérée au travers d'EL, Seam la place dans le contexte de conversation, par exemple pour accéder au message de l'exception:
...
throw new AuthorizationException("You are not allowed to do this!");
<pages>
<exception class="org.jboss.seam.security.AuthorizationException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message severity="WARN"
>#{org.jboss.seam.handledException.message}</message>
</redirect>
</exception>
</pages
>
org.jboss.seam.handledException
conserve l'expcetion lié qui a été actuellement géré par un gestionnaire d'exception. L'exception la plus externe (emballé) est aussi disponible, comme une org.jboss.seam.caughtException
.
Pour le hestionnaire d'exception défini dans pages.xml
, il ets possible de déclarer que le niveau de journalisation pour lequel l'exception sera enregistrée ou même pour supprimer complètement l'enregistrement de l'exception. Les attributs log
et log-level
peuvent être utilisé pour controler le niveau de journalisation des exceptions. En définissant log="false"
comme dans l'exemple suivant, alors aucun message de journalisation ne sera généré quand l'exception spécifié se déclenchera:
<exception class="org.jboss.seam.security.NotLoggedInException" log="false">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
Si l'attribut log
n'est pas spécifié, alors par défaut c'est à true
(i.e. l'exception sera enregistrée). Autre chose, vous pouvez indiquer le log-level
pour controler quel niveau à parti duquel la journalisation des exceptions seront enregistrée:
<exception class="org.jboss.seam.security.NotLoggedInException" log-level="info">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
Les valeurs acceptable de log-level
sont: fatal, error, warn, info, debug
ou trace
. Si le log-level
n'est pas spécifiée, ou si une valeur invalide est configurée, alors cela sera par défaut à error
.
Si vous utilisez JPA:
<exception class="javax.persistence.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message
>Not found</message>
</redirect>
</exception>
<exception class="javax.persistence.OptimisticLockException">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message
>Another user changed the same data, please try again</message>
</redirect>
</exception
>
Si vous utilisez le Seam Application Framework:
<exception class="org.jboss.seam.framework.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message
>Not found</message>
</redirect>
</exception
>
Si vous utilisez Seam Security:
<exception class="org.jboss.seam.security.AuthorizationException">
<redirect>
<message
>You don't have permission to do this</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.NotLoggedInException">
<redirect view-id="/login.xhtml">
<message
>Please log in first</message>
</redirect>
</exception
>
et, pour JSF:
<exception class="javax.faces.application.ViewExpiredException">
<redirect view-id="/error.xhtml">
<message
>Your session has timed out, please try again</message>
</redirect>
</exception
>
UneViewExpiredException
se déclenche, si l'utilisateur retourne vers une page une fois que sa session a expirée. Les réglages conversation-required
et no-conversation-view-id
dans le descripteur de la page de Seam, vue dans Section 7.4, « Demander une conversation à exécution longue », vous donne un control finement dosé sur l'expiration de la session si vous accéder à une page utilisé dans une conversation.