Mutable et @ReadOnly<s:link> et de <s:button>Seam est un serveur d'application pour Java EE5. Il a été inspiré par les principes suivants:
Seam défini un modèle de composant uniforme pour toute la logique métier de votre application. Un composant Seam peut être avec état, avec état associé avec au choix un des contextes bien définie, ce qui inclus le contexte de processus métier à exécution longue et le contexte conversationnel qui est préservé au travers de plusieurs requêtes web dans les intéractions utilisateur.
Il n'y a pas de distinction entre les composants tierces de présentations et les composants de logique métier dans Seam. Vous pouvez mettre en plusieurs couches votre application en accord avec l'architecture de votre choix, au lieu d'être forcé de tordre votre logique d'application dans un schéma avec des couches anormales en étant forcé par une combinaisons de tuyaux des serveurs d'applications que vous utilisez aujourd'hui.
A la différence des composants pur Java EE ou J2EE, les composants Seam peuvent accèder simultanément aux états associés avec la requête web et avec l'état conservé dans les ressources transactionnelles (sans avoir besoin de propager l'état de la requête web avec des paramètres de la méthode). Vous pouvez objecter que la conception en couche de l'application qui vous est imposée par la vieille plateforme J2EE est une Bonne Chose. Et bien, rien ne vous empêche de ne pas créer une architecture en couche équivalent en utilisant Seam — la différence est que vous pouvez convevoir votre application et décider quel mis en couche vous avez et comment elles vont fonctionner ensemble.
JSF et EJB 3.0 sont deux des meilleures fonctionnalités récentes de Java EE5. EJB3 est un modèle de composant tout nouveau pour l’applicatif métier côté serveur et la logique de persistance. Principalement, JSF est un bon modèle par composants pour la partie présentation. Malheureusement, le modèle de composant n’est pas capable de résoudre tous les problèmes dans la programmation elle-même. En fait, JSF et EJB3 fonctionnent bien ensemble. Mais les spécifications Java EE 5 ne fournissent pas un moyen standard pour intégrer les 2 modèles de composants. Heureusement, les créateurs de ces 2 modèles ont prévu cette situation et fournissent des points d’extensions standard pour permettre l’extension et l’intégration d’autres serveurs d'applications.
Seam unifie les modèles de composants de JSF et d’EJB3, élimant le code glue, et laissant le développeur se concentrer sur les problèmes du métier.
Il est possible d'écrire des applications Seam avec "tout" est un EJB. Cela peut être une grosse surprise si vous avez l'habitude de pensez aux EJBs comme des gros grains, des objets "super-lourd". Cependant, la version 3.0 a complètement changé la nature de l'EJB depuis le point de vu du développeur. Un EJB est un objet bien dimensionné — rien de bien complexe qu'un JavaBean annoté. Seam vous encourage même à utiliser les beans de sessions comme des écouteurs d'actions JSF!
D'un autre côté, si vous préférez ne pas adopter EJB 3.0 pour l'instant, vous n'avez pas à le faire. Virtuellement toute classe Java peut être un composant de Seam et Seam fourni toute les fonctionnalité que vous attendez pour un container "poid-mouche" et bien plus, pour chaque composant, EJB ou autre.
Seam utilise deux excelents solutions open source AJAX basé sur JSF: JBoss RichFace et ICEfaces. Ces solutions permettent d’ajouter les capacités d’AJAX à vos interfaces utilisateur sans avoir besoin d’écrire du code Javascript.
Autrement, Seam fourni aussi un couche distante développée en Javascript vous permettant d'appeler les composants de manière assynchrone depuis le côté client JavaScript sans avoir besoin d'une couche d'action intermédiaire. Vous pouvez même sousrire à des sujets JMS côté serveur et recevoir les messages via un push AJAX.
Aucune de ces approches ne fonctionnerai bien si il n'y a pas la concurence livrée par Seam et un gestionnaire d'état qui assure que toute les requêtes concurentes assynchrones et à fines granularités ne sont pas gérées de amnière sure et éfficace du côté serveur.
Optionnellement, Seam intègre la gestion des processus métier de façon transparent via jBPM. Vous n’allez pas y croire qu' il est vraiment facile d’implémenter des enchaînements de taches complexes en utilisant jBPM et Seam.
Seam permet même de définir des enchainements de pages tierces de présentation en utilisant le même langage (jPDL) que jBPM utilise pour la définition de processus métier.
JSF fourni un modèle incroyablement riche d’évènement pour la couche présentation tierce. Seam améliore ce modèle en exposant les processus métier jBPM liant les évènement par un mécanisme de gestion de ces même évènements, fournissant un modèle d'évènement uniforme pour le modèle uniforme de composant de Seam.
Nous somme habitué au concept de gestionnaire de transaction déclaratif et à la sécurité déclarative depuis les tout premier jours des EJB. EJB 3.0 introduit même un gestionnaire de persistance contextuelle déclarative. Ceci sont les trois exemples du grand problème du gestionnaire d’état qui est associé avec un contexte particulie, tout tant s’assurant que tout les besoins en nettoyage interviennent quand le contexte s’arrête. Seam rends le concept de gestionnaire d’état déclaratif bien meilleur et l’applique à l’état applicatif. Traditionnellement, les applications J2EE doivent presque toujours implémenter manuellement un gestionnaire d’état, en affectant et en récupérant les attributs de requête et de session de servlet. Cette approche du gestionnaire d’état est la source de nombreux bugs et de fuite de mémoire quand les applications n’arrivent pas à nettoyer les attributs de la session, ou quand les données de session associées avec différentes enchaînements de tâches cohabitent dans une application multi fenêtrée. Seam dispose du potentiel pour éliminer presque complètement ce genre de bugs.
Une application gestionnaire d’état déclaratif est devenue possible par la richesse du modèle contextuel définis par Seam. Seam étends le modèle de contexte définie par les spécifications servlet c — request, session, application c — avec deux nouveaux contextes : conversation et processus métier c — ces derniers prennent tous leur sens du points de vue de la logique métier.
Vous allez être impréssionné comment les choses deviennent plus simple une fois que vous commencerez à utiliser les conversations. Avez vous déjà connu la douleur dans la gestion de correspondance d'association chargées à la demande avec une solution ORM comme Hibernate ou JPA? Les contextes de persistance d'étendue conversationnelle de Seam font que vous allez rarement voir une LazyInitializationException. Avez-vous déjà eu des problèmes avec le bouton rafraichir ? Le bouton précédent? Avec une soumission de formulaire dupliquée ? Avec des messages propagées au travers d'un post-ensuite-redirection? Le gestionnaire de conversation de Seam résoud tous ces problèmes sans que vous n'aillez besoin de réellement y penser. Ils sont tous des symptomes d'une architecture de gestions d'état morcelée qui a prévalue depuis les premiers temps du web.
La notion d'Inversion de contrôle ou d’injection dépendante existe à la fois dans JSF et dans EJB3, tout autant que "les containeurs légers" aux multiples dénominations. La plus part de ces containers parlent d’injection de composants en implémentant des services sans état. Même quand l’injection de composants avec état est disponible (comme dans JSF), il est virtuellement inutile pour la gestion de l’état de l’application car la durée de vie du composants avec état ne peut être définie avec suffisamment de flexibilité et parce que l'appartenance des composants à une étendue plus grande ne peuvent pas être injecté dans des composants appartenant à des étendues plus réduites.
La Bijectiondiffère de l’inversion de contrôle (IoC) dans le fait qu’il est dynamique, contextuel et bidirectionnel. Vous pouvez penser à ce mécanisme comme des variables de contextes avec alias (les noms dans les différents contextes correspondant au thread courrant) pour des attributs de composants. La bijection autorise un assemblage automatisé de composants avec état par le containeur. Il autorise le composant de manière sécurisante et facile de manipuler les valeurs des variables de contexte, juste par assignation d’un attribut d’un composant.
Les applications de Seam permettent à l'utilisateur de librement basculer entre les multiples onglets du navigateur, chacun associé avec une conversation différentes, isolé de manière sécurisée. Les applications peuvent même profiter du gestionnaire d'espace de travail autorisant l'utilisateur à basculer entre des conversations (les espaces de travail) dans un seul onglet du navigateur. Seam ne fournit pas seulement des opération correcte multi-fenétrée mais aussi des opération simulant le multi-fenetré dans une seule fenètre!
Traditionnellement, la communauté Java a été dans un état de grande confusion à propos de ce qui précisément compte comme méta-information dans une configuration. J2EE et les containeurs "légers" ont fourni des descripteurs de déploiement basé sur XML à la fois pour les choses qui sont clairement configurable entre les différents déploiement du système et pour tout autre chose comme déclaration qui ne peut pas être facilement exprimé en Java. Les annotations de Java 5 ont changé tout cela.
EJB 3.0 utilise les annotations et la "configuration par exception" est la façon la plus facile de fournir de l'informations au containeur dans une forme déclarative. Malheureusement, JSF est encore lourdement dépendant de fichiers de configuration XML verbeux. Seam étend les annotations fournies par EJB3.0 avec un groupe de d'annotations pour le gestionnaire d'état déclaratif et une démarcation du contexte déclarative. Ceci vous permet d'éliminer les complexes déclaration de gestion des beans de JSF et de reduire le XML nécéssaire à ce qui est du ressort réellement du XML (les règles de navigation de JSF).
Les composants Seam, en étant de pures classes Java, sont par nature testable unitairement. Mais pour des applications complexes, le fait de tester unitairement n’est pas suffisant. Tester unitairement est traditionnellement une tâche fatigante et complexe pour les applications web Java. Cependant, Seam fourni pour le test des applications Seam un fonctionnalité au coeur du serveur d'applidcation Vous pouvez facilement écrire des tests JUnit ou TestNG pour reproduire une parfaite interaction avec un utilisateur, sollicitant tous les composants du système indépendamment de vues (la page JSP ou Facelets). Vous pouvez exécuter ces tests directement depuis votre IDE, là où Seam va automatiquement déployer les composants EJB en utilisant Jboss Embeddable.
Vous pensez que la dernière incarnation de Java EE est géniale. Mais nous savons qu'elle n'est pas parfaite. Là où il y a des trous dans la spécification (par exemple les limitations dans le cycle de vie de JSF pour les requêtes GET), Seam les règles lui-même. Et les auteurs de Seam travaille avec le groupe d'experts JCP pour faire en sorte que ces corrections seront conservé dans les prochaines révisions des standards.
Aujourd'huis les serveurs d'applications pensent trop petit. Ils vous permettent d'obtenir les entrées d'un formulaire utilisateur dans vos objets Java. Et ensuite ils vous laissent vous débrouiller. Un serveur d'application web vraiment complet devrait régler les problèmes comme la persistance, la concurence, l'assynchronisme, la gestion d'état, la sécurité, l'email, les messages, le PDF, la génération de diagrame, l'enchainement de tâche, le rendu du formatage wiki, les services web, la mise en cache et bien plus encore. Une fois que vous aurez plogeés sous la surface de Seam, vous allez être impréssionné par comment les problèmes deviennent plus simple....
Seam intègre JPA et Hibernate3 pour la persistance, le EJB Timer Service et Quartz pour l'assynchronisme version allégée, jBPM pour l'enchainement de tâches, JBoss Rules pour les règles métiers, Meldware Mail pour les emails, Hibernate Search et Lucene pour la recherche plein texte, JMS pour les messages et JBoss Cache pour la mise en cache de fragments. Les couches de Seam son un serveur d'application de sécurité innovant basés sur des règles au desus de JAAS et de JBoss Rules. Il ya aussi des bibliothèques de tag JSF pour le rendu de PDF, l'envoie des emails, les diagrammes et le texte wiki. Les composants de Seam peuvent même être appelés de mmanière synchrones comme des services web, asynchronisme depuis le JavaScript côté client ou avec Google Web Toolkit ou bien sur directement depuis JSF.
Seam fonctionne dans n’importe quel serveur d’application Java EE et fonctionne même dans Tomcat. Si votre environement supporte EJB3.0, génial!Si cen'est pas le cas, pas de problème, vous pouvez utiliser le gestionnaire de transaction livré dans Seam avec JPA ou Hibernate3 pour la persistance. Ou, vous pouvez deployer JBoss Embedded dans Tomcat, et avoir le support plein et entier d'EJB 3.0.

Il est clair que la combinaison de Seam, JSF et EJB3 est la façon la plus simple d'écrire une application web complexe en Java. Vous ne réalisez pas le peut de code qu'il nécéssite!
Visitez SeamFramework.org pour découvrir comment contribuer à Seam!
Seam provides a number of example applications demonstrating how to use the various features of Seam. This tutorial will guide you through a few of those examples to help you get started learning Seam. The Seam examples are located in the examples subdirectory of the Seam distribution. The registration example, which will be the first example we look at, is in the examples/registration directory.
Each example has the same directory structure:
The view directory contains view-related files such as web page templates, images and stylesheets.
The resources directory contains deployment descriptors and other configuration files.
The src directory contains the application source code.
The example applications run both on JBoss AS and Tomcat with no additional configuration. The following sections will explain the procedure in both cases. Note that all the examples are built and run from the Ant build.xml, so you'll need a recent version of Ant installed before you get started.
The examples are configured for use on JBoss AS 4.2 or 5.0. You'll need to set jboss.home, in the shared build.properties file in the root folder of your Seam installation, to the location of your JBoss AS installation.
Once you've set the location of JBoss AS and started the application server, you can build and deploy any example by typing ant explode in the the directory for that example. Any example that is packaged as an EAR deploys to a URL like /seam-, where exampleexample is the name of the example folder, with one exception. If the example folder begins with seam, the prefix "seam" is ommitted. For instance, if JBoss AS is running on port 8080, the URL for the registration example is http://localhost:8080/seam-registration/, whereas the URL for the seamspace example is http://localhost:8080/seam-space/.
If, on the other hand, the example gets packaged as a WAR, then it deploys to a URL like /jboss-seam-. Most of the examples can be deployed as a WAR to Tomcat with Embedded JBoss by typing exampleant tomcat.deploy. Several of the examples can only be deployed as a WAR. Those examples are groovybooking, hibernate, jpa, and spring.
The examples are also configured for use on Tomcat 6.0. You will need to follow the instructions in Section 30.6.1, « Installing Embedded JBoss » for installing JBoss Embedded on Tomcat 6.0. JBoss Embedded is only required to run the Seam demos that use EJB3 components on Tomcat. There are also examples of non-EJB3 applications that can be run on Tomcat without the use of JBoss Embedded.
You'll need to set tomcat.home, in the shared build.properties file in the root folder of your Seam installation, to the location of your Tomcat installation. make sure you set the location of your Tomcat.
You'll need to use a different Ant target when using Tomcat. Use ant tomcat.deploy in example subdirectory to build and deploy any example for Tomcat.
On Tomcat, the examples deploy to URLs like /jboss-seam-, so for the registration example the URL would be examplehttp://localhost:8080/jboss-seam-registration/. The same is true for examples that deploy as a WAR, as mentioned in the previous section.
Most of the examples come with a suite of TestNG integration tests. The easiest way to run the tests is to run ant test. It is also possible to run the tests inside your IDE using the TestNG plugin. Consult the readme.txt in the examples directory of the Seam distribution for more information.
The registration example is a simple application that lets a new user store his username, real name and password in the database. The example isn't intended to show off all of the cool functionality of Seam. However, it demonstrates the use of an EJB3 session bean as a JSF action listener, and basic configuration of Seam.
We'll go slowly, since we realize you might not yet be familiar with EJB 3.0.
The start page displays a very basic form with three input fields. Try filling them in and then submitting the form. This will save a user object in the database.

The example is implemented with two Facelets templates, one entity bean and one stateless session bean. Let's take a look at the code, starting from the "bottom".
We need an EJB entity bean for user data. This class defines persistence and validation declaratively, via annotations. It also needs some extra annotations that define the class as a Seam component.
Exemple 1.1. User.java
@Entity
@Name("user")
@Scope(SESSION)
@Table(name="users")
public class User implements Serializable
{
private static final long serialVersionUID = 1881413500711441951L;
private String username;
private String password;
private String name;
public User(String name, String password, String username)
{
this.name = name;
this.password = password;
this.username = username;
}
public User() {}
@NotNull @Length(min=5, max=15)
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
@NotNull
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Id @NotNull @Length(min=5, max=15)
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
![]() | The EJB3 standard |
![]() | A Seam component needs a component name specified by the |
![]() | Whenever Seam instantiates a component, it binds the new instance to a context variable in the component's default context. The default context is specified using the |
![]() | The EJB standard |
![]() |
|
![]() | An empty constructor is both required by both the EJB specification and by Seam. |
![]() | The |
![]() | The EJB standard |
The most important things to notice in this example are the @Name and @Scope annotations. These annotations establish that this class is a Seam component.
We'll see below that the properties of our User class are bound directly to JSF components and are populated by JSF during the update model values phase. We don't need any tedious glue code to copy data back and forth between the JSP pages and the entity bean domain model.
However, entity beans shouldn't do transaction management or database access. So we can't use this component as a JSF action listener. For that we need a session bean.
Most Seam application use session beans as JSF action listeners (you can use JavaBeans instead if you like).
We have exactly one JSF action in our application, and one session bean method attached to it. In this case, we'll use a stateless session bean, since all the state associated with our action is held by the User bean.
This is the only really interesting code in the example!
Exemple 1.2. RegisterAction.java
@Stateless@Name("register") public class RegisterAction implements Register { @In private Use
r user; @PersistenceContext private Ent
ityManager em; @Logger private Log
log; public String register() {
List existing = em.createQuery( "select username from User where username = #{user.username}") .getR
esultList(); if (existing.size()==0) { em.persist(user); log.info("Registered new user #{user.username}"); retur
n "/registered.xhtml"; }
else { FacesMessages.instance().add("User #{user.username} already exists"); retur
n null; } } }
![]() | The EJB |
![]() | The |
![]() | The EJB standard |
![]() | The Seam |
![]() | The action listener method uses the standard EJB3 |
![]() | Notice that Seam lets you use a JSF EL expression inside EJB-QL. Under the covers, this results in an ordinary JPA |
![]() | The |
![]() | JSF action listener methods return a string-valued outcome that determines what page will be displayed next. A null outcome (or a void action listener method) redisplays the previous page. In plain JSF, it is normal to always use a JSF navigation rule to determine the JSF view id from the outcome. For complex application this indirection is useful and a good practice. However, for very simple examples like this one, Seam lets you use the JSF view id as the outcome, eliminating the requirement for a navigation rule. Note that when you use a view id as an outcome, Seam always performs a browser redirect. |
![]() | Seam provides a number of built-in components to help solve common problems. The |
Note that we did not explicitly specify a @Scope this time. Each Seam component type has a default scope if not explicitly specified. For stateless session beans, the default scope is the stateless context, which is the only sensible value.
Our session bean action listener performs the business and persistence logic for our mini-application. In more complex applications, we might need require a separate service layer. This is easy to achieve with Seam, but it's overkill for most web applications. Seam does not force you into any particular strategy for application layering, allowing your application to be as simple, or as complex, as you want.
Note that in this simple application, we've actually made it far more complex than it needs to be. If we had used the Seam application framework controllers, we would have eliminated all of our application code. However, then we wouldn't have had much of an application to explain.
Naturally, our session bean needs a local interface.
That's the end of the Java code. Now we'll look at the view.
The view pages for a Seam application could be implemented using any technology that supports JSF. In this example we use Facelets, because we think it's better than JSP.
Exemple 1.4. register.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Username: <h:inputText value="#{user.username}" required="true"/>
Real Name: <h:inputText value="#{user.name}" required="true"/>
Password: <h:inputSecret value="#{user.password}" required="true"/>
</h:panelGrid>
</s:validateAll>
<h:messages/>
<h:commandButton value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html>
The only thing here that is specific to Seam is the <s:validateAll> tag. This JSF component tells JSF to validate all the contained input fields against the Hibernate Validator annotations specified on the entity bean.
Exemple 1.5. registered.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Successfully Registered New User</title>
</head>
<body>
<f:view>
Welcome, #{user.name}, you are successfully registered as #{user.username}.
</f:view>
</body>
</html>
This is a simple Facelets page using some inline EL. There's nothing specific to Seam here.
Since this is the first Seam app we've seen, we'll take a look at the deployment descriptors. Before we get into them, it is worth noting that Seam strongly values minimal configuration. These configuration files will be created for you when you create a Seam application. You'll never need to touch most of these files. We're presenting them now only to help you understand what all the pieces in the example are doing.
If you've used many Java frameworks before, you'll be used to having to declare all your component classes in some kind of XML file that gradually grows more and more unmanageable as your project matures. You'll be relieved to know that Seam does not require that application components be accompanied by XML. Most Seam applications require a very small amount of XML that does not grow very much as the project gets bigger.
Nevertheless, it is often useful to be able to provide for some external configuration of some components (particularly the components built in to Seam). You have a couple of options here, but the most flexible option is to provide this configuration in a file called components.xml, located in the WEB-INF directory. We'll use the components.xml file to tell Seam how to find our EJB components in JNDI:
Exemple 1.6. components.xml
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/core
http://jboss.com/products/seam/core-2.2.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.2.xsd">
<core:init jndi-pattern="@jndiPattern@"/>
</components>
This code configures a property named jndiPattern of a built-in Seam component named org.jboss.seam.core.init. The funny @ symbols are there because our Ant build script puts the correct JNDI pattern in when we deploy the application, which it reads from the components.properties file. You learn more about how this process works in Section 5.2, « Configuring components via components.xml ».
The presentation layer for our mini-application will be deployed in a WAR. So we'll need a web deployment descriptor.
Exemple 1.7. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>10</session-timeout>
</session-config>
</web-app>
This web.xml file configures Seam and JSF. The configuration you see here is pretty much identical in all Seam applications.
Most Seam applications use JSF views as the presentation layer. So usually we'll need faces-config.xml. In our case, we are going to use Facelets for defining our views, so we need to tell JSF to use Facelets as its templating engine.
Exemple 1.8. faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>
Note that we don't need any JSF managed bean declarations! Our managed beans are annotated Seam components. In Seam applications, the faces-config.xml is used much less often than in plain JSF. Here, we are simply using it to enable Facelets as the view handler instead of JSP.
In fact, once you have all the basic descriptors set up, the only XML you need to write as you add new functionality to a Seam application is orchestration: navigation rules or jBPM process definitions. Seam's stand is that process flow and configuration data are the only things that truly belong in XML.
In this simple example, we don't even need a navigation rule, since we decided to embed the view id in our action code.
The ejb-jar.xml file integrates Seam with EJB3, by attaching the SeamInterceptor to all session beans in the archive.
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
The persistence.xml file tells the EJB persistence provider where to find the datasource, and contains some vendor-specific settings. In this case, enables automatic schema export at startup time.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="userDatabase">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
Finally, since our application is deployed as an EAR, we need a deployment descriptor there, too.
Exemple 1.9. registration application
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/application_5.xsd"
version="5">
<display-name>Seam Registration</display-name>
<module>
<web>
<web-uri>jboss-seam-registration.war</web-uri>
<context-root>/seam-registration</context-root>
</web>
</module>
<module>
<ejb>jboss-seam-registration.jar</ejb>
</module>
<module>
<ejb>jboss-seam.jar</ejb>
</module>
<module>
<java>jboss-el.jar</java>
</module>
</application>
This deployment descriptor links modules in the enterprise archive and binds the web application to the context root /seam-registration.
We've now seen all the files in the entire application!
When the form is submitted, JSF asks Seam to resolve the variable named user. Since there is no value already bound to that name (in any Seam context), Seam instantiates the user component, and returns the resulting User entity bean instance to JSF after storing it in the Seam session context.
The form input values are now validated against the Hibernate Validator constraints specified on the User entity. If the constraints are violated, JSF redisplays the page. Otherwise, JSF binds the form input values to properties of the User entity bean.
Next, JSF asks Seam to resolve the variable named register. Seam uses the JNDI pattern mentioned earlier to locate the stateless session bean, wraps it as a Seam component, and returns it. Seam then presents this component to JSF and JSF invokes the register() action listener method.
But Seam is not done yet. Seam intercepts the method call and injects the User entity from the Seam session context, before allowing the invocation to continue.
The register() method checks if a user with the entered username already exists. If so, an error message is queued with the FacesMessages component, and a null outcome is returned, causing a page redisplay. The FacesMessages component interpolates the JSF expression embedded in the message string and adds a JSF FacesMessage to the view.
If no user with that username exists, the "/registered.xhtml" outcome triggers a browser redirect to the registered.xhtml page. When JSF comes to render the page, it asks Seam to resolve the variable named user and uses property values of the returned User entity from Seam's session scope.
Clickable lists of database search results are such an important part of any online application that Seam provides special functionality on top of JSF to make it easier to query data using EJB-QL or HQL and display it as a clickable list using a JSF <h:dataTable>. The messages example demonstrates this functionality.

The message list example has one entity bean, Message, one session bean, MessageListBean and one JSP.
The Message entity defines the title, text, date and time of a message, and a flag indicating whether the message has been read:
Exemple 1.10. Message.java
@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
private Long id;
private String title;
private String text;
private boolean read;
private Date datetime;
@Id @GeneratedValue
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
@NotNull @Length(max=100)
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
@NotNull @Lob
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
@NotNull
public boolean isRead()
{
return read;
}
public void setRead(boolean read)
{
this.read = read;
}
@NotNull
@Basic @Temporal(TemporalType.TIMESTAMP)
public Date getDatetime()
{
return datetime;
}
public void setDatetime(Date datetime)
{
this.datetime = datetime;
}
}
Just like in the previous example, we have a session bean, MessageManagerBean, which defines the action listener methods for the two buttons on our form. One of the buttons selects a message from the list, and displays that message. The other button deletes a message. So far, this is not so different to the previous example.
But MessageManagerBean is also responsible for fetching the list of messages the first time we navigate to the message list page. There are various ways the user could navigate to the page, and not all of them are preceded by a JSF action — the user might have bookmarked the page, for example. So the job of fetching the message list takes place in a Seam factory method, instead of in an action listener method.
We want to cache the list of messages in memory between server requests, so we will make this a stateful session bean.
Exemple 1.11. MessageManagerBean.java
@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean implements Serializable, MessageManager
{
@DataModel
private Lis
t<Message> messageList;
@DataModelSelection
@Out(requir
ed=false)
private Mes
sage message;
@PersistenceContext(type=EXTENDED)
private Ent
ityManager em;
@Factory("messageList")
public void
findMessages()
{
messageList = em.createQuery("select msg from Message msg order by msg.datetime desc")
.getResultList();
}
public void select()
{
message.setRead(true);
}
public void delete()
{
messageList.remove(message);
em.remove(message);
message=null;
}
@Remove
public void
destroy() {}
}![]() | The |
![]() | The |
![]() | The |
![]() | This stateful bean has an EJB3 extended persistence context. The messages retrieved in the query remain in the managed state as long as the bean exists, so any subsequent method calls to the stateful bean can update them without needing to make any explicit call to the |
![]() | The first time we navigate to the JSP page, there will be no value in the |
![]() | The |
![]() | The |
![]() | All stateful session bean Seam components must have a method with no parameters marked |
Note that this is a session-scoped Seam component. It is associated with the user login session, and all requests from a login session share the same instance of the component. (In Seam applications, we usually use session-scoped components sparingly.)
All session beans have a business interface, of course.
Exemple 1.12. MessageManager.java
@Local
public interface MessageManager
{
public void findMessages();
public void select();
public void delete();
public void destroy();
}
From now on, we won't show local interfaces in our code examples.
Let's skip over components.xml, persistence.xml, web.xml, ejb-jar.xml, faces-config.xml and application.xml since they are much the same as the previous example, and go straight to the JSP.
The JSP page is a straightforward use of the JSF <h:dataTable> component. Again, nothing specific to Seam.
Exemple 1.13. messages.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Messages</title>
</head>
<body>
<f:view>
<h:form>
<h2>Message List</h2>
<h:outputText value="No messages to display"
rendered="#{messageList.rowCount==0}"/>
<h:dataTable var="msg" value="#{messageList}"
rendered="#{messageList.rowCount>0}">
<h:column>
<f:facet name="header">
<h:outputText value="Read"/>
</f:facet>
<h:selectBooleanCheckbox value="#{msg.read}" disabled="true"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:commandLink value="#{msg.title}" action="#{messageManager.select}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Date/Time"/>
</f:facet>
<h:outputText value="#{msg.datetime}">
<f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/>
</h:outputText>
</h:column>
<h:column>
<h:commandButton value="Delete" action="#{messageManager.delete}"/>
</h:column>
</h:dataTable>
<h3><h:outputText value="#{message.title}"/></h3>
<div><h:outputText value="#{message.text}"/></div>
</h:form>
</f:view>
</body>
</html>
The first time we navigate to the messages.jsp page, the page will try to resolve the messageList context variable. Since this context variable is not initialized, Seam will call the factory method findMessages(), which performs a query against the database and results in a DataModel being outjected. This DataModel provides the row data needed for rendering the <h:dataTable>.
When the user clicks the <h:commandLink>, JSF calls the select() action listener. Seam intercepts this call and injects the selected row data into the message attribute of the messageManager component. The action listener fires, marking the selected Message as read. At the end of the call, Seam outjects the selected Message to the context variable named message. Next, the EJB container commits the transaction, and the change to the Message is flushed to the database. Finally, the page is re-rendered, redisplaying the message list, and displaying the selected message below it.
If the user clicks the <h:commandButton>, JSF calls the delete() action listener. Seam intercepts this call and injects the selected row data into the message attribute of the messageList component. The action listener fires, removing the selected Message from the list, and also calling remove() on the EntityManager. At the end of the call, Seam refreshes the messageList context variable and clears the context variable named message. The EJB container commits the transaction, and deletes the Message from the database. Finally, the page is re-rendered, redisplaying the message list.
jBPM provides sophisticated functionality for workflow and task management. To get a small taste of how jBPM integrates with Seam, we'll show you a simple "todo list" application. Since managing lists of tasks is such core functionality for jBPM, there is hardly any Java code at all in this example.

The center of this example is the jBPM process definition. There are also two JSPs and two trivial JavaBeans (There was no reason to use session beans, since they do not access the database, or have any other transactional behavior). Let's start with the process definition:
Exemple 1.14. todo.jpdl.xml
<process-definition name="todo"> <start-state name="start"> <transition to="todo"/> </start-state> <task-node
name="todo"> <task na
me="todo" description="#{todoList.description}"> <assi
gnment actor-id="#{actor.id}"/> </task> <transition to="done"/> </task-node> <end-state
name="done"/> </process-definition>
![]() | The |
![]() | The |
![]() | The |
![]() | Tasks need to be assigned to a user or group of users when they are created. In this case, the task is assigned to the current user, which we get from a built-in Seam component named |
![]() | The |
If we view this process definition using the process definition editor provided by JBossIDE, this is what it looks like:

This document defines our business process as a graph of nodes. This is the most trivial possible business process: there is one task to be performed, and when that task is complete, the business process ends.
The first JavaBean handles the login screen login.jsp. Its job is just to initialize the jBPM actor id using the actor component. In a real application, it would also need to authenticate the user.
Exemple 1.15. Login.java
@Name("login")
public class Login
{
@In
private Actor actor;
private String user;
public String getUser()
{
return user;
}
public void setUser(String user)
{
this.user = user;
}
public String login()
{
actor.setId(user);
return "/todo.jsp";
}
}
Here we see the use of @In to inject the built-in Actor component.
The JSP itself is trivial:
Exemple 1.16. login.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<f:view>
<h:form>
<div>
<h:inputText value="#{login.user}"/>
<h:commandButton value="Login" action="#{login.login}"/>
</div>
</h:form>
</f:view>
</body>
</html>
The second JavaBean is responsible for starting business process instances, and ending tasks.
Exemple 1.17. TodoList.java
@Name("todoList")
public class TodoList
{
private String description;
public Stri
ng getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
@CreateProcess(definition="todo")
public void createTodo() {}
@StartTask @EndTask
public void done() {}
}![]() | The description property accepts user input from the JSP page, and exposes it to the process definition, allowing the task description to be set. |
![]() | The Seam |
![]() | The Seam |
In a more realistic example, @StartTask and @EndTask would not appear on the same method, because there is usually work to be done using the application in order to complete the task.
Finally, the core of the application is in todo.jsp:
Exemple 1.18. todo.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<f:view>
<h:form id="list">
<div>
<h:outputText value="There are no todo items."
rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task"
rendered="#{not empty taskInstanceList}">
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:inputText value="#{task.description}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Created"/>
</f:facet>
<h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
<f:convertDateTime type="date"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Priority"/>
</f:facet>
<h:inputText value="#{task.priority}" style="width: 30"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Due Date"/>
</f:facet>
<h:inputText value="#{task.dueDate}" style="width: 100">
<f:convertDateTime type="date" dateStyle="short"/>
</h:inputText>
</h:column>
<h:column>
<s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column>
</h:dataTable>
</div>
<div>
<h:messages/>
</div>
<div>
<h:commandButton value="Update Items" action="update"/>
</div>
</h:form>
<h:form id="new">
<div>
<h:inputText value="#{todoList.description}"/>
<h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
</div>
</h:form>
</f:view>
</body>
</html>
Let's take this one piece at a time.
The page renders a list of tasks, which it gets from a built-in Seam component named taskInstanceList. The list is defined inside a JSF form.
Exemple 1.19. todo.jsp
<h:form id="list">
<div>
<h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task"
rendered="#{not empty taskInstanceList}">
...
</h:dataTable>
</div>
</h:form>
Each element of the list is an instance of the jBPM class TaskInstance. The following code simply displays the interesting properties of each task in the list. For the description, priority and due date, we use input controls, to allow the user to update these values.
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:inputText value="#{task.description}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Created"/>
</f:facet>
<h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
<f:convertDateTime type="date"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Priority"/>
</f:facet>
<h:inputText value="#{task.priority}" style="width: 30"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Due Date"/>
</f:facet>
<h:inputText value="#{task.dueDate}" style="width: 100">
<f:convertDateTime type="date" dateStyle="short"/>
</h:inputText>
</h:column>
#{task.dueDate}.This button ends the task by calling the action method annotated @StartTask @EndTask. It passes the task id to Seam as a request parameter:
<h:column>
<s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column>
Note that this is using a Seam <s:button> JSF control from the seam-ui.jar package. This button is used to update the properties of the tasks. When the form is submitted, Seam and jBPM will make any changes to the tasks persistent. There is no need for any action listener method:
<h:commandButton value="Update Items" action="update"/>
A second form on the page is used to create new items, by calling the action method annotated @CreateProcess.
<h:form id="new">
<div>
<h:inputText value="#{todoList.description}"/>
<h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
</div>
</h:form>
After logging in, todo.jsp uses the taskInstanceList component to display a table of outstanding todo items for a the current user. Initially there are none. It also presents a form to enter a new entry. When the user types the todo item and hits the "Create New Item" button, #{todoList.createTodo} is called. This starts the todo process, as defined in todo.jpdl.xml.
The process instance is created, starting in the start state and immediately transition to the todo state, where a new task is created. The task description is set based on the user's input, which was saved to #{todoList.description}. Then, the task is assigned to the current user, which was stored in the seam actor component. Note that in this example, the process has no extra process state. All the state in this example is stored in the task definition. The process and task information is stored in the database at the end of the request.
When todo.jsp is redisplayed, taskInstanceList now finds the task that was just created. The task is shown in an h:dataTable. The internal state of the task is displayed in each column: #{task.description}, #{task.priority}, #{task.dueDate}, etc... These fields can all be edited and saved back to the database.
Each todo item also has "Done" button, which calls #{todoList.done}. The todoList component knows which task the button is for because each s:button specificies taskInstance="#{task}", referring to the task for that particular line of of the table. The @StartTast and @EndTask annotations cause seam to make the task active and immediately complete the task. The original process then transitions into the done state, according to the process definition, where it ends. The state of the task and process are both updated in the database.
When todo.jsp is displayed again, the now-completed task is no longer displayed in the taskInstanceList, since that component only display active tasks for the user.
For Seam applications with relatively freeform (ad hoc) navigation, JSF/Seam navigation rules are a perfectly good way to define the page flow. For applications with a more constrained style of navigation, especially for user interfaces which are more stateful, navigation rules make it difficult to really understand the flow of the system. To understand the flow, you need to piece it together from the view pages, the actions and the navigation rules.
Seam allows you to use a jPDL process definition to define pageflow. The simple number guessing example shows how this is done.

The example is implemented using one JavaBean, three JSP pages and a jPDL pageflow definition. Let's begin with the pageflow:
Exemple 1.20. pageflow.jpdl.xml
<pageflow-definition
xmlns="http://jboss.com/products/seam/pageflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pageflow
http://jboss.com/products/seam/pageflow-2.2.xsd"
name="numberGuess">
<start-page
name="displayGuess" view-id="/numberGuess.jspx">
<redirect/>
<transit
ion name="guess" to="evaluateGuess">
<acti
on expression="#{numberGuess.guess}"/>
</transition>
<transition name="giveup" to="giveup"/>
<transition name="cheat" to="cheat"/>
</start-page>
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
<decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
<transition name="true" to="lose"/>
<transition name="false" to="displayGuess"/>
</decision>
<page name="giveup" view-id="/giveup.jspx">
<redirect/>
<transition name="yes" to="lose"/>
<transition name="no" to="displayGuess"/>
</page>
<process-state name="cheat">
<sub-process name="cheat"/>
<transition to="displayGuess"/>
</process-state>
<page name="win" view-id="/win.jspx">
<redirect/>
<end-conversation/>
</page>
<page name="lose" view-id="/lose.jspx">
<redirect/>
<end-conversation/>
</page>
</pageflow-definition>![]() | The |
![]() | The |
![]() | A transition |
![]() | A |
Here is what the pageflow looks like in the JBoss Developer Studio pageflow editor:

Now that we have seen the pageflow, it is very, very easy to understand the rest of the application!
Here is the main page of the application, numberGuess.jspx:
Exemple 1.21. numberGuess.jspx
<<?xml version="1.0"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns="http://www.w3.org/1999/xhtml"
version="2.0">
<jsp:output doctype-root-element="html"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<jsp:directive.page contentType="text/html"/>
<html>
<head>
<title>Guess a number...</title>
<link href="niceforms.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" src="niceforms.js" />
</head>
<body>
<h1>Guess a number...</h1>
<f:view>
<h:form styleClass="niceform">
<div>
<h:messages globalOnly="true"/>
<h:outputText value="Higher!"
rendered="#{numberGuess.randomNumber gt numberGuess.currentGuess}"/>
<h:outputText value="Lower!"
rendered="#{numberGuess.randomNumber lt numberGuess.currentGuess}"/>
</div>
<div>
I'm thinking of a number between
<h:outputText value="#{numberGuess.smallest}"/> and
<h:outputText value="#{numberGuess.biggest}"/>. You have
<h:outputText value="#{numberGuess.remainingGuesses}"/> guesses.
</div>
<div>
Your guess:
<h:inputText value="#{numberGuess.currentGuess}" id="inputGuess"
required="true" size="3"
rendered="#{(numberGuess.biggest-numberGuess.smallest) gt 20}">
<f:validateLongRange maximum="#{numberGuess.biggest}"
minimum="#{numberGuess.smallest}"/>
</h:inputText>
<h:selectOneMenu value="#{numberGuess.currentGuess}"
id="selectGuessMenu" required="true"
rendered="#{(numberGuess.biggest-numberGuess.smallest) le 20 and
(numberGuess.biggest-numberGuess.smallest) gt 4}">
<s:selectItems value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
</h:selectOneMenu>
<h:selectOneRadio value="#{numberGuess.currentGuess}" id="selectGuessRadio"
required="true"
rendered="#{(numberGuess.biggest-numberGuess.smallest) le 4}">
<s:selectItems value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
</h:selectOneRadio>
<h:commandButton value="Guess" action="guess"/>
<s:button value="Cheat" view="/confirm.jspx"/>
<s:button value="Give up" action="giveup"/>
</div>
<div>
<h:message for="inputGuess" style="color: red"/>
</div>
</h:form>
</f:view>
</body>
</html>
</jsp:root>
Notice how the command button names the guess transition instead of calling an action directly.
The win.jspx page is predictable:
Exemple 1.22. win.jspx
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
version="2.0">
<jsp:output doctype-root-element="html"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<jsp:directive.page contentType="text/html"/>
<html>
<head>
<title>You won!</title>
<link href="niceforms.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>You won!</h1>
<f:view>
Yes, the answer was <h:outputText value="#{numberGuess.currentGuess}" />.
It took you <h:outputText value="#{numberGuess.guessCount}" /> guesses.
<h:outputText value="But you cheated, so it doesn't count!"
rendered="#{numberGuess.cheat}"/>
Would you like to <a href="numberGuess.seam">play again</a>?
</f:view>
</body>
</html>
</jsp:root>
The lose.jspx looks roughly the same, so we'll skip over it.
Finally, we'll look at the actual application code:
Exemple 1.23. NumberGuess.java
@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess implements Serializable {
private int randomNumber;
private Integer currentGuess;
private int biggest;
private int smallest;
private int guessCount;
private int maxGuesses;
private boolean cheated;
@Create
public void begin()
{
randomNumber = new Random().nextInt(100);
guessCount = 0;
biggest = 100;
smallest = 1;
}
public void setCurrentGuess(Integer guess)
{
this.currentGuess = guess;
}
public Integer getCurrentGuess()
{
return currentGuess;
}
public void guess()
{
if (currentGuess>randomNumber)
{
biggest = currentGuess - 1;
}
if (currentGuess<randomNumber)
{
smallest = currentGuess + 1;
}
guessCount ++;
}
public boolean isCorrectGuess()
{
return currentGuess==randomNumber;
}
public int getBiggest()
{
return biggest;
}
public int getSmallest()
{
return smallest;
}
public int getGuessCount()
{
return guessCount;
}
public boolean isLastGuess()
{
return guessCount==maxGuesses;
}
public int getRemainingGuesses() {
return maxGuesses-guessCount;
}
public void setMaxGuesses(int maxGuesses) {
this.maxGuesses = maxGuesses;
}
public int getMaxGuesses() {
return maxGuesses;
}
public int getRandomNumber() {
return randomNumber;
}
public void cheated()
{
cheated = true;
}
public boolean isCheat() {
return cheated;
}
public List<Integer> getPossibilities()
{
List<Integer> result = new ArrayList<Integer>();
for(int i=smallest; i<=biggest; i++) result.add(i);
return result;
}
}
![]() | The first time a JSP page asks for a |
The pages.xml file starts a Seam conversation (much more about that later), and specifies the pageflow definition to use for the conversation's page flow.
Exemple 1.24. pages.xml
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">
<page view-id="/numberGuess.jspx">
<begin-conversation join="true" pageflow="numberGuess"/>
</page>
</pages>
As you can see, this Seam component is pure business logic! It doesn't need to know anything at all about the user interaction flow. This makes the component potentially more reuseable.
We'll step through basic flow of the application. The game starts with the numberGuess.jspx view. When the page is first displayed, the pages.xml configuration causes conversation to begin and associates the numberGuess pageflow with that conversation. The pageflow starts with a start-page tag, which is a wait state, so the numberGuess.xhtml is rendered.
The view references the numberGuess component, causing a new instance to be created and stored in the conversation. The @Create method is called, initializing the state of the game. The view displays an h:form that allows the user to edit #{numberGuess.currentGuess}.
The "Guess" button triggers the guess action. Seam defers to the pageflow to handle the action, which says that the pageflow should transition to the evaluateGuess state, first invoking #{numberGuess.guess}, which updates the guess count and highest/lowest suggestions in the numberGuess component.
The evaluateGuess state checks the value of #{numberGuess.correctGuess} and transitions either to the win or evaluatingRemainingGuesses state. We'll assume the number was incorrect, in which case the pageflow transitions to evaluatingRemainingGuesses. That is also a decision state, which tests the #{numberGuess.lastGuess} state to determine whether or not the user has more guesses. If there are more guesses (lastGuess is false), we transition back to the original displayGuess state. Finally we've reached a page state, so the associated page /numberGuess.jspx is displayed. Since the page has a redirect element, Seam sends a redirect to the the user's browser, starting the process over.
We won't follow the state any more except to note that if on a future request either the win or the lose transition were taken, the user would be taken to either the /win.jspx or /lose.jspx. Both states specify that Seam should end the conversation, tossing away all the game state and pageflow state, before redirecting the user to the final page.
The numberguess example also contains Giveup and Cheat buttons. You should be able to trace the pageflow state for both actions relatively easily. Pay particular attention to the cheat transition, which loads a sub-process to handle that flow. Although it's overkill for this application, it does demonstrate how complex pageflows can be broken down into smaller parts to make them easier to understand.
The booking application is a complete hotel room reservation system incorporating the following features:
User registration
Login
Logout
Set password
Hotel search
Hotel selection
Room reservation
Reservation confirmation
Existing reservation list

The booking application uses JSF, EJB 3.0 and Seam, together with Facelets for the view. There is also a port of this application to JSF, Facelets, Seam, JavaBeans and Hibernate3.
One of the things you'll notice if you play with this application for long enough is that it is extremely robust. You can play with back buttons and browser refresh and opening multiple windows and entering nonsensical data as much as you like and you will find it very difficult to make the application crash. You might think that we spent weeks testing and fixing bugs to achive this. Actually, this is not the case. Seam was designed to make it very straightforward to build robust web applications and a lot of robustness that you are probably used to having to code yourself comes naturally and automatically with Seam.
As you browse the sourcecode of the example application, and learn how the application works, observe how the declarative state management and integrated validation has been used to achieve this robustness.
The project structure is identical to the previous one, to install and deploy this application, please refer to Section 1.1, « Using the Seam examples ». Once you've successfully started the application, you can access it by pointing your browser to http://localhost:8080/seam-booking/
The application uses six session beans for to implement the business logic for the listed features.
AuthenticatorAction provides the login authentication logic.
BookingListAction retrieves existing bookings for the currently logged in user.
ChangePasswordAction updates the password of the currently logged in user.
HotelBookingAction implements booking and confirmation functionality. This functionality is implemented as a conversation, so this is one of the most interesting classes in the application.
HotelSearchingAction implements the hotel search functionality.
RegisterAction registers a new system user.
Three entity beans implement the application's persistent domain model.
Hotel is an entity bean that represent a hotel
Booking is an entity bean that represents an existing booking
User is an entity bean to represents a user who can make hotel bookings
We encourage you browse the sourcecode at your pleasure. In this tutorial we'll concentrate upon one particular piece of functionality: hotel search, selection, booking and confirmation. From the point of view of the user, everything from selecting a hotel to confirming a booking is one continuous unit of work, a conversation. Searching, however, is not part of the conversation. The user can select multiple hotels from the same search results page, in different browser tabs.
Most web application architectures have no first class construct to represent a conversation. This causes enormous problems managing conversational state. Usually, Java web applications use a combination of several techniques. Some state can be transfered in the URL. What can't is either thrown into the HttpSession or flushed to the database after every request, and reconstructed from the database at the beginning of each new request.
Since the database is the least scalable tier, this often results in an utterly unacceptable lack of scalability. Added latency is also a problem, due to the extra traffic to and from the database on every request. To reduce this redundant traffic, Java applications often introduce a data (second-level) cache that keeps commonly accessed data between requests. This cache is necessarily inefficient, because invalidation is based upon an LRU policy instead of being based upon when the user has finished working with the data. Furthermore, because the cache is shared between many concurrent transactions, we've introduced a whole raft of problem's associated with keeping the cached state consistent with the database.
Now consider the state held in the HttpSession. The HttpSession is great place for true session data, data that is common to all requests that the user has with the application. However, it's a bad place to store data related to individual series of requests. Using the session of conversational quickly breaks down when dealing with the back button and multiple windows. On top of that, without careful programming, data in the HTTP Session can grow quite large, making the HTTP session difficult to cluster. Developing mechanisms to isolate session state associated with different concurrent conversations, and incorporating failsafes to ensure that conversation state is destroyed when the user aborts one of the conversations by closing a browser window or tab is not for the faint hearted. Fortunately, with Seam, you don't have to worry about that.
Seam introduces the conversation context as a first class construct. You can safely keep conversational state in this context, and be assured that it will have a well-defined lifecycle. Even better, you won't need to be continually pushing data back and forth between the application server and the database, since the conversation context is a natural cache of data that the user is currently working with.
In this application, we'll use the conversation context to store stateful session beans. There is an ancient canard in the Java community that stateful session beans are a scalability killer. This may have been true in the early days of enterprise Java, but it is no longer true today. Modern application servers have extremely sophisticated mechanisms for stateful session bean state replication. JBoss AS, for example, performs fine-grained replication, replicating only those bean attribute values which actually changed. Note that all the traditional technical arguments for why stateful beans are inefficient apply equally to the HttpSession, so the practice of shifting state from business tier stateful session bean components to the web session to try and improve performance is unbelievably misguided. It is certainly possible to write unscalable applications using stateful session beans, by using stateful beans incorrectly, or by using them for the wrong thing. But that doesn't mean you should never use them. If you remain unconvinced, Seam allows the use of POJOs instead of stateful session beans. With Seam, the choice is yours.
The booking example application shows how stateful components with different scopes can collaborate together to achieve complex behaviors. The main page of the booking application allows the user to search for hotels. The search results are kept in the Seam session scope. When the user navigates to one of these hotels, a conversation begins, and a conversation scoped component calls back to the session scoped component to retrieve the selected hotel.
The booking example also demonstrates the use of RichFaces Ajax to implement rich client behavior without the use of handwritten JavaScript.
The search functionality is implemented using a session-scope stateful session bean, similar to the one we saw in the message list example.
Exemple 1.25. HotelSearchingAction.java
@Stateful@Name("hotelSearch") @Scope(ScopeType.SESSION) @Restrict("#{i
dentity.loggedIn}") public class HotelSearchingAction implements HotelSearching { @PersistenceContext private EntityManager em; private String searchString; private int pageSize = 10; private int page; @DataModel
private List<Hotel> hotels; public void find() { page = 0; queryHotels(); } public void nextPage() { page++; queryHotels(); } private void queryHotels() { hotels = em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} " + "or lower(h.city) like #{pattern} " + "or lower(h.zip) like #{pattern} " + "or lower(h.address) like #{pattern}") .setMaxResults(pageSize) .setFirstResult( page * pageSize ) .getResultList(); } public boolean isNextPageAvailable() { return hotels!=null && hotels.size()==pageSize; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } @Factory(value="pattern", scope=ScopeType.EVENT) public String getSearchPattern() { return searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%'; } public String getSearchString() { return searchString; } public void setSearchString(String searchString) { this.searchString = searchString; }
@Remove public void destroy() {} }
![]() | The EJB standard |
![]() | The |
![]() | The |
![]() | The EJB standard |
The main page of the application is a Facelets page. Let's look at the fragment which relates to searching for hotels:
Exemple 1.26. main.xhtml
<div class="section">
<span class="errors">
<h:messages globalOnly="true"/>
</span>
<h1>Search Hotels</h1>
<h:form id="searchCriteria">
<fieldset>
<h:inputText id="searchString" value="#{hotelSearch.searchString}"
style="width: 165px;">
<a:support event="onkeyup" actionListener="#{hotelSearch.find}"
reRender="searchResults" />
</h:inputText>
 
<a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}"
reRender="searchResults"/>
 
<a:stat
us>
<f:facet name="start">
<h:graphicImage value="/img/spinner.gif"/>
</f:facet>
</a:status>
<br/>
<h:outputLabel for="pageSize">Maximum results:</h:outputLabel> 
<h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
</fieldset>
</h:form>
</div>
<a:outputPanel
id="searchResults">
<div class="section">
<h:outputText value="No Hotels Found"
rendered="#{hotels != null and hotels.rowCount==0}"/>
<h:dataTable id="hotels" value="#{hotels}" var="hot"
rendered="#{hotels.rowCount>0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{hot.name}
</h:column>
<h:column>
<f:facet name="header">Address</f:facet>
#{hot.address}
</h:column>
<h:column>
<f:facet name="header">City, State</f:facet>
#{hot.city}, #{hot.state}, #{hot.country}
</h:column>
<h:column>
<f:facet name="header">Zip</f:facet>
#{hot.zip}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<s
:link id="viewHotel" value="View Hotel"
action="#{hotelBooking.selectHotel(hot)}"/>
</h:column>
</h:dataTable>
<s:link value="More results" action="#{hotelSearch.nextPage}"
rendered="#{hotelSearch.nextPageAvailable}"/>
</div>
</a:outputPanel> ![]() | The RichFaces Ajax |
![]() | The RichFaces Ajax |
![]() | The RichFaces Ajax |
![]() | The Seam If you're wondering how navigation occurs, you can find all the rules in |
This page displays the search results dynamically as we type, and lets us choose a hotel and pass it to the selectHotel() method of the HotelBookingAction, which is where the really interesting stuff is going to happen.
Now let's see how the booking example application uses a conversation-scoped stateful session bean to achieve a natural cache of persistent data related to the conversation. The following code example is pretty long. But if you think of it as a list of scripted actions that implement the various steps of the conversation, it's understandable. Read the class from top to bottom, as if it were a story.
Exemple 1.27. HotelBookingAction.java
@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
@Persistenc
eContext(type=EXTENDED)
private EntityManager em;
@In
private User user;
@In(required=false) @Out
private Hotel hotel;
@In(required=false)
@Out(requir
ed=false)
private Booking booking;
@In
private FacesMessages facesMessages;
@In
private Events events;
@Logger
private Log log;
private boolean bookingValid;
@Begin
public void selectHotel(Hotel selectedHotel)
{
hotel = em.merge(selectedHotel);
}
public void bookHotel()
{
booking = new Booking(hotel, user);
Calendar calendar = Calendar.getInstance();
booking.setCheckinDate( calendar.getTime() );
calendar.add(Calendar.DAY_OF_MONTH, 1);
booking.setCheckoutDate( calendar.getTime() );
}
public void setBookingDetails()
{
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1);
if ( booking.getCheckinDate().before( calendar.getTime() ) )
{
facesMessages.addToControl("checkinDate", "Check in date must be a future date");
bookingValid=false;
}
else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
{
facesMessages.addToControl("checkoutDate",
"Check out date must be later than check in date");
bookingValid=false;
}
else
{
bookingValid=true;
}
}
public boolean isBookingValid()
{
return bookingValid;
}
@End
public void confirm()
{
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number " +
" for #{hotel.name} is #{booki g.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End
public void cancel() {}
@Remove
public void destroy() {}
![]() | This bean uses an EJB3 extended persistence context, so that any entity instances remain managed for the whole lifecycle of the stateful session bean. |
![]() | The |
![]() | The |
![]() | The |
![]() | This EJB remove method will be called when Seam destroys the conversation context. Don't forget to define this method! |
HotelBookingAction contains all the action listener methods that implement selection, booking and booking confirmation, and holds state related to this work in its instance variables. We think you'll agree that this code is much cleaner and simpler than getting and setting HttpSession attributes.
Even better, a user can have multiple isolated conversations per login session. Try it! Log in, run a search, and navigate to different hotel pages in multiple browser tabs. You'll be able to work on creating two different hotel reservations at the same time. If you leave any one conversation inactive for long enough, Seam will eventually time out that conversation and destroy its state. If, after ending a conversation, you backbutton to a page of that conversation and try to perform an action, Seam will detect that the conversation was already ended, and redirect you to the search page.
The WAR also includes seam-debug.jar. The Seam debug page will be available if this jar is deployed in WEB-INF/lib, along with the Facelets, and if you set the debug property of the init component:
<core:init jndi-pattern="@jndiPattern@" debug="true"/>
This page lets you browse and inspect the Seam components in any of the Seam contexts associated with your current login session. Just point your browser at http://localhost:8080/seam-booking/debug.seam .

Long-running conversations make it simple to maintain consistency of state in an application even in the face of multi-window operation and back-buttoning. Unfortunately, simply beginning and ending a long-running conversation is not always enough. Depending on the requirements of the application, inconsistencies between what the user's expectations and the reality of the application’s state can still result.
The nested booking application extends the features of the hotel booking application to incorporate the selection of rooms. Each hotel has available rooms with descriptions for a user to select from. This requires the addition of a room selection page in the hotel reservation flow.

The user now has the option to select any available room to be included in the booking. As with the hotel booking application we saw previously, this can lead to issues with state consistency. As with storing state in the HTTPSession, if a conversation variable changes it affects all windows operating within the same conversation context.
To demonstrate this, let’s suppose the user clones the room selection screen in a new window. The user then selects the Wonderful Room and proceeds to the confirmation screen. To see just how much it would cost to live the high-life, the user returns to the original window, selects the Fantastic Suite for booking, and again proceeds to confirmation. After reviewing the total cost, the user decides that practicality wins out and returns to the window showing Wonderful Room to confirm.
In this scenario, if we simply store all state in the conversation, we are not protected from multi-window operation within the same conversation. Nested conversations allow us to achieve correct behavior even when context can vary within the same conversation.
Now let's see how the nested booking example extends the behavior of the hotel booking application through use of nested conversations. Again, we can read the class from top to bottom, as if it were a story.
Exemple 1.28. RoomPreferenceAction.java
@Stateful
@Name("roomPreference")
@Restrict("#{identity.loggedIn}")
public class RoomPreferenceAction implements RoomPreference
{
@Logger
private Log log;
@In private Hotel hotel;
@In private Booking booking;
@DataModel(value="availableRooms")
private List<Room> availableRooms;
@DataModelSelection(value="availableRooms")
private Room roomSelection;
@In(required=false, value="roomSelection")
@Out(required=false, value="roomSelection")
private Room room;
@Factory("availableRooms")
public void
loadAvailableRooms()
{
availableRooms = hotel.getAvailableRooms(booking.getCheckinDate(), booking.getCheckoutDate());
log.info("Retrieved #0 available rooms", availableRooms.size());
}
public BigDecimal getExpectedPrice()
{
log.info("Retrieving price for room #0", roomSelection.getName());
return booking.getTotal(roomSelection);
}
@Begin(nest
ed=true)
public String selectPreference()
{
log.info("Room selected");
this.roo
m = this.roomSelection;
return "payment";
}
public String requestConfirmation()
{
// all validations are performed through the s:validateAll, so checks are already
// performed
log.info("Request confirmation from user");
return "confirm";
}
@End(beforeRedirect=true)
public Stri
ng cancel()
{
log.info("ending conversation");
return "cancel";
}
@Destroy @Remove
public void destroy() {}
}
![]() | The |
![]() | When |
![]() | The |
![]() | The |
When we begin a nested conversation it is pushed onto the conversation stack. In the nestedbooking example, the conversation stack consists of the outer long-running conversation (the booking) and each of the nested conversations (room selections).
Exemple 1.29. rooms.xhtml
<div class="section">
<h1>Room Preference</h1>
</div>
<div class="section">
<h:form id="room_selections_form">
<div class="section">
<h:outputText styleClass="output"
value="No rooms available for the dates selected: "
rendered="#{availableRooms != null and availableRooms.rowCount == 0}"/>
<h:outputText styleClass="output"
value="Rooms available for the dates selected: "
rendered="#{availableRooms != null and availableRooms.rowCount > 0}"/>
<h:outputText styleClass="output" value="#{booking.checkinDate}"/> -
<h:outputText styleClass="output" value="#{booking.checkoutDate}"/>
<br/><br/>
<h:dataTable value="#{availableRooms}" var="room"
rendered="#{availableRooms.rowCount > 0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{room.name}
</h:column>
<h:column>
<f:facet name="header">Description</f:facet>
#{room.description}
</h:column>
<h:column>
<f:facet name="header">Per Night</f:facet>
<h:outputText value="#{room.price}">
<f:convertNumber type="currency" currencySymbol="$"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="selectRoomPreference"
action="#{roomPreference.selectPreference}">Select</h:commandLink>
</h:column>
</h:dataTable>
</div>
<div class="entry">
<div class="label"> </div>
<d
iv class="input">
<s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
</div>
</div>
</h:form>
</div>
![]() | When requested from EL, the |
![]() | Invoking the |
![]() | Revising the dates simply returns to the |
Now that we have seen how to nest a conversation, let's see how we can confirm the booking once a room has been selected. This can be achieved by simply extending the behavior of the HotelBookingAction.
Exemple 1.30. HotelBookingAction.java
@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
@PersistenceContext(type=EXTENDED)
private EntityManager em;
@In
private User user;
@In(required=false) @Out
private Hotel hotel;
@In(required=false)
@Out(required=false)
private Booking booking;
@In(required=false)
private Room roomSelection;
@In
private FacesMessages facesMessages;
@In
private Events events;
@Logger
private Log log;
@Begin
public void selectHotel(Hotel selectedHotel)
{
log.info("Selected hotel #0", selectedHotel.getName());
hotel = em.merge(selectedHotel);
}
public String setBookingDates()
{
// the result will indicate whether or not to begin the nested conversation
// as well as the navigation. if a null result is returned, the nested
// conversation will not begin, and the user will be returned to the current
// page to fix validation issues
String result = null;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1);
// validate what we have received from the user so far
if ( booking.getCheckinDate().before( calendar.getTime() ) )
{
facesMessages.addToControl("checkinDate", "Check in date must be a future date");
}
else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
{
facesMessages.addToControl("checkoutDate", "Check out date must be later than check in date");
}
else
{
result = "rooms";
}
return result;
}
public void bookHotel()
{
booking = new Booking(hotel, user);
Calendar calendar = Calendar.getInstance();
booking.setCheckinDate( calendar.getTime() );
calendar.add(Calendar.DAY_OF_MONTH, 1);
booking.setCheckoutDate( calendar.getTime() );
}
@End(root=true)
public void
confirm()
{
// on confirmation we set the room preference in the booking. the room preference
// will be injected based on the nested conversation we are in.
booking.setRoomPreference(roomSelection);
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End(root=t
rue, beforeRedirect=true)
public void cancel() {}
@Destroy @Remove
public void destroy() {}
}
![]() | Annotating an action with |
![]() | The |
![]() | By simply annotating the cancellation action with |
Feel free to deploy the application, open many windows or tabs and attempt combinations of various hotels with various room preferences. Confirming a booking always results in the correct hotel and room preference thanks to the nested conversation model.
The DVD Store demo application shows the practical usage of jBPM for both task management and pageflow.
The user screens take advantage of a jPDL pageflow to implement searching and shopping cart functionality.

The administration screens take use jBPM to manage the approval and shipping cycle for orders. The business process may even be changed dynamically, by selecting a different process definition!

The Seam DVD Store demo can be run from dvdstore directory, just like the other demo applications.
Seam makes it very easy to implement applications which keep state on the server-side. However, server-side state is not always appropriate, especially in for functionality that serves up content. For this kind of problem we often want to keep application state in the URL so that any page can be accessed at any time through a bookmark. The blog example shows how to a implement an application that supports bookmarking throughout, even on the search results page. This example demonstrates how Seam can manage application state in the URL as well as how Seam can rewrite those URLs to be even

The Blog example demonstrates the use of "pull"-style MVC, where instead of using action listener methods to retrieve data and prepare the data for the view, the view pulls data from components as it is being rendered.
This snippet from the index.xhtml facelets page displays a list of recent blog entries:
Exemple 1.31.
<h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3">
<h:column>
<div class="blogEntry">
<h3>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt}"/>
</div>
<p>
<s:link view="/entry.xhtml" rendered="#{blogEntry.excerpt!=null}" propagation="none"
value="Read more...">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
<p>
[Posted on 
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText>]
 
<s:link view="/entry.xhtml" propagation="none" value="[Link]">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
</div>
</h:column>
</h:dataTable>
If we navigate to this page from a bookmark, how does the #{blog.recentBlogEntries} data used by the <h:dataTable> actually get initialized? The Blog is retrieved lazily — "pulled" — when needed, by a Seam component named blog. This is the opposite flow of control to what is used in traditional action-based web frameworks like Struts.
Exemple 1.32.
@Name("blog")
@Scope(ScopeType.STATELESS)
@AutoCreate
public class BlogService
{
@In EntityM
anager entityManager;
@Unwrap
public Blog getBlog()
{
return (Blog) entityManager.createQuery("select distinct b from Blog b left join fetch b.blogEntries")
.setHint("org.hibernate.cacheable", true)
.getSingleResult();
}
}![]() | This component uses a seam-managed persistence context. Unlike the other examples we've seen, this persistence context is managed by Seam, instead of by the EJB3 container. The persistence context spans the entire web request, allowing us to avoid any exceptions that occur when accessing unfetched associations in the view. |
![]() | The |
This is good so far, but what about bookmarking the result of form submissions, such as a search results page?
The blog example has a tiny form in the top right of each page that allows the user to search for blog entries. This is defined in a file, menu.xhtml, included by the facelets template, template.xhtml:
Exemple 1.33.
<div id="search">
<h:form>
<h:inputText value="#{searchAction.searchPattern}"/>
<h:commandButton value="Search" action="/search.xhtml"/>
</h:form>
</div>
To implement a bookmarkable search results page, we need to perform a browser redirect after processing the search form submission. Because we used the JSF view id as the action outcome, Seam automatically redirects to the view id when the form is submitted. Alternatively, we could have defined a navigation rule like this:
<navigation-rule>
<navigation-case>
<from-outcome>searchResults</from-outcome>
<to-view-id>/search.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
Then the form would have looked like this:
<div id="search">
<h:form>
<h:inputText value="#{searchAction.searchPattern}"/>
<h:commandButton value="Search" action="searchResults"/>
</h:form>
</div>
But when we redirect, we need to include the values submitted with the form in the URL to get a bookmarkable URL like http://localhost:8080/seam-blog/search/. JSF does not provide an easy way to do this, but Seam does. We use two Seam features to accomplish this: page parameters and URL rewriting. Both are defined in WEB-INF/pages.xml:
Exemple 1.34.
<pages>
<page view-id="/search.xhtml">
<rewrite pattern="/search/{searchPattern}"/>
<rewrite pattern="/search"/>
<param name="searchPattern" value="#{searchService.searchPattern}"/>
</page>
...
</pages>
The page parameter instructs Seam to link the request parameter named searchPattern to the value of #{searchService.searchPattern}, both whenever a request for the Search page comes in and whenever a link to the search page is generated. Seam takes responsibility for maintaining the link between URL state and application state, and you, the developer, don't have to worry about it.
Without URL rewriting, the URL for a search on the term book would be http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book. This is nice, but Seam can make the URL even simpler using a rewrite rule. The first rewrite rule, for the pattern /search/{searchPattern}, says that any time we have a URL for search.xhtml with a searchPattern request parameter, we can fold that URL into the simpler URL. So,the URL we saw earlier, http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book can be written instead as http://localhost:8080/seam-blog/search/book.
Just like with page parameters, URL rewriting is bi-directional. That means that Seam forwards requests for the simpler URL to the the right view, and it also automatically generates the simpler view for you. You never need to worry about constructing URLs. It's all handled transparently behind the scenes. The only requirement is that to use URL rewriting, the rewrite filter needs to be enabled in components.xml.
<web:rewrite-filter view-mapping="/seam/*" />
The redirect takes us to the search.xhtml page:
<h:dataTable value="#{searchResults}" var="blogEntry">
<h:column>
<div>
<s:link view="/entry.xhtml" propagation="none" value="#{blogEntry.title}">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
posted on
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText>
</div>
</h:column>
</h:dataTable>
Which again uses "pull"-style MVC to retrieve the actual search results using Hibernate Search.
@Name("searchService")
public class SearchService
{
@In
private FullTextEntityManager entityManager;
private String searchPattern;
@Factory("searchResults")
public List<BlogEntry> getSearchResults()
{
if (searchPattern==null || "".equals(searchPattern) ) {
searchPattern = null;
return entityManager.createQuery("select be from BlogEntry be order by date desc").getResultList();
}
else
{
Map<String,Float> boostPerField = new HashMap<String,Float>();
boostPerField.put( "title", 4f );
boostPerField.put( "body", 1f );
String[] productFields = {"title", "body"};
QueryParser parser = new MultiFieldQueryParser(productFields, new StandardAnalyzer(), boostPerField);
parser.setAllowLeadingWildcard(true);
org.apache.lucene.search.Query luceneQuery;
try
{
luceneQuery = parser.parse(searchPattern);
}
catch (ParseException e)
{
return null;
}
return entityManager.createFullTextQuery(luceneQuery, BlogEntry.class)
.setMaxResults(100)
.getResultList();
}
}
public String getSearchPattern()
{
return searchPattern;
}
public void setSearchPattern(String searchPattern)
{
this.searchPattern = searchPattern;
}
}
Very occasionally, it makes more sense to use push-style MVC for processing RESTful pages, and so Seam provides the notion of a page action. The Blog example uses a page action for the blog entry page, entry.xhtml. Note that this is a little bit contrived, it would have been easier to use pull-style MVC here as well.
The entryAction component works much like an action class in a traditional push-MVC action-oriented framework like Struts:
@Name("entryAction")
@Scope(STATELESS)
public class EntryAction
{
@In Blog blog;
@Out BlogEntry blogEntry;
public void loadBlogEntry(String id) throws EntryNotFoundException
{
blogEntry = blog.getBlogEntry(id);
if (blogEntry==null) throw new EntryNotFoundException(id);
}
}
Page actions are also declared in pages.xml:
<pages>
...
<page view-id="/entry.xhtml">
<rewrite pattern="/entry/{blogEntryId}" />
<rewrite pattern="/entry" />
<param name="blogEntryId"
value="#{blogEntry.id}"/>
<action execute="#{entryAction.loadBlogEntry(blogEntry.id)}"/>
</page>
<page view-id="/post.xhtml" login-required="true">
<rewrite pattern="/post" />
<action execute="#{postAction.post}"
if="#{validation.succeeded}"/>
<action execute="#{postAction.invalid}"
if="#{validation.failed}"/>
<navigation from-action="#{postAction.post}">
<redirect view-id="/index.xhtml"/>
</navigation>
</page>
<page view-id="*">
<action execute="#{blog.hitCount.hit}"/>
</page>
</pages>
Notice that the example is using page actions for post validation and the pageview counter. Also notice the use of a parameter in the page action method binding. This is not a standard feature of JSF EL, but Seam lets you use it, not just for page actions but also in JSF method bindings.
When the entry.xhtml page is requested, Seam first binds the page parameter blogEntryId to the model. Keep in mind that because of the URL rewriting, the blogEntryId parameter name won't show up in the URL. Seam then runs the page action, which retrieves the needed data — the blogEntry — and places it in the Seam event context. Finally, the following is rendered:
<div class="blogEntry">
<h3>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.body}"/>
</div>
<p>
[Posted on 
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText>]
</p>
</div>
If the blog entry is not found in the database, the EntryNotFoundException exception is thrown. We want this exception to result in a 404 error, not a 505, so we annotate the exception class:
@ApplicationException(rollback=true)
@HttpError(errorCode=HttpServletResponse.SC_NOT_FOUND)
public class EntryNotFoundException extends Exception
{
EntryNotFoundException(String id)
{
super("entry not found: " + id);
}
}
An alternative implementation of the example does not use the parameter in the method binding:
@Name("entryAction")
@Scope(STATELESS)
public class EntryAction
{
@In(create=true)
private Blog blog;
@In @Out
private BlogEntry blogEntry;
public void loadBlogEntry() throws EntryNotFoundException
{
blogEntry = blog.getBlogEntry( blogEntry.getId() );
if (blogEntry==null) throw new EntryNotFoundException(id);
}
}
<pages>
...
<page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry}">
<param name="blogEntryId" value="#{blogEntry.id}"/>
</page>
...
</pages>
It is a matter of taste which implementation you prefer.
The blog demo also demonstrates very simple password authentication, posting to the blog, page fragment caching and atom feed generation.
La distribution Seam inclus un utilitaire en ligne de commande qui permet facilement de configurer un projet Eclipse, de générer un squelette de code simple Seam et de réaliser une ingénierie inverse sur une base de données pré existante.
C’est une façon simple de garder les pieds au sec avec Seam, et de vous donnez des munitions pour la prochaine fois que vous vous trouverez enfermer dans un ascenseur avec un de ces gars tordus de Ruby vous taquinant sur le fait que leur jouet est génial et merveilleux pour construire des applications triviales qui mettent des choses dans les base de données.
Dans cette version, seam-gen fonctionne mieux pour tous avec JBoss AS. Vous pouvez utiliser le projet généré avec d’autre serveurs d’application J2EE ou Java EE5. en réalisant plusieurs modifications à la main à la configuration du projet.
Vous pouvez utiliser seam-gen sans Eclipse, mais dans ce tutoriel, nous voulons vous montrer comment l’utiliser en conjonction avec Eclipse pour le débogage ou l’intégration des test. Si vous ne voulez pas installer Eclipse vous pouvez quand même suivre ce tutoriel—toutes les étapes peuvent être réalisées depuis la ligne de commande.
Seam-gen est simplement un gros script Ant bien déguelasse utilisant des outils Hibernate, liant ensemble des patrons. Ce qui rends facile la personnalisation cela vos besoins.
Soyez sur d’avoir JDK5 ou JDK6 (voir Section 42.1, « Les dépendances du JDK » pour les détails), JBoss AS 4.2 ou 5.0 et Ant 1.7.0, avec des versions récentes d’Eclipse, de Jboss IDE plugin pour Eclipse et de TestNG plugin pour Eclipse correctement installés avant le démarrage. Ajoutez votre installation JBoss a la vue Server JBoss dans Eclipse. Démarrer JBoss en mode debug. Enfin, démarrez un interpréteur de commande dans le dossier où vous avez dézipper la distribution de Seam.
Jboss dispose d’un support sophistiquer pour le redéploiement à chaud des WARs et des EARs. Malheureusement, à cause de bugs dans la JVM, le redéploiement répété de EAR—ce qui est habituel dans la phase de développement—peut éventuellement entrainer la JVM a être à cours d’espace perm gen. Pour cette raison, nous recommandons d’exécuter JBoss dans une JVM avec un large espace perm gen pendant la période de développement. Si vous exécuter Jboss depuis JBoss IDE, vous pouvez configurer cela dans la configuration de lancement du serveur, dans "VM arguments". Nous vous conseillons les valeurs suivantes :
-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m
Si vous ne disposez pas d’assez de mémoire, ce qui suit est notre recommandation minimale :
-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m
vous exécuter JBoss depuis la ligne de commande, vous pouvez configurer les options de la JVM dans bin/run.conf.
Si vous ne voulez pas vous inquiéter de ce truc pour l’instant, nous n’êtes pas obligé, revenez y plus tard quand vous rencontrer votre premier OutOfMemoryException..
La première chose que vous avez besoin de faire est de configurer seam-gen pour votre environnement : le dossier d’installation de JBoss AS, l'espace de travail d’Eclipse et connexion à la base de données. C'est simple, entrez juste :
cd jboss-seam-2.2.x seam setup
Et vous allez être interrogé pour un complément d’information :
~/workspace/jboss-seam$ ./seam setup
Buildfile: build.xml
init:
setup:
[echo] Welcome to seam-gen :-)
[input] Enter your project workspace (the directory that contains your Seam projects) [C:/Projects] [C:/Projects]
/Users/pmuir/workspace
[input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA] [C:/Program Files/jboss-4.2.3.GA]
/Applications/jboss-4.2.3.GA
[input] Enter the project name [myproject] [myproject]
helloworld
[echo] Accepted project name as: helloworld
[input] Select a RichFaces skin (not applicable if using ICEFaces) [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)
[input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear] ([ear], war, )
[input] Enter the Java package name for your session beans [com.mydomain.helloworld] [com.mydomain.helloworld]
org.jboss.helloworld
[input] Enter the Java package name for your entity beans [org.jboss.helloworld] [org.jboss.helloworld]
[input] Enter the Java package name for your test cases [org.jboss.helloworld.test] [org.jboss.helloworld.test]
[input] What kind of database are you using? [hsql] ([hsql], mysql, oracle, postgres, mssql, db2, sybase, enterprisedb, h2)
mysql
[input] Enter the Hibernate dialect for your database [org.hibernate.dialect.MySQLDialect] [org.hibernate.dialect.MySQLDialect]
[input] Enter the filesystem path to the JDBC driver jar [lib/hsqldb.jar] [lib/hsqldb.jar]
/Users/pmuir/java/mysql.jar
[input] Enter JDBC driver class for your database [com.mysql.jdbc.Driver] [com.mysql.jdbc.Driver]
[input] Enter the JDBC URL for your database [jdbc:mysql:///test] [jdbc:mysql:///test]
jdbc:mysql:///helloworld
[input] Enter database username [sa] [sa]
pmuir
[input] Enter database password [] []
[input] skipping input as property hibernate.default_schema.new has already been set.
[input] Enter the database catalog name (it is OK to leave this blank) [] []
[input] Are you working with tables that already exist in the database? [n] (y, [n], )
y
[input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y, [n], )
n
[input] Enter your ICEfaces home directory (leave blank to omit ICEfaces) [] []
[propertyfile] Creating new property file: /Users/pmuir/workspace/jboss-seam/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss server
[echo] Type 'seam create-project' to create the new project
BUILD SUCCESSFUL
Total time: 1 minute 32 seconds
~/workspace/jboss-seam $ Cet outil fourni des valeurs par défaut judicieuses que vous pouvez accepter juste en appuyant sur la couche enter à la demande.
Le choix le plus important que vous devez faire est entre un déploiement EAR et un déploiement WAR pour votre projet. Les projets EAR supportent EJB 3.0 et nécessite Java EE5. Les projets WAR ne supportent pas EJB 3.0 mais peuvent être déployés dans un environnement J2EE. Le conditionnement en WAR est aussi plus simple à comprendre. Si vous avez installé un server d’application opérationnel EJB3 comme JBoss, choisissez ear. Sinon, choississez war. Nous allons partir du principe que vous avez choisi un déploiement EAR pour le reste du tutoriel, mais vous pouvez suivre exactement les même étapes pour un déploiement WAR.
Si vous travaillez avec un modèle de données déjà existant, soyez sur d’indiquer à seam-gen que des tables existent déjà dans la base de données.
Les régleages sont stockés dans seam-gen/build.properties, mais vous pouvez aussi les modifier simplement en relançant seam setup une seconde fois.
Maintenant vous pouvez créer un nouveau projet dans le dossier de votre espace de travail d’Eclipse, en entrant :
seam new-project
C:\Projects\jboss-seam>seam new-project
Buildfile: build.xml
...
new-project:
[echo] A new Seam project named 'helloworld' was created in the C:\Projects directory
[echo] Type 'seam explode' and go to http://localhost:8080/helloworld
[echo] Eclipse Users: Add the project into Eclipse using File > New > Project and select General > Project (not Java Project)
[echo] NetBeans Users: Open the project in NetBeans
BUILD SUCCESSFUL
Total time: 7 seconds
C:\Projects\jboss-seam>Cela recopie les jars de Seam, les jars nécéssaires et le jar du pilote JDBC dans un nouveau projet Eclipse et génère tout les fichiers de configuration et les ressources requises, le fichier modèle facelets et la feuille de style, ainsi que les metadata Eclipse et le script de construction Ant. Le projet Eclipse va être automatiquement déployé dans une structure de dossiers transférer dans JBoss AS dès que vous ajouterez le projet en utilisant New -> Project... -> General -> Project -> Next, entrez le Project name ( helloworld dans notre cas), et alors cliquez sur Finish. Ne sélectionnez pas Java Project dans l'assistant de Nouveau Projet.
Si votre JDK par défaut dans Eclipse n’est pas un SDK Java SE 5 ou Java SE 6, vous allez devoir sélectionner un JDK compatible Java SE 5 en faisant Project -> Properties -> Java Compiler.
utre alternative, vous pouvez déployer le projet hors d’Eclipse en entrant seam explode.
Allez sur http://localhost:8080/helloworld pour voir la page d’accueil. C’est une page facelets, view/home.xhtml, l, en utilisant le modèle view/layout/template.xhtml. Vous pouvez éditer cette page, ou le modèle dans Eclipse et voir le résultat immediately, en appuyant sur le bouton actualiser de votre navigateur.
Ne soyez pas effrayé par les documents de configuration XML qui ont été généré dans le dossier du projet. Ils sont pour la plus part des trucs standards Java EE, un truc créé une fois et plus jamais consulté ensuite et ceci est le cas dans 90% des projets Seam. ( ils sont si facile à écrire que même seam-gen peut le faire.)
Le projet généré inclu trois bases de données et des configurations de persistance. Les fichiers persistence-test.xml, persistence-test.xml et import-test.sql sont utilisés quand vous exécutez les tests unitaires testNG avec HSQLDB. Le schéma de base de données et les données de test dans import-test.sql sont toujours exportés vers la base de données avant l’exécution des tests. Les fichiers myproject-dev-ds.xml, persistence-dev.xml et import-dev.sql ont utilisés pendant le déploiement de l’application vers la base de données de développement. Le schéma peut être exporté automatiquement pendant le déploiement, cela dépend si vous avez indiqué à seam-gen que vous travaillez avec une base de données déjà existante. Les fichiers myproject-prod-ds.xml, persistence-prod.xml et import-prod.sql sont utilisés pendant le déploiement de l’application vers la base de données de production. Le schema n’est pas exporté automatiquement pendant le déploiement.
Si vous avez l’habitude de serveur d’application web de style action traditionnel, vous vous demandez comment créer une simple page web avec un méthode d’action sans état en Java. Si vous entrez :
seam new-action
Seam va vous demander des informations, et génèrer une nouvelle page facelets ainsi qu’un composant Seam pour votre projet.
C:\Projects\jboss-seam>seam new-action
Buildfile: build.xml
validate-workspace:
validate-project:
action-input:
[input] Enter the Seam component name
ping
[input] Enter the local interface name [Ping]
[input] Enter the bean class name [PingBean]
[input] Enter the action method name [ping]
[input] Enter the page name [ping]
setup-filters:
new-action:
[echo] Creating a new stateless session bean component with an action method
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
[copy] Copying 1 file to C:\Projects\helloworld\view
[echo] Type 'seam restart' and go to http://localhost:8080/helloworld/ping.seam
BUILD SUCCESSFUL
Total time: 13 seconds
C:\Projects\jboss-seam>Vue que nous avons ajouté un nouveau composant Seam, nous devons redémarrer le déploiement de la structure du dossier. Vous pouvez faire cela en entrant seam restart, en en execution la cible restart qui se trouve dans le fichier build.xml avec Eclipse. Une autre façon de forcer un redémarrage est d’éditer le fichier resources/META-INF/application.xml dans Eclipse. Notez que vous navez pas à redémarrer JBoss à chaque vois que vous modifier votre application.
Maintenant allez à http://localhost:8080/helloworld/ping.seam et cliquez sur le bouton. Vous pouvez voir le code ci-dessous en action en regardant dans le dossier src. Placez un point d’arrêt dans la méthode ping() et cliquez sur le bouton de nouveau.
Finalement, localisez le fichier PingTest.xml dans le package de test et lancez les tests d’intégration en utilisant le plugin testNG pour Eclipse. Autre alternative, lancez les tests en utilisant seam test ou via la cible du fichier généré test.
L’étape suivante est de créer un formulaire Entrez :
seam new-form
C:\Projects\jboss-seam>seam new-form
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml
validate-workspace:
validate-project:
action-input:
[input] Enter the Seam component name
hello
[input] Enter the local interface name [Hello]
[input] Enter the bean class name [HelloBean]
[input] Enter the action method name [hello]
[input] Enter the page name [hello]
setup-filters:
new-form:
[echo] Creating a new stateful session bean component with an action method
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[copy] Copying 1 file to C:\Projects\hello\view
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[echo] Type 'seam restart' and go to http://localhost:8080/hello/hello.seam
BUILD SUCCESSFUL
Total time: 5 seconds
C:\Projects\jboss-seam>Relancer l’application encore et allez sur http://localhost:8080/helloworld/hello.seam. Ensuite, jetez un coup d’œil sur le code généré. Lancez le test. Essayez d’ajouter de nouveau champs dans le formulaire et dans le composant Seam (rappelez vous de redémarrer le deploiement chaque fois que vous modifiez le code Java).
Manuellement créez plusieurs tables dans votre base de données. (Si vous devez basculer vers une autre base de données, relancez juste encore une fois seam setup.) Maintenant entrez :
seam generate-entities
Relancez le déploiement et allez sur http://localhost:8080/helloworld. Vous pouvez naviguer dans la base de données, éditer les objets existants et créez de nouveaux objets. Si vous allez jeter un coup d’œil au code généré, vous être probablement émerveillé par sa simplicité ! Seam a été prévu pour que le code d’accès aux données soit simple à écrire à la main, même pour les gens qui ne veulent pas tricher en utilisant seam-gen.
Placez votre existant, les classes entitées valides dans src/main. Ensuite entrez
seam generate-ui
Redémarrez le déploiement, et allez sur http://localhost:8080/helloworld.
Au final, nous voulons être capable de déployer l'application en utilisant l'empaquetage standard de Java EE. En premier, vous avons besoin de retirer le dossier fabriqué en exécutant seam unexplode. Pour déployer le EAR, nous pouvons entrer seam deploy à l'invite de commande, ou exécuter la cible deploy du script généré de construction du projet. Vous pouvez le désinstaller en utilisant seam undeploy ou la cible undeploy.
Par défaut, l'application sera déployée avec le dev profile. Le EAR inclura les fichiers persistence-dev.xml et import-dev.sql, et le fichier myproject-dev-ds.xml sera déployé. Vous pouvez modifier le profil et utilisez le prod profile, en entrant
seam -Dprofile=prod deploy
Vous pouvez même définir de nouveaux profils de déploiement pour votre application. Ajoutez juste les fichier dénommés de manière appropriés à votre projet —par exemple, persistence-staging.xml, import-staging.sql et myproject-staging-ds.xml—et sélectionnez le nom du profil en utilisant -Dprofile=staging.
Quand vous déployez votre application Seam dans un dossier complet, vous allez avoir un peu d'indications pour le déploiement incrémental à chaud au moment du développement. Vous avez besoin d'activer le mode debug à la fois dans Seam et Facelets, en ajoutant cette ligne à components.xml:
<core:init debug="true"
>
Maintenant, les fichiers suivants devraient être redéployés sans nécéssiter un rédémarrage complet de l'application web:
toutes les pages facelets
et tout fichier pages.xml
Mais si nous voulons changer le code Java, nous continuons à avoir besoin de réaliser un redémarrage complet de l’application.( Dans JBoss, cela peut être accompli en changeant le descripteur de déploiement de plus haut niveau application.xml pour un déploiement d'un EAR ou web.xml pour un déploiement d'un WAR.)
Mais si vous voulez réellement un cycle rapide d'édition/compilation/test, Seam supporte un redéploiement incrémental des composants JavaBeans. Pour utiliser cette fonctionnalité, vous devez déployer les composants JavaBeans dans le dossier WEB-INF/dev, donc il vont être rechargé par un chargeur de classes spécialisé de Seam au lieu du chargeur de classes WAR ou EAR.
Vous devez être au courrant sur les limitations suivantes:
les composants doivent être des composants JavaBeans, ils ne peuvent pas être des beans EJB3 (nous travaillons pour régler cette limitation)
les entités ne peuvent pas être déployées à chaud
les composants déployés via components.xml ne devraient pas être déployées à chaud
les composants déployables à chaud ne seront pas visibles dans les classes déployées hors de WEB-INF/dev
e mode de debug de Seam doit être activé et jboss-seam-debug.jar doit être dans WEB-INF/lib
Vous devez avoir le filtre de Seam installé dans web.xml
Vous pouvez avoir des erreurs si le systèm est placé en chargement et débogage est actif.
Si vous créé un projet WAR en utilisant seam-gen, le déploiement incrémental à chaud est disponible automatiquement pour les classes placées dans le dossier source src/hot. Mais, sean-gen ne permet le support du déploiement incrémental à chaud pour les projets EAR.
Seam 2 a été développé pour JavaServer Faces 1.2. Avec l’utilisation de JBoss AS, nous vous recommandons d’utiliser JBoss 4.2 ou JBoss 5.0 les deux sont liés avec l'implémentation de référence JSF 1.2. Malgré tout, il est toujours possible d’utiliser Seam 2 sur une plateforme JBoss 4.0. il y a deux étapes de bases pour faire cela : installer une version de JBoss 4.0 avec EJB3 activé et remplacer MyFaces par l’implémentation de référence JSF 1.2. Une fois que ces étapes sont faites, l’application Seam 2 peut être déployé sur JBoss 4.0.
JBoss 4.0 ne propose pas une configuration par défaut compatible avec Seam. Pour exécuter Seam, vous devez installer JBoss 4.0.5 en utilisant l’installateur JEMS 1.2 avec le profil ejv3 sélectionné. Seam ne va pas s’exécuter avec une installation qui n’inclu pas le support EJB3. L’installateur JEMS peut être téléchargé depuis http://www.jboss.org/jbossas/downloads.
La configuration de JBoss 4.0 peut être trouvée dans server/default/deploy/jbossweb-tomcat55.sar. Vous allez avoir besoin d'effacer myfaces-api.jar et myfaces-impl.jar du dossier jsf-libs. Ensuite, vous allez devoir copier jsf-api.jar, jsf-impl.jar, el-api.jar, et el-ri.jar dans ce même dossier. Les JARs de JSF peuvent être trouvés dans le dossier lib de Seam. Les JARs EL peuvent être obtenue depuis la version 1.2 de Seam.
Vous allez aussi devoir éditer le fichier conf/web.xml, en remplaçantmyfaces-impl.jar par jsf-impl.jar.
JBoss Toolsest une collection de plugins Eclipse. JBoss Tools est un projet de d'assistant de création de Seam, Content Assist pour Unified Expression Language (EL) à la fois en facelets et en code Java, un editeur graphique pour le jPDL, un editeur graphique pour les fichiers de configuration de Seam, le support pour l'exécution des tests d'intégration de Seam depuis Eclipse, et bien plus.
Pour faire simple, si vous ête un utilisateur d'Eclipse, alors vous allez vouloirs JBoss Tools!
JBoss Tools, avec seam-gen, fonctionne mieux avec le JBoss AS, mais si c'est pas possible avec quelques trucs d'avoir votre application qui s'exécute sur les autres seveurs d'applications. Le changement est surtout pour ce qui est décrit plus loin pour seam-gen dans ce manueld e référence.
Vérifiez que vous avez JDK 5, JBoss AS 4.2 ou 5.0, Eclipse 3.3, le JBoss Tools plugins (à minima Seam Tools, le Visual Page Editor, jBPM Tools et JBoss AS Tools) et le plugin TestNG pour Eclipse correctement installé avant de commencer.
Merci de consulter la page officielle JBoss Tools installation pour la plus rapide façon d'avoir l'installation de JBoss Tools dans Eclipse. Vous pouvez aussi consulter la page Installing JBoss Tools sur le wiki de la communauté JBoss pour les détails les plus extrèmes et une suite d'approches alternatives.
Démarrez Eclipse et sélectionnez la perspective Seam.
Allez dans File -> New -> Seam Web Project.

En premier lieu, entrez un nom pour votre nouveau projet. Pour ce tutoriel, nous allons utiliserhelloworld .
Maintenant, vous devez indiquer à JBoss Tools où est JBoss AS. Dans cet exemple, nous utilisons JBoss AS, pensez que vous pouvez certainement utiliser JBoss AS 5.0 tout aussi bien. La sélection de JBoss AS est un processus en deux étapes. En premier vous avez besoin de le définir à l'exécution. Ensuite, vous allez choisir JBoss AS, dasn ce cas:

Entrez le nom à l'exécution, et localisez le sur votre disque dur:

Ensuite, vous devez définir un serveur pour que JBoss Tools puisse y déployer le projet. Soyez sur de sélectionner encore une fois JBoss AS et aussi à l'exéuction vous devez juste définir:

Sur l'écran suivant donnez au serveur un nom, et appuyez sur Finish:

Soyez sur qu'à l'exécution et que le server que vous avez juste créez sont sélectionné, sélectionnez Dynamic Web Project with Seam 2.0 (technology preview) et appuyez sur Next:

Les 3 écrans suivants vous permettent de personnalisé votre nouveau projet, mais pour nous les réglages par défaut seront très bien. Donc appuyez juste sur Next tant que vous n'avez pas atteind l'écran final.
La première étape ici est de dire à JBoss Tools où se trouve le Seam téléchargé que vous voulez utiliser. Add un nouveau Seam Runtime - soyez sur d'indiquer le nom , et sélectionez 2.0 comme version:

La choix le plus important que vous devez faire est entre un déploiement EAR ou un déploiement WAR de votre projet. Les projets EAR supportent les EJB 3.0 et nécéssitent Java EE5. Les projets WAR ne supportent pas les EJB3.0 mais peuvent être déployés dans un environement J2EE. L'empaquetage d'un WAR est aussi plus simple à comprendre. Si vous installez un serveur d'application prêt pour EJB3, choissisez EAR. Sinon choississez WAR. Nous allons supposez que vous avez choisi un déploiement WAR pour le reste du tutoriel, mais vous pouvez suivre exactement les mêmes étapes pour un déploiement de EAR.
Ensuite, sélectionnez votre type de base de données. Nous allons supposé que vous installé MySQL avec un schéma de base de données existant Vous allez devir indiquer à JBoss Tools où est la base de données, selectionnez MySQL comme base de données, et créez un nouveau profil de connection. Sélectionnez Generic JDBC Connection:

Indiquez lui un nom:

JBoss Tolls n'est pas fourni avec des pilotes pour les bases de données, vous devez dire à JBoss Tools où se trouve le pilote MySQL JDBC. Indiquez lui où se trouve le pilote en cliquant sur ....
Localisez le MySQL 5, et appuyez surAdd...:

Choississez le modèle de MySQL JDBC Driver:

Localisez le jar sur votre ordinateur en choissisant Edit Jar/Zip:

Indiquez votre nom d'utilisateur et votre mode passe à utiliser pour se connecter, et si c'est correcte, appuyez sur Ok.
AU final, choississez le pilote nouvellement créer:

Si vous travaillez avec un modèle de données existant, soyez sur de dire à JBoss Tools que des tables existent déjà dans la base de données.
Indiquez le nom d'utilisateur et le mot de passe utilisé pour se connecter, testez la connection en utilisant le bouton Test Connection , et si cela fonctionne, appuyez sur Finish:
Au final, vérifiez les noms des paquet de vos beans générés et si vous en êtes content, cliquez sur Finish:

JBoss dispose d'un support sophistiqué pour le re-déploiement à chaud des EARs et des WARs. Malheureusement, à cause de bugs dans la JVM, le redéploiement d'un EAR—ce qui est common pendant le développement—peut éventuellement déclencher un épuisement de l'espace perm gen. Pour cette raison, nous recommendons d'exécuter JBoss dans une JVM avec un espace perm gen important pendant la phase de développement. Nous sudgerons les valeurs suivantes:
-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512
Si vous navez pas autant de mémoire disponible, les valeurs suivantes sont notre recommendation minimale:
-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256
Localisez le serveur dans le JBoss Server View, clic droit sur le server et selectionnez Edit Launch Configuration:

Ensuite, modifier les arguments de la VM:

SI vous ne voulez pas vous inquietes avec tout ce bidule maintenant, vous n'avez pas à le faire—revennez plus tard, quand vous aurez votre premier OutOfMemoryException.
Pour démarrer JBoss, et deployez votre projet, clic droit sipplement sur le server que vous avez créés et clic sur Start, (ou sur Debug pour démarrer en mode debogage):

Ne soyez pas appeuré par les documents de configuration en XML qui sont générés dans le dossier du projet. Ils sont pour la plus par des trucs de Java EE standards, le genre de truc que vous avez besoin de créer une fois et ensuite de ne jamais plus le regarder, et ils sont dans 90% des cas les même entre tous les projets Seam.
SI vous utilisez un serveur d'application web de style action traditionnel, vous vous inquiétez surement de comment créer une simple page web avec des méthode d'action sans état en Java.
En premier, sélectonnez New -> Seam Action:

Maintenant, entrez le nom du composant de Seam. JBoss Tools sélectionne des valeurs par défauts judicieuses pour les autres champs:

Au final, appuyez sur Finish.
Maintenant allez sur http://localhost:8080/helloworld/ping.seam et cliquez sur le bouton. Vous pouvez voir le code derrière cette action en regardant dans le dossier src du projet. Metez un point d'arrêt dans la méthode ping() et cliquez de nouveau sur le bouton.
Au final, ouvrez le projet helloworld-test, localisez la classe PingTest, clic droit sur elle et choisissez Run As -> TestNG Test:

La première étape pour créer un formulaire. Sélectionnez New -> Seam Form:

Maintenant, entrez le nom du composant de Seam. JBoss Tools sélectionne des valeurs par défauts judicieuses pour les autres champs:

Allez sur http://localhost:8080/helloworld/hello.seam. Ensuite, regardez comment est le code généré. Exécutez le test. Essayez d'ajouter de nouveaux champs au formulaire et au composant de Seam (notez, vous n'avez pas à redémarrer le serveur d'application à chaque fois que vous modifier le code dans src/action car Seam recharge à chaud le composant pour vous Section 3.6, « Seam et le déploiement incrémental à chaud avec JBoss Tools »).
Manuellement, créez quelques tables dans votre base de données. (Si vous avez besoin de basculer vers une base de données différente, créez une nouveau projet et sélectionnez la base de données correcte. Ensuite, sélectionnez New -> Seam Generate Entities:

JBoss Tools vous offre aussi l'option de réaliser une ingénieurie inverse des entitées, composants et des vues depuis votre schéma de base de données ou depuis les composants et les vues des entitées JPA existantes. Nous allonrs réaliser une ingénieurie inverse depuis une abase de données.
Redémarrer le déploiement:

Ensuite, allez vers http://localhost:8080/helloworld. Vous pouvez naviguer dans la base de données, editer des objets existants, et créez de nouveaux objets. Si vous regardez dans le code généré, vous allez probablement être impréssionné par sa simplicité! Seam a été conçu pour que le code d'accès aux données soit simple à écrire à la main, même par des gens qui ne veulent pas tricher en utilisant l'ingénieurie inverse.
JBoss Tools permet le déploiement incrémental à chaud de :
toute page facelets
tout fichier pages.xml
au déballage de la boite.
Mais si nous voulons modifier n'importe quel code Java, nous avons toujours besoin de faire un redémarrage complet de l'application en faisant un Full Publish.
Mais si vous voulez un cycle rapide d'édition/compilation/test, Seam permet de redéploiement incrémental des composants JavaBean. Pour vous permetre d'utilisez cette fonctionnalité, vous devez déploiyer les composants JavaBean dans le dossierWEB-INF/dev , ainsi ils sont recharger par le classloader spécial de Seam, au lieu du classloader WAR ou EAR.
Vous devez être au courrant des limitations suivantes:
les composants doivent être des composants JavaBean, il ne peuvent pas être des beans EJB3 (nous travaillons pour corriger cette limitation)
les entités ne peuvent jamais être déploiyées à chaud.
les composants déployés via components.xml ne peuvent pas être rechargés à chaud.
les composants rechargés à chaud ne seront pas visible pour toute les classes déployés à l'extérieur de WEB-INF/dev
Le mode de débogage de Seam doit être actif et jboss-seam-debug.jar doit être dans WEB-INF/lib
Vous devez avoir le filtre Seam installé dans web.xml
Vous pouvez voir des erreurs si le système est situé pendant le chargement ou le débogage est activé.
Si vous créez un projet WAR en utilisant JBoss Tools, le déploiement incrémental à chaud est disponible immédiatement pour les classes se trouvant dans le dossier des sources src/action. Cependant, JBoss Tools ne fourni pas de déploiement incrémental à chaud pour les projets EAR.
Mutable et @ReadOnlyLes deux concepts centraux dans Seam sont la notion de contexte et la notion de composant. Les composants sont des objets avec état, habituellement des EJBs, et une instance d’un composant est associé avec un contexte, et défini par un nom dans ce contexte. La Bijection fourni un mécanisme pour nommer les composants par un alias interne (les variables d’instances) dans les noms du contexte, autorisant des arbres de composants a être dynamiquement assemblés et réassemblés par Seam.
Commençons par la description des contextes existant dans Seam.
Les contextes de Seam sont créés et détruit par le serveur de squelette d’application (framework) via des appels explicite à l’API Java. Les contextes sont habituellement implicites. Dans certains cas, malgré tout, les contextes sont spécifiés via des annotations.
Les contextes de base de Seam
Le contexte sans état
Le contexte évènementiel (autrement dir, de requête)
Le contexte de Page
Le contexte conversatinnel
Le contexte de Session
Le contexte de processus métier
Le contexte d'Application
Nous pouvons reconnaître certain de ces contexte de part les spécifications des servlets autour des servlets. Malgré cela, deux d’entre elles devraient être nouvelle pour vous : contexte conversationnel, et contexte de processus métier. Une des raisons pour que le gestionnaire d’état qui est dans les applications web, soit si fragile et un nid à erreurs c’est que trois des contextes livrés (requête, session et application) ne sont pas particulièrement utile du point de vue de la logique métier. Une session d’authentification d’un utilisateur, par exemple, est construite presque arbitrairement dans une application de flot de travail. Ainsi, les composants Seam sont bornés à la conversation ou aux contextes des processus métier, ainsi ils sont les contextes qui ont le plus de sens en termes d’application.
Ayons un regard sur chacun de ces contextes l'un après l'autre.
Les composants qui sont réellement sans état (les beans sessions sans état, principalement) vivent toujours dans un contexte sans état (ce qui est basiquement une absence de contexte). Les composants sans état ne sont pas vraiment intéressant et sont dénigrés pour ne pas être orienté objet. Malgré cela, ils sont importants, très souvent utile et aussi une part important de toute application Seam.
Le contexte d’évènement est le contexte avec état le plus "réduit", et est la généralisation de la notion du contexte d’une requête web pour couvrir les autres types d’évènements. Malgré cela, le contexte d’évènement associé avec un cycle de vue dans une requête JSF est l’exemple le plus important d’un contexte d’évènement, et le seul qui va fonctionner le plus souvent. Les composants associés avec un contexte d’évènement sont détruit à la fin de la requête, mais leurs état est disponible et bien définie pour au moins le cycle de vie de la requête.
Quand vous invoquez un composant Seam via RMI ou Seam Remoting, le contexte d’évènement est créé et détruit juste pour l’invocation.
Le contexte de page vous permet d’associer un état avec une instance particulière d’un page à rendre. Vous pouvez initialiser un état dans votre écouteur d’évènement ou au moment ou la page est en train d'être rendue et ensuite avoir un accès sur elle depuis tout évènement qui a comme origine cette page. Ceci est spécialement utile pour la fonctionnalité comme une liste cliquable quand la liste est controlée par une donnée changeante côté serveur. L’état est actuellement sérialisé vers le client, donc cette construction est extrêmement robuste dans le respect d’opération multi fenêtre et du bouton précédent.
Le contexte de conversation est un vraiment un concept central dans Seam. Une conversation est une unité de travail du point de vue de l’utilisateur. Elle peut s’étendre sur plusieurs interactions de l’utilisateur, plusieurs requêtes, et plusieurs transactions de base de données. Mais pour l’utilisateur, une conversation résout un seul problème. Par exemple, "réserver un hôtel", "valider un contact", "créer un bon de commande" sont toutes des conversations. Vous devriez apprécier de penser en termes d’implémentation de la conversation comme un seul "cas d’utilisation" ou une "exemple d’utilisation" mais la relation n’est pas nécessairement aussi directe.
Une conversation retient un état associé avec "ce que l’utilisateur est en train de faire maintenant dans cette fenêtre". Un simple utilisateur peut avoir de multiples conversations en cours à tout moment dans le temps, habituellement dans de multiples fenêtres. Le contexte de conversation nous permet de nous assurer que cet état provenant de différentes conversations ne peut se caramboler et causer des bugs.
Il est possible que cela vous prennes du temps de maitriser la façon de penser l’application en termes de conversations. Mais une fois que vous avez l’habitude de le faire, nous pensons que vous allez adorer cette notion, et que vous ne serez plus capable de ne jamais plus penser en termes de conversations!
Quelques conversations perdurent dans une simple requête. Les conversations qui s’étendent sur plusieurs requêtes doivent être bien démarquées en utilisant les annotations fournies par Seam.
Quelques conversations sont aussi des tâches. une tache est une conversation ce qui a un sens en termes de processus métier à exécution longue et a le potentiel de déclencher une transition d’état pour un processus métier quand elle réussit à se terminer. Seam fournis une série d’annotations spéciales pourdifférencie cette tâche.
Les conversations doivent être reliées, avec une conversation prenant sa place "à l’intérieur" d’une plus grande conversation. Ceci est une fonctionnalité avancée.
Habituellement, l’état de la conversation est actuellement maintenu par Seam dans la session servlet entre les requêtes. Seam implémente un delais de péremption configurable, détruisant automatiquement les conversations inactives et ainsi s’assurant que l’état maintenu par une session de connexion d’un seul utilisateur ne grandi hors limitation si l’utilisateur abandonne les conversations.
Seam sérialise les requêtes de processus concurent qui ont lieu dans le même contexte de conversation à exécution longue, dans le même processus.
Autre alternative, Seam peut être configuré pour converser l’état conversationnel dans le navigateur du client.
Un contexte de session conserve un état associé avec une session de connexion utilisateur. Pour les quelques cas où il est utilise de partager l’état entre plusieurs conversation, nous libèrons habituellement l’utilisation du contexte de session pour stocker les composants autre que les informations globales à propos de la connection de l’utilisateur..
Dans l’environnement portail JSR168, le contexte de session représente la session portail.
Le contexte de processus métier stocke l’état associé au processus métier à exécution longue. Cet état est géré et est rendu persistant par le moteur BMP (JBoss JBPM). Le processus métier s’étend sur de multiples interactions avec de multiples utilisateurs, donc cet état est partagé entre plusieurs utilisateurs mais de manière parfaitement définie. La tache courante détermine l’instance du processus métier courante et le cycle de vie du processus métier est définie en externe en utilisant un language de définition de processus, donc il n’y a pas d’annotatation spéciale pour la séparation du processus métier.
Le contexte d’application est le contexte de servlet familier de la spécification servlet. Le contexte d’application est principalement utilisé pour stocker l’information statique comme des données de configuration, des données de référence ou des méta-modèles. Par exemple, Seam stocke sa propre configuration et son méta-modèle dans le contexte d’application.
Un contexte défini un espace de nom, un ensemble de variables de contexte. Ceci fonctionne à peu prêt comme des attributs de session ou de requête dans la spécification servlet. Vous devez faire correspondre n’importe quelle valeur que vous voulez à une variable de contexte, mais habituellement nous faisons correspondre les instances de composant Seam à des variables de contexte.
Donc, dans un contexte, une instance de composant est identifiée par le nom de la variable de contexte (ceci est habituellement, mais pas toujours, la même chose que le nom du composant). Vous pouvez par programmation accéder à l’instance du composant nommée dans une étendue particulière via la classe Contexts, qui fournis un accès aux différentes instances reliées par thread de l’interace Context:
User user = (User) Contexts.getSessionContext().get("user");
Vous pouvez aussi définir ou changer la valeur associé avec son nom :
Contexts.getSessionContext().set("user", user);
Habituellement, malgré tout, nous obtenons les composants depuis le contexte via une injection, et mettons les instances de composants dans le contexte via une extrusion.
Parfois, comme ci-dessus, les instances des composants sont obtenus depuis une étendue connus particulière. D’autre fois, tous les étendues avec états sont parcourues, dans un ordre de priorité. Cet ordre est indiqué ci-dessous:
Contexte d'évènement
Le contexte de Page
Le contexte conversatinnel
Le contexte de Session
Le contexte de processus métier
Le contexte d'Application
Vous pouvez réaliser une recherche par priorité en appelantContexts.lookupInStatefulContexts(). Malgré cela, si vous accédez a un composant par son nom depuis une page JSF, une recherche par ordre de priorité est faite.
Ni les spécifications servlet ni EJB ne définissent la moindre indications pour l’organisation des requêtes originaire du même client. Le container de servlet simplement laisse tous les threards s’exécuter de manière concurrente et laisse la sureté des threads se renforcer dans le code applicatif. Le container EJB autorise les composants sans état à être accédés de manière concurrente et déclenche des exceptions si de multiples threads accèdent à un bean session avec état.
Cette fonctionnalité devrait être ok dans les applications web au style ancien qui sont basées autour de requêtes bien dimensionnées et synchrones. Mais les applications modernes qui font une utilisation lourde de nombreuses requêtes bien dimensionnées et asynchrones (AJAX), de manière concurrente est un état de fait doivent être supporté par le modèle de programmation. Seam fourni une couche de gestion concurrente dans son modèle de contexte.
La session Seam et les contextes d’application sont multi threadées. Seam va nous autoriser des requêtes concurrentes dans un contexte pouvant être exécuté en concurrence. Les contextes page et d'évènements sont par nature un simple thread. Le contexte de processus métier est strictement parlant multi threadé, mais en pratique la concurrence est suffisamment rare pour que ce fait puisse être assez peu controlé la plus part du temps. Finalement, Seam renforce le modèle d'un seul thread par conversation par processus pour le contexte de conversation en sérialisant les requêtes concurrentes dans un même contexte de conversation d’exécution longue.
Depuis que le contexte de session est multi-threadé et souvent il contient un état volatile, les composants de l’étendue session sont toujours protégés par Seam d’accès concurrents aussi longtemps que les intercepteurs de Seam ne sont pas désactivés par ce composant. Si les intercepteurs sont désactivés, alors tout accès sécurisé au thread doit être implémenté par le concurent lui-même. Seam sérialise les requêtes dans les beans de session l’étendue de session et dans les JavaBeans par défaut (et détecte et brise toute étreinte mortele qui apparait). Ceci n’est pas la fonctionnalité par défaut pour les composants de l’étendue application, car les composants de l’étendue application ne conservent pas habituellement un état volatile et tout cela à cause de la synchronisation au niveau global qui est extrèmement couteuse. Malgré tout, vous pouvez forcer le modèle de threads en sérialisation sur n’imoporte quel bean session ou composant JavaBean en ajoutant l’annotation @Synchronized.
Le modèle concurrent signifie que les clients AJAX peuvent de manière sure utiliser la session volatile et un état conversationnel, sans le besoin d’un travail particulier de la part du développeur.
Les composants de Seam sont ds POJOs (Plain old java Objects). En particulier, s'ils sont des JavaBeans ou bean entreprise EJB3.0. Tant que Sean ne requière pas que ces composants soient des EJBs et ainsi qu’il puisse même être utilisés sans un container compatible EJB3, Seam a été conçu avec EJB3.0 comme état d’esprit et il inclus une intégration profonde avec EJB3.0. Seam supporte les types de composants suivants.
Beans de session sans état EJB 3.0
Beans de session avec état EJB 3.0
Beans entité EJB 3.0 (autrement dit, des classes d'entité JPA)
JavaBeans
Les beans conducteur de message EJB 3.0
Les beans de Spring (see Chapitre 27, Spring Framework integration)
Les composants de bean session sans état ne sont pas capables de retenir un état au travers de multiples invocations. Ainsi, ils travaillent habituellement en opérant au dessus de l’état d’autres composants dans les différents contextes de Seam. Ils peuvent être utilisés comme écouteur d’action JSF mais ne peuvent fournir une propriété à des composants JSF pour l’affichage.
Les beans session sans états existent toujours dans un contexte sans état.
Les beans de session sans état peuvent être accédés de manière concurente comme une nouvelle instance qui est utilisé pour chaque requête. Assigner l'instance à une requête est de la responsabilité du containeur EJB3 (les instances normallement seront alloués depuis un groupement réutilisable ce qui signifie que vous pourriez trouver des variables d'instance contenant des données d'un utilisation précédente du bean).
Les beans session sans états sont les moins intéressants des types de composant Seam.
Les beans session sans états peuvent être instanciés en utilisant Component.getInstance() ou @In(create=true). Ils ne devraient pas être instanciés via l'observateur JNDI ou par l'opérateur new.
Les composants beans de session avec état sont capables de retenir un état pas seulement au travers de multiples invocations du bean mais aussi au travers de multiples requêtes. L’état de l’application ne doit pas s’attarder dans la base de données pour être éventuellement retenu par des beans de sessions avec état. Ceci est une différence principale entre Seam et d’autres serveurs d’application web. Plutôt que de coller de l’information à propos de la conversation courante directement dans le HttpSession, vous devriez conserver ces variables d’instance d’un bean session avec état qui va être relié au contexte de conversation. Ceci autorise Seam à gérer le cycle de vie de cet état pour vous et vous assure qu’il n’y aura pas de collision entre les états dans différentes conversations concurrentes.
Les beans de session avec état sont souvent utilisés comme écouteur d’action JSF, et comme beans de soutient qui fournissent des propriétés aux composants JSF pour l’affichage ou la soumissions de formulaire.
Par défaut, les beans de session avec état sont reliés au contexte de conversation. Ils ne peuvent jamais reliés aux contextes de page ou sans état.
Les requêtes concurrentes pour les sessions avec état dans l’étendue de session sont toujours sérialisées par Seam si les intercepteurs de Seam ne sont pas désactivés par le bean.
Les beans session avec états peuvent être instanciés en utilisant Component.getInstance() ou @In(create=true). Ils ne devraient pas être instanciés via l'observateur JNDI ou par l'opérateur new.
Les beans entité peuvent être reliés à une variable de contexte et fonctionne comme un composant de Seam. Avec des entitées ayant un identité persistante en plus de leur identité contextuelle, les instance d’entité sont habituellement reliées explicitement par le code Java, plutôt qu’être instanciées implicitement par Seam.
Les composants bean entité ne supportent pas la bijection ou la démarcation du contexte. Aucune invocation d’un bean entité ne déclenchera la validation.
Les beans entité ne sont habituellement pas utilisés comme écouteur d’action JSF, mais ont aussi la fonction d’un bean de soutient qui fourni des propriétés aux composants JSF pour l’affichage et la soumission de formulaire. En particulier, il est commun d’utiliser une entité comme un bean de soutient, avec un écouteur d’action de bean de session sans état pour implémenter les fonctionnalités de type création/mise à jour/effacement.
Par défaut, les beans entités sont reliés au contexte de conversation. Ils ne devraient jamais être reliés au contexte sans état.
Notez que dans un environement en cluster il est parfois moins efficace de relier un bean entité directement à une conversation ou à une variable de contexte de Seam d'étendue de session que de maintenir une reference au bean entité dans une bean de session avec état. C'est pour cette raison que toutes les applications Seam ne définissent pas des beans entité a être des composants Seam.
Les composant beans entité de Seam peuvent être instanciés en utilisant Component.getInstance() ou @In(create=true) ou directement en utilisant l'opérateur new.
Les Javabeans devraient être utilisé tout comme un bean de session avec ou sans état. Magrè tout, ils ne fournissent pas les fonctionnalités de bean de session (démarcation de transaction déclarative, sécurité déclarative, réplication d’état en cluster efficace, peristance EJB3.0, méthode hors-delais,etc).
Dans un chapitre suivant, nous vous montrerons comment utiliser Seam et Hibernate sans un container EJB. Dans ce cas d’utilisation, les composants sont des JavaBeans au lieu de beans de session. Notez, malgré cela, dans beaucoup de serveurs d’application il est parfois moins efficace de rassembler les conversations ou les composants JavaBeans de Seam d’étendue session que de rassembler les composants bean de session avec état.
Par défaut, les JavaBeans sont reliés au contexte d’évènement.
Les requêtes concurrentes dans les Javabeans d’étendue session sont toujours sérialisées par Seam.
Les composant JavaBean de Seam peuvent être instanciés en utilisant Component.getInstance() ou @In(create=true). Ils ne devraient jamais être instancié en utilisant l'opérateur new.
Les beans conducteur-de-message peuvent fonctionner comme un composant Seam. Malgrè cela, les beans conducteur-de-message sont appeler un peu différemment des autres composants de Seam- au lieu de les invoquer via une variable de contexte, ils écoutent les messages envoyés vers une file d’attente JMS ou un topic.
Les beans conducteur-de-message peuvent ne pas être reliés à un contexte de Seam. Ils n’ont pas non plus d’accès à la session ou l’état de conversation de leur "appelant". Magrè cela, ils peuvent supporter la bijection et quelques autres fonctionnalités de Seam.
Les beans conducteur-de-message ne sont jamais instanciés par l'application. Ils sont instanciés par le containeur EJB quand un message est reçu.
Pour réussir à faire sont tour de magie (bijection, démarcation de contexte, validation, etc.), Seam doit intercepter les invocations de composant. Pour les JavaBeans, Seam est un contrôleur total de l’instanciation du composant, et aucune configuration spéciale n’est requise. Pour les beans entité, l’interception n’est pas requise à moins d'une bijection et la démarcation de contexte ne sont pas défini. Pour les beans session, nous devons enregistrer un intercepteur EJB pour le composant bean de session. Nous pourrions utiliser une annotation, comme ci-dessous :
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
Mais la meilleure façon de faire est de définir un intercepteur dans ejb-jar.xml.
<interceptors>
<interceptor>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name
>*</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor
>
Tous les composants de Seam au besoin d’un nom. Nous pouvons assigner un nom à un composant en utilisant l’annotation @Name:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Ce nom est un nom de composant seam et n’est pas lié a un tout autre nom défini par la spécification EJB. Malgré tout, les noms de composant Seam fonctionnent tous comme les noms des beans de gestion JSF et vous pouvez penser que les deux concepts sont identiques.
@Name n'est pas la seule façon de définir un nom de composant, mais nous avons toujours besoin de spécifier ce nom quelque part. Si nous ne le faisons pas, alors aucune autre annotation de Seam ne va fonctionner.
A chaque fois que Seam instancie un composant, il relie cette nouvelle instance à une variable dans l'étendue configuré pour ce composant qui correspondant au nom du composant. Cette fonctionnalité est identique à comment JSF fait travailler les beans qu'il gère, exception que Seam vous permet de configurer cette relation en utilisant des annotation au lieu de XMM. Par exemple, le connecté courant dans User peut être relié à une variable de contexte de session currentUser tant que le User dont il est sujet à quelques fonctionnalités administratives qui peuvent être reliées à la variable de contexte de conversation user. Soyez prudent, pensez-y, parce qu'au traver d'une affectation programmée, il est possible de surcharger une variable de contexte qui a une référence sur un composant de Seam, avec de potentiel problèmes de confusion.
Pour de plus grande applications, et pour les composants de Seam livrés, les noms qualifiés sont souvent utilisés.
@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}
Nous devrions utiliser le nom de composant qualifié à la fois dans le code Java et dans le langage d’expression de JSF:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Comme c’est perturbant, Seam fourni aussi une manière pour renommer un nom qualifié en nom simple. Ajouter une ligne comme celle-ci dans le fichier components.xml:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
Tous les composants Seam livrés ont un nom qualifié, mais la plus part d’entre eux sont renommés vers un nom simple grâce à la fonctionnalité d'importation de l'espace de nommage de Seam. Le fichier components.xml inclus dans le JAR de Seam définie les espaces de nommage suivant.
<components xmlns="http://jboss.com/products/seam/components">
<import>org.jboss.seam.core</import>
<import>org.jboss.seam.cache</import>
<import>org.jboss.seam.transaction</import>
<import>org.jboss.seam.framework</import>
<import>org.jboss.seam.web</import>
<import>org.jboss.seam.faces</import>
<import>org.jboss.seam.international</import>
<import>org.jboss.seam.theme</import>
<import>org.jboss.seam.pageflow</import>
<import>org.jboss.seam.bpm</import>
<import>org.jboss.seam.jms</import>
<import>org.jboss.seam.mail</import>
<import>org.jboss.seam.security</import>
<import>org.jboss.seam.security.management</import>
<import>org.jboss.seam.security.permission</import>
<import>org.jboss.seam.captcha</import>
<import>org.jboss.seam.excel.exporter</import>
<!-- ... --->
</components>
Pour essayer de résoudre un nom non-qualifié, Seam va vérifier chacun de ces espace de nommage, dans l'odre. Vous pouvez inclure des espaces de nommages additionnels dans votre fichier components.xml de votre application pour des espaces de nommages spécifiques à votre application.
Nous pouvons surchargé létendue par défaut (le contexte) d’un composant en utilisation l’annotation @Scope.Ceci nous permets de définir quel contexte une instance de composant est relié à, quand il est instancié par Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType defini une énumération des étendues possibles.
Des classes de composants Seam peuvent être utilisées avec plus d’un rôle dans le système. Par exemple, nous avons souvent une classe Userqui est habituellement utilisée comme un composant d’étendue de session représentant l’utilisateur courant mais elle est aussi utilisée dans les écrans de l’administration de l’utilisateur comme un composant d’étendue de conversation. L’annotation @Role nous permet de définir un rôle avec un nom additionnel, avec une étendue différente — il nous laisse relier la même classe de composant dans des variables de contextes différentes. (Toute iinstancei de composant Seam peut être reliée à de multiples variables de contexte, mais cela nous laisse le faire au niveau classe et d'avoir l’avantage d’une auto instanciation.)
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}
L'annotation @Roles nous permet de spécifier autant de rôles additionnels que nous voulons.
@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}
Tout comme beaucoup de bon serveur d’application, Seam mange sa propre nourriture et il implémente un série d’intercepteurs livrés (voir ci-dessous) et de composants de Seam. Ceci rend plus facile pour les applications d'interagir avec les composants livrés à l’exécution ou même de personnaliser les fonctionnalités de base de Seam en remplaçant les composants livrés avec des implémentations personnalisées. Les composants livrés sont défini dans l’espace de nommage de org.jboss.seam.core et le paquet Java du même nom.
Les composants livrés peuvent être injectés, tout comme des composants Seam, mais ils fournissent aussi des méthodes instance() pratiques statiques:
FacesMessages.instance().add("Welcome back, #{user.name}!");
L'injection de dépendance ou l'inversion de contrôle est maintenant un concept familier pour la plus part des développeurs Java. L’injection de dépendance permet à un composant d'obtenir une référence sur un autre composant en passant par le containeurs qui "injecte" l'autre composant par une méthode assesseur ou par une variable d'instance. Dans toutes les implémentations d’injection de dépendances que nous avons vu, l’injection intervient quand le composant est construit, et la référence ne change pas postérieurement pour le cycle de vie de l’instance du composant. Pour les composants sans état, ceci est raisonnable. Du point de vue d’un client, toutes les instances d’un composant sans état particulier sont interchangeables. Dans un autre côté, Seam accentue l’utilisation de composants avec état. Donc l’injection de dépendance traditionnelle n’est plus une construction vraiment utilisable. Seam introduit la notion de bijection comme une généralisation de l’injection. A la différence de l’injection, la bijection est :
contextuelle - la bijection est utilisé pour assembler les composants avec état depuis plusieurs contextes différents (un composant d’un contexte "évasé" peut même contenir une référence sur un composant d’un contexte "étroit")
bidirectionelle - les valeurs sont injectées depuis les variables de contexte dans des attributs du composant qui a été invoqués et aussi extradé depuis les attributs du composant en retour vers le contexte, autorisant le composant à être invoqué pour manipuler les valeurs des variables contextuelles simplement en définissant ces propres variables d’instances
dynamique - avec une valeur des variables contextuelles changeante au cours du temps et avec des composants Seam qui sont avec état, la bijection a lieu à chaque fois qu’un composant est invoqué
Par essence, la bijection vous permet de renommer une variables de contexte dans une variables d’instance de composant en spécifiant que la valeur de la variable d’instance est injectée, extradée ou les deux. Bien sûr, nous utilisons des annotation pour activer la bijection.
L'annotation @In indique que la valeur devrait être injectée, aussi dans une variable d’instance.
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
ou dans une méthode assesseur :
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
...
}
Par défaut, Seam va donner une priorité de recherche dans tous les contextes en utilisant le nom de la propriété ou de la variable d’instance qui a été injectée. Vous devriez vouloir spécifier le nom de la variable de contexte explicitement, en utilisant, par exemple, @In("currentUser").
Si vous voulez que Seam puisse créer une instance du composant quand il n’y a pas d’instance de composant existante reliée à une variable de contexte nommée, vous pouvez spécifier @In(create=true). Si la valeur est optionnel (elle peut être null) spécifiez @In(required=false).
Pour quelques composants, il peut être répétitif d’avoir à spécifier @In(create=true) partout où ils sont utilisés . Dans ce genre de cas, vous pouvez annoter le composant @AutoCreate, et alors il va toujours être créé, n’important quand, même sans l’utilisation explicite de create=true.
Vous pouvez même injecter la valeur d’une expression :
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
Les valeurs injectées sont désalouées (autrement dit, définie à null) immédiatement après la réalisation de la méthode et sont extraction.
(Il y a beaucoup plus d'information sur le cicle de vie d'un composant et l'injection dans le chapitre suivant).
L'annotation @Out indique qu'un attribue devrait être extradé, tout comme une variable d'instance:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}
ou comme une méthode assesseur:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@Out
public User getUser() {
return user;
}
...
}
Un attribut peut être à la fois injecté et extradé:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}
ou:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
@Out
public User getUser() {
return user;
}
...
}
Le bean de session et les composants Seam de bean entité supportent tous les rappels du cycle de vie EJB3.0 usuel (@PostConstruct, @PreDestroy, etc). Mais Seam étend tous ces rappels aux composant JavaBean. Cependant tant que ces annotation ne sont pas disponible dans un environement J2EE, Seam défini deux composants additionnel pour le rappel dans le cycle de vie, équivalent à @PostConstruct et à @PreDestroy.
La méthode @Create est appelée après que Seam instancie un composant. Les composants ne peuvent définir seulement qu'une seule méthode @Create.
La méthode @Destroy est appelé quand le contexte qui lie le composant de Seam se termine. Les composants ne peuvent définir qu'une seule méthode @Destroy.
De plus, les composants bean avec état doivent définir une méthode sans aucun paramètre annotée @Remove. Cette méthode est appelé par Seam quand le contexte se termine.
Finalement, une annotation liée est l’annotation @Startup qui peut être appliquée à toutes l’application ou un composant d’étendue session. L’annotation @Startup indique à Seam d’instancier le composant immédiatement, quand le contexte commence, au lieu d’attendre avant son premier référencement par un client. Il est possible de controler l’ordre d’instanciation du démarrage des composants en spécifiant @Startup(depends={....}).
L'annotation @Install vous permet de contrôler l’installation conditionnelle du composant qui sont requis dans certains scénarios de déploiement et pas dans d’autres. C'est utile si :
Vous voulez singer quelques composants d’infrastructure dans des tests.
Vous voulez modifier l’implémentation d’un composant dans certains scénarios de déploiement.
Vous voulez installer quelques composants seulement si leurs dépendances sont disponibles (utile pour les auteurs des serveur d’applications).
@Install fonctionne en vous laissant spécifier la précédence et les dépendances.
La précédence d’un composant est un numéro que Seam utilise pour décider quel composant est à installer quand il a plusieurs classes avec le même nom de composant dans le classpath. Seam va choisir le composant avec la précédence la plus haute. Il y a quelques valeurs de précédences prédéfinie (dans l’ordre ascendant) :
BUILT_IN — les composants de précédence la plus faible sont les composants livrés avec Seam.
FRAMEWORK — les composants définis par un serveur d’application tierces peuvent surcharger les composants livrés, mais ils sont surchargés par les composants d’application.
APPLICATION — la précédence par défaut. C'est la valeurappropriée pour la plus part des composants d’application.
DEPLOYMENT — pour les composants d’application qui ont un déploiement spécifique.
MOCK — pour les objets singeant qui sont utilisés en test.
Supposez que nous avons un composant appelé messageSender qui parle à la queue de JMS.
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
Si dans vos tests unitaires, nous n’avons pas de queue de JMS disponible, mais que nous aimerions simuler cette méthode. Nous allons créer un composant le singeant et qui existe dans le classpath quand les tests unitaires sont exécutés mais il n’est jamais déployé avec l’application:
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
La precedence aide Seam à décider quel version à utiliser quand il trouve deux composants dans la classpath.
C’est bien si vous étiez capable de contrôler exactement quelles classes sont dans le classpath. Mais si j’ai écrit un serveur d’application réutilisable avec beaucoup de dépendances, je ne veux pas à avoir à briser ce serveur d’applications au travers de nombreux jars. Je veux d’être capable de décider quel composants sont à installer selon quel autres composants sont déjà installés et selon quelles classes sont disponibles dans le classpath. L’annotation @Install contrôle aussi cette fonctionnalité. Seam utilise ce mécanisme interne pour activer l’installation conditionnelle de beaucoup des composants livrés. Malgré tout, vous n’aurez probablement pas besoin de l’utiliser dans votre application.
Qui n’en n’a pas assez de voir du code aussi touffu que celui-ci ?
private static final Log log = LogFactory.getLog(CreateOrderAction.class);
public Order createOrder(User user, Product product, int quantity) {
if ( log.isDebugEnabled() ) {
log.debug("Creating new order for user: " + user.username() +
" product: " + product.name()
+ " quantity: " + quantity);
}
return new Order(user, product, quantity);
}
Il est difficile d’imaginer comment le code pour un simple message de log peut devenir beaucoup verbeux. Il y a beaucoup plus de lignes de code dédié à l'affichage des traces plus que de la réelle logique métier! Je reste totalement abasourdie que la communauté Java n’a pas encore trouvé mieux depuis 10 ans.
Seam fourni un API d'affichage des traces qui simplifie significativement le code :
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
return new Order(user, product, quantity);
}
Il n’est pas utile si vous déclarez la variable log statique ou non— elle va fonctionner dans tous les cas, à l’exception des composants bean entité qui demande à la variable de log d'être statique.
Notez que nous n’avons pas besoin d’être touffu si ( log.isDebugEnabled() ) conserver, depuis la concaténation des chaines de caractères apparaissent dans la méthode debug().Notez aussi que nous n’avons habituellement pas besoin de spécifier la catégorie de log explicitement, car Seam connaît quel composant au sein du quel il va injecter le Log.
Si User et Product sont des composants Seam disponible dans les contextes courant, il n’y a pas mieux :
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
return new Order(user, product, quantity);
}
Seam logue auto-magiquement choisi s’il faut envoyer la sortie vers log4j ou vers le logging JDK. Si log4j est dans le classpath, Seam va l’utiliser. Si ce n’est pas le cas, Seam va utiliser le logging JDK.
Plusieurs serveurs d’applications fonctionnent comme d’incroyable implémentation brissant le clustering HttpSession quand les modifications d’état d’objet mutables relié à une session sont seulement répliqués quand l’application appelle setAttribute() explicitement. Ceci est la source de bugs qui ne peuvent pas effectivement être testé pendant le temps de développement, jusqu’à qu’ils se manifestent quand les erreurs surviennent. En outre, le message actuel répliqué contient le graphe d’objet intégralement sérialisé relié à l’attribut de la session, ce qui est inefficace.
Bien sur, les beans de session avec état EJB peuvent réaliser automatiquement la sale vérification et la réplication des états mutés et un containeur EJB sophistiqué peut introduire des optimisations comme la réplication au niveau attribut. Malheureusement, tous les utilisateurs de Seam n’ont la bonne surprise de travailler dans un environnement qui supporte EJB3.0. Donc, pour la session et le JavaBean d’étendue conversationnelle et les composants bean entité, Seam propose une couche additionnelle pour le gestionnaire d’état sécurisé pour le cluster au dessus du clustering de session du container web.
Pour la session ou des composants de JavaBeans d’étendue conversationnel, Seam automatiquement force la réplication qui survient en appelantsetAttribute() une fois sur chaque requète quand le composant est invoqué par l'application.Bien sur, cette stratégie est inéficace pour les composants principalement en lecture seul. Vous pouvez controler cette fonctionnalité en implémentant l'interface org.jboss.seam.core.Mutable et écrire votre propre logique de sale-vérification dans votre composant. Par exemple :
@Name("account")
public class Account extends AbstractMutable
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
setDirty(this.balance, balance);
this.balance = balance;
}
public BigDecimal getBalance()
{
return balance;
}
...
}
Ou, vous pouvez utiliser l’annotation @ReadOnly pour obtenir un effet similaire :
@Name("account")
public class Account
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
this.balance = balance;
}
@ReadOnly
public BigDecimal getBalance()
{
return balance;
}
...
}
Pour la session ou pour des composants bean entité d’étendue conversation, Seam automatiquement forces la réplication qui intervient en appelant setAttribute() une fois par requête, à moins que l'entité (d'étendue conversationnelle) ne soit actuellement associée avec le contexte de persistance gérée par Seam, dans ce cas aucune réplication n'est requise. Cette stratégie n'est pas nécéssairement efficace, donc une session ou des beans entité d'édendue conversationnelle devraient l’utiliser avec prudence. Vous devez toujours écrire un bean session avec état ou un composant JavaBean pour "gérer" l’instance du bean entité. Par exemple,
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
Notez que la classe EntityHome dans le Seam Application Framework fourni un bel exemple de gestion d’une instance bean entité utilisant un composant Seam.
Nous avons souvent besoin de travailler avec des objets qui ne sont pas des composants Seam. Mais nous continuons à vouloir être capable de les injecter dans notre composants en utilisant @In et de les utiliser en tant que valeur et méthode reliées aux expressions, etc. De temps en temps, nous avons même besoin de les attacher dans le cycle de vie du contexte de Seam (@Destroy, par exemple). Donc les contextes de Seam peuvent obtenir des objets qui ne sont pas des composants Seam et Seam fourni quelques fonctionnalités sympa qui rendent plus facile le travail avec les objets non-composants reliés aux contextes.
Le modèle de conception fabrique de composant autorise un composant Seam à agir comme l’instanciateur pour un objet non-composant. Une méthode fabrique va être appelée quand une variable du contexte est référencée mais n’a pas de valeur reliée à elle. Nous définissons des méthodes fabrique en utilisant l’annotation @Factory. La méthode fabrique relie une valeur à une variable de contexte et détermine l’étendue de la valeur liée. Il y a deux types de méthode fabrique. La première méthode retourne une valeur, qui est relié au contexte par Seam :
@Factory(scope=CONVERSATION)
public List<Customer
> getCustomerList() {
return ... ;
}
Le second style est une méthode de type void qui relie cette valeur à la variable de contexte elle-même :
@DataModel List<Customer
> customerList;
@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}
Dans les deux cas, la méthode fabrique est appelée quand nous référençons la variable de contexte customerList et cette valeur est null, et alors n’a absolument pas à intervenir dans le cycle de vie de la valeur. Et même un patron de conception plus puisssant est le pattron de conception gestionnaire de composant. Dans ce cas, nous avons un composant Seam qui est relié à une variable de contexte qui gère la valeur de la variable de contexte restant invisible aux clients.
Un composant gestionnaire est n’importe quel composant avec une méthode @Unwrap. Cette méthode retourne la valeur qui va être visible pour les clients, et elle est appeléeà chaque fois qu'une variable de contexte est référencée.
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer
> getCustomerList() {
return ... ;
}
}
Le patron de conception gestionnaire est particulièrement utile si nous avons un objet où nous avons besoin de contrôler le cycle de vie du composant. Par exemple, si vous avez des objets très lourds qui ont besoin d’opération de nettoyage quand le contexte s’arrête, vous pouvez @Unwrap l'objet et réaliser un nettoyage dans la méthode @Destroy du gestionnaire.
@Name("hens")
@Scope(APPLICATION)
public class HenHouse
{
Set<Hen
> hens;
@In(required=false) Hen hen;
@Unwrap
public List<Hen
> getHens()
{
if (hens == null)
{
// Setup our hens
}
return hens;
}
@Observer({"chickBorn", "chickenBoughtAtMarket"})
public addHen()
{
hens.add(hen);
}
@Observer("chickenSoldAtMarket")
public removeHen()
{
hens.remove(hen);
}
@Observer("foxGetsIn")
public removeAllHens()
{
hens.clear();
}
...
}
Ici le composant gestionnaire observe tous les évènement qui modifie l'objet sous-jacent. Le composant gère ces actions lui-même, et parce que l'objet est empaqueté donc à chaque accès, une vue cohérante est fournie.
The philosophy of minimizing XML-based configuration is extremely strong in Seam. Nevertheless, there are various reasons why we might want to configure a Seam component using XML: to isolate deployment-specific information from the Java code, to enable the creation of re-usable frameworks, to configure Seam's built-in functionality, etc. Seam provides two basic approaches to configuring components: configuration via property settings in a properties file or in web.xml, and configuration via components.xml.
Seam components may be provided with configuration properties either via servlet context parameters, via system properties, or via a properties file named seam.properties in the root of the classpath.
The configurable Seam component must expose JavaBeans-style property setter methods for the configurable attributes. If a Seam component named com.jboss.myapp.settings has a setter method named setLocale(), we can provide a property named com.jboss.myapp.settings.locale in the seam.properties file, a system property named org.jboss.seam.properties.com.jboss.myapp.settings.locale via -D at startup, or as a servlet context parameter, and Seam will set the value of the locale attribute whenever it instantiates the component.
The same mechanism is used to configure Seam itself. For example, to set the conversation timeout, we provide a value for org.jboss.seam.core.manager.conversationTimeout in web.xml, seam.properties, or via a system property prefixed with org.jboss.seam.properties. (There is a built-in Seam component named org.jboss.seam.core.manager with a setter method named setConversationTimeout().)
The components.xml file is a bit more powerful than property settings. It lets you:
Configure components that have been installed automatically — including both built-in components, and application components that have been annotated with the @Name annotation and picked up by Seam's deployment scanner.
Install classes with no @Name annotation as Seam components — this is most useful for certain kinds of infrastructural components which can be installed multiple times with different names (for example Seam-managed persistence contexts).
Install components that do have a @Name annotation but are not installed by default because of an @Install annotation that indicates the component should not be installed.
Override the scope of a component.
A components.xml file may appear in one of three different places:
The WEB-INF directory of a war.
The META-INF directory of a jar.
Any directory of a jar that contains classes with an @Name annotation.
Usually, Seam components are installed when the deployment scanner discovers a class with a @Name annotation sitting in an archive with a seam.properties file or a META-INF/components.xml file. (Unless the component has an @Install annotation indicating it should not be installed by default.) The components.xml file lets us handle special cases where we need to override the annotations.
For example, the following components.xml file installs jBPM:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpm="http://jboss.com/products/seam/bpm">
<bpm:jbpm/>
</components>
This example does the same thing:
<components>
<component class="org.jboss.seam.bpm.Jbpm"/>
</components>
This one installs and configures two different Seam-managed persistence contexts:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:persistence="http://jboss.com/products/seam/persistence"
<persistence:managed-persistence-context name="customerDatabase"
persistence-unit-jndi-name="java:/customerEntityManagerFactory"/>
<persistence:managed-persistence-context name="accountingDatabase"
persistence-unit-jndi-name="java:/accountingEntityManagerFactory"/>
</components>
As does this one:
<components>
<component name="customerDatabase"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName">java:/customerEntityManagerFactory</property>
</component>
<component name="accountingDatabase"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName">java:/accountingEntityManagerFactory</property>
</component>
</components>
This example creates a session-scoped Seam-managed persistence context (this is not recommended in practice):
<components xmlns="http://jboss.com/products/seam/components"
xmlns:persistence="http://jboss.com/products/seam/persistence"
<persistence:managed-persistence-context name="productDatabase"
scope="session"
persistence-unit-jndi-name="java:/productEntityManagerFactory"/>
</components>
<components>
<component name="productDatabase"
scope="session"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName">java:/productEntityManagerFactory</property>
</component>
</components>
It is common to use the auto-create option for infrastructural objects like persistence contexts, which saves you from having to explicitly specify create=true when you use the @In annotation.
<components xmlns="http://jboss.com/products/seam/components"
xmlns:persistence="http://jboss.com/products/seam/persistence"
<persistence:managed-persistence-context name="productDatabase"
auto-create="true"
persistence-unit-jndi-name="java:/productEntityManagerFactory"/>
</components>
<components>
<component name="productDatabase"
auto-create="true"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName">java:/productEntityManagerFactory</property>
</component>
</components>
The <factory> declaration lets you specify a value or method binding expression that will be evaluated to initialize the value of a context variable when it is first referenced.
<components>
<factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>
</components>
You can create an "alias" (a second name) for a Seam component like so:
<components>
<factory name="user" value="#{actor}" scope="STATELESS"/>
</components>
You can even create an "alias" for a commonly used expression:
<components>
<factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>
</components>
It is especially common to see the use of auto-create="true" with the <factory> declaration:
<components>
<factory name="session" value="#{entityManager.delegate}" scope="STATELESS" auto-create="true"/>
</components>
Sometimes we want to reuse the same components.xml file with minor changes during both deployment and testing. Seam lets you place wildcards of the form @wildcard@ in the components.xml file which can be replaced either by your Ant build script (at deployment time) or by providing a file named components.properties in the classpath (at development time). You'll see this approach used in the Seam examples.
If you have a large number of components that need to be configured in XML, it makes much more sense to split up the information in components.xml into many small files. Seam lets you put configuration for a class named, for example, com.helloworld.Hello in a resource named com/helloworld/Hello.component.xml. (You might be familiar with this pattern, since it is the same one we use in Hibernate.) The root element of the file may be either a <components> or <component> element.
The first option lets you define multiple components in the file:
<components>
<component class="com.helloworld.Hello" name="hello">
<property name="name">#{user.name}</property>
</component>
<factory name="message" value="#{hello.message}"/>
</components>
The second option only lets you define or configure one component, but is less noisy:
<component name="hello">
<property name="name">#{user.name}</property>
</component>
In the second option, the class name is implied by the file in which the component definition appears.
Alternatively, you may put configuration for all classes in the com.helloworld package in com/helloworld/components.xml.
Properties of string, primitive or primitive wrapper type may be configured just as you would expect:
org.jboss.seam.core.manager.conversationTimeout 60000
<core:manager conversation-timeout="60000"/>
<component name="org.jboss.seam.core.manager">
<property name="conversationTimeout">60000</property>
</component>
Arrays, sets and lists of strings or primitives are also supported:
org.jboss.seam.bpm.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml
<bpm:jbpm>
<bpm:process-definitions>
<value>order.jpdl.xml</value>
<value>return.jpdl.xml</value>
<value>inventory.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm>
<component name="org.jboss.seam.bpm.jbpm">
<property name="processDefinitions">
<value>order.jpdl.xml</value>
<value>return.jpdl.xml</value>
<value>inventory.jpdl.xml</value>
</property>
</component>
Even maps with String-valued keys and string or primitive values are supported:
<component name="issueEditor">
<property name="issueStatuses">
<key>open</key> <value>open issue</value>
<key>resolved</key> <value>issue resolved by developer</value>
<key>closed</key> <value>resolution accepted by user</value>
</property>
</component>
When configuring multi-valued properties, by default, Seam will preserve the order in which you place the attributes in components.xml (unless you use a SortedSet/SortedMap then Seam will use TreeMap/TreeSet). If the property has a concrete type (for example LinkedList) Seam will use that type.
You can also override the type by specifying a fully qualified class name:
<component name="issueEditor">
<property name="issueStatusOptions" type="java.util.LinkedHashMap">
<key>open</key> <value>open issue</value>
<key>resolved</key> <value>issue resolved by developer</value>
<key>closed</key> <value>resolution accepted by user</value>
</property>
</component>
Finally, you may wire together components using a value-binding expression. Note that this is quite different to injection using @In, since it happens at component instantiation time instead of invocation time. It is therefore much more similar to the dependency injection facilities offered by traditional IoC containers like JSF or Spring.
<drools:managed-working-memory name="policyPricingWorkingMemory"
rule-base="#{policyPricingRules}"/>
<component name="policyPricingWorkingMemory"
class="org.jboss.seam.drools.ManagedWorkingMemory">
<property name="ruleBase">#{policyPricingRules}</property>
</component>
Seam also resolves an EL expression string prior to assigning the initial value to the bean property of the component. So you can inject some contextual data into your components.
<component name="greeter" class="com.example.action.Greeter">
<property name="message">Nice to see you, #{identity.username}!</property>
</component>
However, there is one important exception. If the type of the property to which the initial value is being assigned is either a Seam ValueExpression or MethodExpression, then the evaluation of the EL is deferred. Instead, the appropriate expression wrapper is created and assigned to the property. The message templates on the Home component from the Seam Application Framework serve as an example.
<framework:entity-home name="myEntityHome"
class="com.example.action.MyEntityHome" entity-class="com.example.model.MyEntity"
created-message="'#{myEntityHome.instance.name}' has been successfully added."/>
Inside the component, you can access the expression string by calling getExpressionString() on the ValueExpression or MethodExpression. If the property is a ValueExpression, you can resolve the value using getValue() and if the property is a MethodExpression, you can invoke the method using invoke(Object args...). Obviously, to assign a value to a MethodExpression property, the entire initial value must be a single EL expression.
Throughout the examples, there have been two competing ways of declaring components: with and without the use of XML namespaces. The following shows a typical components.xml file without namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">
<component class="org.jboss.seam.core.init">
<property name="debug">true</property>
<property name="jndiPattern">@jndiPattern@</property>
</component>
</components>
As you can see, this is somewhat verbose. Even worse, the component and attribute names cannot be validated at development time.
The namespaced version looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">
<core:init debug="true" jndi-pattern="@jndiPattern@"/>
</components>
Even though the schema declarations are verbose, the actual XML content is lean and easy to understand. The schemas provide detailed information about each component and the attributes available, allowing XML editors to offer intelligent autocomplete. The use of namespaced elements makes generating and maintaining correct components.xml files much simpler.
Now, this works great for the built-in Seam components, but what about user components? There are two options. First, Seam supports mixing the two models, allowing the use of the generic <component> declarations for user components, along with namespaced declarations for built-in components. But even better, Seam allows you to quickly declare namespaces for your own components.
Any Java package can be associated with an XML namespace by annotating the package with the @Namespace annotation. (Package-level annotations are declared in a file named package-info.java in the package directory.) Here is an example from the seampay demo:
@Namespace(value="http://jboss.com/products/seam/examples/seampay")
package org.jboss.seam.example.seampay;
import org.jboss.seam.annotations.Namespace;
That is all you need to do to use the namespaced style in components.xml! Now we can write:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:pay="http://jboss.com/products/seam/examples/seampay"
... >
<pay:payment-home new-instance="#{newPayment}"
created-message="Created a new payment to #{newPayment.payee}" />
<pay:payment name="newPayment"
payee="Somebody"
account="#{selectedAccount}"
payment-date="#{currentDatetime}"
created-date="#{currentDatetime}" />
...
</components>
Or:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:pay="http://jboss.com/products/seam/examples/seampay"
... >
<pay:payment-home>
<pay:new-instance>"#{newPayment}"</pay:new-instance>
<pay:created-message>Created a new payment to #{newPayment.payee}</pay:created-message>
</pay:payment-home>
<pay:payment name="newPayment">
<pay:payee>Somebody"</pay:payee>
<pay:account>#{selectedAccount}</pay:account>
<pay:payment-date>#{currentDatetime}</pay:payment-date>
<pay:created-date>#{currentDatetime}</pay:created-date>
</pay:payment>
...
</components>
These examples illustrate the two usage models of a namespaced element. In the first declaration, the <pay:payment-home> references the paymentHome component:
package org.jboss.seam.example.seampay;
...
@Name("paymentHome")
public class PaymentController
extends EntityHome<Payment>
{
...
}
The element name is the hyphenated form of the component name. The attributes of the element are the hyphenated form of the property names.
In the second declaration, the <pay:payment> element refers to the Payment class in the org.jboss.seam.example.seampay package. In this case Payment is an entity that is being declared as a Seam component:
package org.jboss.seam.example.seampay;
...
@Entity
public class Payment
implements Serializable
{
...
}
If we want validation and autocompletion to work for user-defined components, we will need a schema. Seam does not yet provide a mechanism to automatically generate a schema for a set of components, so it is necessary to generate one manually. The schema definitions for the standard Seam packages can be used for guidance.
The following are the the namespaces used by Seam:
components — http://jboss.com/products/seam/components
core — http://jboss.com/products/seam/core
drools — http://jboss.com/products/seam/drools
framework — http://jboss.com/products/seam/framework
jms — http://jboss.com/products/seam/jms
remoting — http://jboss.com/products/seam/remoting
theme — http://jboss.com/products/seam/theme
security — http://jboss.com/products/seam/security
mail — http://jboss.com/products/seam/mail
web — http://jboss.com/products/seam/web
pdf — http://jboss.com/products/seam/pdf
spring — http://jboss.com/products/seam/spring
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.
<s:link> et de <s:button>Il est temps de comprendre le modèle conversationnel de manière plus détaillée.
Historiquement, la notion d'une "conversation" de Seam vient de la fusion de trois idées différentes:
L'idée d'un espace de travail, que j'ai rencontré dans un projet pour le gouvernement Victorien en 2002. Dans ce projet, j'étais obligé d'implémenter un gestionnaire d'espace de travail par dessus Struts, une expérience que j'espère ne jamais plus répétée.
L'idée d'une transaction d'application avec une sémantique optimiste, et que la réalisation d'un serveur d'application basé autour d'une architecture sans état ne peut fournir une gestion efficace des contextes de persistences étendues. (L'équipe Hibernate est réellement écoeurée d'être accusé pour toutes les LazyInitializationExceptions, ce qui n'est pas vraiment la faute d'Hibernate, mais plutôt la faute de la persistence extrèmement limitée du modèle de contexte supporté par les architectures sans état comme le serveur d'application Spring ou le traditionnel (anti)pattron facade de session sans état dans J2EE.)
L'idée d'un enchainement de tâches.
En unifiant ces idées et en fournissant un profond support dans le squellete d'application, nous avons une construction percutante qui nous permets de construire de plus riche et de plus éfficace applications avec moins de code qu'auparavant.
Les exemples que nous avons vu jusque là utilisent un modèle conversationnel très simple qui suit ces règles:
l y a toujours un contexte conversationnel actif pendant l'application des valeurs de la requête, l'exécution des validations, la mise à jours des valeurs du modèles, l'invocation de l'application et le rendu des phases de responses du cycle de vie de la requête JSF.
A la fin de la phase de restoration de la vue du cycle de vie de la requête JSF, Seam essaye de restorer tout contexte conversationnel à éxécution longue. Si aucun n'existe, Seam crée un nouveau contexte conversationnel temporaire.
Quand une méthode @Begin est rencontrée, le contexte conversationnel temporaire est promu en conversation à exécution longue.
Quand la méthode @End est rencontré , chaque contexte conversationnel à exécution longue est dégommé en conversation temporaire.
At the end of the render response phase of the JSF request lifecycle, Seam stores the contents of a long running conversation context or destroys the contents of a temporary conversation context.
Chaque requête face (un postback JSF) va propager le contexte conversationnel. Par défaut, les requêtes non-faces (requêtes GET, par exemple), ne propage pas le contexte conversationnel, mais regardez ci-dessous pour plus d'information à propos de cela.
Si le cycle de vie de la requête JSF est réduit par une redirection, Seam entreprose de manière transparente et restitue le contexte conversationnel courrant — à moins que la conversation n'est été déjà terminée via @End(beforeRedirect=true).
Seam propage de manière transparente le contexte conversationnel au travers des postbacks JSF et des redirections. Si vous ne voulez pas faire quelquechose de spécial, une requête non-faces (une requête GET par exemple) ne va pas propager le contexte conversationnel et va être exécuté dans une nouvelle conversdation temporaire. C'est habituellement- mais pas toujours- le fonctionnement désiré.
Si vous voulez propager une conversation Seam au travers d'une requête non-faces, vous devez explicitement coder l'identifiant de conversation comme un paramètre de requête:
<a href="main.jsf?#{manager.conversationIdParameter}=#{conversation.id}"
>Continue</a
>
Ou beaucoup plus, JSF-ien:
<h:outputLink value="main.jsf">
<f:param name="#{manager.conversationIdParameter}" value="#{conversation.id}"/>
<h:outputText value="Continue"/>
</h:outputLink
>
Si vous utilisez la librairie de tag de Seam, ceci est équivalent:
<h:outputLink value="main.jsf">
<s:conversationId/>
<h:outputText value="Continue"/>
</h:outputLink
>
Si vous voulez désactiver la propagation du contexte conversationnel pour un postback, un truc similaire est utilisé:
<h:commandLink action="main" value="Exit">
<f:param name="conversationPropagation" value="none"/>
</h:commandLink
>
Si vous utilisez la librairie de tag de Seam, ceci est équivalent:
<h:commandLink action="main" value="Exit">
<s:conversationPropagation type="none"/>
</h:commandLink
>
Notez que la désactivation de la propgation du contexte conversationnel n'est absolument pas la même chose que finir la conversation.
Le paramètre de requête conversationPropagation ou le tag <s:conversationPropagation> peut même être utilisé pour commencer ou finir la conversation liée.
<h:commandLink action="main" value="Exit">
<s:conversationPropagation type="end"/>
</h:commandLink
>
<h:commandLink action="main" value="Exit">
<s:conversationPropagation type="endRoot"/>
</h:commandLink
>
<h:commandLink action="main" value="Select Child">
<s:conversationPropagation type="nested"/>
</h:commandLink
>
<h:commandLink action="main" value="Select Hotel">
<s:conversationPropagation type="begin"/>
</h:commandLink
>
<h:commandLink action="main" value="Select Hotel">
<s:conversationPropagation type="join"/>
</h:commandLink
>
Ce modèle conversationnel rend facile de construire des applications qui traite correctement en respectant les opération multi-fenètres. Pour beaucoup d'application, c'est tout ce qu'il y a besoin. Quelques applications complexes ont besoin d'une ou des deux exigeances additionnelles suivantes:
Une conversation s'étend sur plusieurs petites unités d'intéraction de l'utilisateur, qui s'exécute en série ou même en concurence. Les plus petites conversations liées ont leur propre groupe d'état conversationnel isolé, et aussi ont accès à l'état des conversation externe.
L'utilisateur est aussi capable de basculer entre plusieurs conversations dans la même fenètre du navigateur. Cette fonctionnalité est appelé le gestionnaire d'espace de travail.
Une conversation liée est créée en invoquant la métode marqué @Begin(nested=true) à l'intérieur d'une conversation existante. Une conversation liée a son propre contexte conversationnel et peut aussi lire les valeurs depuis le contexte conversationnel externe . Le contexte conversationnel externe est en lecture seul à l'intérieur d'une conversation liée mais parce que les objets sont obtenue par référence, les modifications des objets eux-même seront réfléchies dans le contexte externe.
En liant au travers d'une conversation initialise un contexte qui sera empilé sur le contexte de la conversation orginelle ou externe. La conversation externe est considéré comme la parente.
Toute valeurs extradée ou directement défini dans le contexte conversationnelle lié n'affectera pas l'accéssibilité des objets dans le contexte conversationnel parent.
L'injection ou le parcours dans le contexte depuis le contexte conversaionnel cherchera en premier la valeur dans le contexte conversationnel courant et si aucune valeur n'est trouvée, descendra dans la pile de conversation si la conversation est liée. Comme vous le voyez pour l'instant, cette fonctionnalité peut être surchargée.
Quand un @End est par la suite rencontré, la conversation liée sera détruite et la conversation externe sera terminée, en la "retirant du dessus" de la pile des conversations. Les conversations peuvent être liée à n'importe quelle profondeur arbitraire.
Certaines activités de l'utilisateur (gestionnaire de l'espace de travail ou bouton précédent) peuvent faire en sorte que la conversation externe soit reprise avant que la conversation interne ne soit finie. Dans ce cas, il est possible d'avoir de multiples conversations concurrantes liées appartenant à la même conversation externe. Si la conversation externe se fini avant que la conversation liée ne s'arrête, Seam détruit tous les contextes conversationnels liés en relation avec le contexte externe.
La conversation au bas de la pil de conversation est la conversation racine. La destruction de cette conversation va toujours détruire les conversations enfantées. Vous pouvez réaliser ceci en spécifiant @End(root=true).
Une conversation peut être imaginée comme un état continue. Les conversations liée permette à l'application de capture un état constistent et continue à plusieurs moments de l'interaction utilisateur, ainsi assurant de manière réellement correcte le focntionnement vis à vis du bouton précédent et de la gestion de l'espace de travail.
Comme indiqué précédemment, si un composant existe dans la conversation parente de la conversation liée courante, la conversation liée utilisera la même instance. Occasionnellement, il est utile d'avoir une instance différente pour chaque conversation liée, ainsi l'instance du composant qui existe dans la conversation parente est invisible à ses propres conversations filles. Vous pouvez avoir cette fonctionnalité en annotatant le composant @PerNestedConversation.
JSF ne définie aucun type d'écouteur d'action qui est déclencher quand une page est accédée via une requête non-faces (par exemple, une requête HTTP GET). Cela peut arriver si l'utilisateur fait une favori de la page, ou si nous navigons vers la page via un <h:outputLink>.
Parfois nous voulons commencer une conversation immédiatement quand la page est accédée. Depuis qu'il n'y a pas de méthode d'action JSF, nous ne pouvons résoudre le problème de la manière habituelle, en annotation l'action avec@Begin.
Un autre problème est levé si la page a besoin des états soient reliés dans une variable de contexte. Nous avons déjà vue deux façon de résoudre ce problème. Si l'état est retenue par un composant Seam, nous pouvons relié l'éata dans une méthode @Create . Sinon, nous pouvons définir une méthode @Factory pour la variable de contexte.
Si aucune de ces options ne fonctionne pour vous, Seam vous permet de définir une action de page dans le fichierpages.xml file.
<pages>
<page view-id="/messageList.jsp" action="#{messageManager.list}"/>
...
</pages
>
Cette méthode d'action est appelé au début de la phase de rendue de la réponse, à chaque fois que la page est au moment d'être rendue. Si une actiond e page retourne une sortie non-nulle, Seam va réaliser chaque règle de navigation JSF et Seam appropriée, résultant possiblement dans une page complètement différente à être rendue.
Si tout ce que vous voulez faire avant de rendre la page est de commencer une conversation, vous pouvez utiliser une méthode d'action livrée qui fait juste cela:
<pages>
<page view-id="/messageList.jsp" action="#{conversation.begin}"/>
...
</pages
>
Notez que vous pouvez aussi appeler cette action livrée depuis un control JSF, et , de manière simmilaire, vous pouvez utiliser #{conversation.end} pour finir les conversations.
Si vous voulez plus de contrôle, pour rejoindre les conversations existantes ou commencer une conversation liée, pour commencer une enchainement de page ou une conversation insécable, vous devriez utiliser l'élément <begin-conversation>.
<pages>
<page view-id="/messageList.jsp">
<begin-conversation nested="true" pageflow="AddItem"/>
<page>
...
</pages
>
Il y aussi un élément <end-conversation>.
<pages>
<page view-id="/home.jsp">
<end-conversation/>
<page>
...
</pages
>
Pour résoudre le premier problème, nous avons maintenant cinq options:
Annoter la méthode @Create avec @Begin
Annoter la méthode @Factory avec @Begin
Annoter l'action de la page Seam avec @Begin
Utiliser <begin-conversation> dans pages.xml.
Utiliser #{conversation.begin} dans la méthode d'action de page de Seam
Certaines pages ne sont pertinente que dans le contexte d'une conversation à exécution longue. Une façon de "proteger" ce type de page est de demander une conversation à exécution longue comme un prérequis pour rendre la page. Heureusement, Seam a un mécanisme livré pour mettre en place ce prérequis.
Dans le descripteur de la page de Seam, vous pouvez indiquer que la conversation courante doit être à exécution longue (ou liée) pour que la page soit rendue en utilisant l'attribut conversation-required comme ci-dessous:
<page view-id="/book.xhtml" conversation-required="true"/>
Le seul défaut est qu'il n'y à pas de façpon livré pour indiquer quelle conversation à exécution longue est requise. Vous pouvez construire ce mécanisme d'autorisation simple en à al fois vérifiant qu'une valeur spécifique est présente dans la conversation avec une action de page.
Quand Seam détermine que cette page est requise à l'extérieure d'une conversation à exécution longue, les actions suivantes interviennent:
Un évènement contextuel dénommé org.jboss.seam.noConversation est levé
Un message de status avertissement est enregistré en utilisant la clef fournie org.jboss.seam.NoConversation
L'utilisateur est rediriger vers une page alternative, si définie
La page alternative est définie dans l'attribut no-conversation-view-id sur l'élément <pages> dans le descripteur de page de Seam comme ci-dessous:
<pages no-conversation-view-id="/main.xhtml"/>
A ce moment là, vous pouvez seulement définir un seul type de page pour toute l'application.
Les liens de commande JSF réalise toujours une soumission de formulaire via JavaScript, qui casse la fonctionnalité des navigateur web de "ouvrir une nouvelle fenêtre" ou "ouvrir un nouvel onglet". En pur JSF, vous avez besoin d'utiliser un <h:outputLink> si vous avez besoin de la fonctionnalité. Mais il y a deux limitations majeures à <h:outputLink>.
JSF fourni une façon d'attacher un écouteur d'action à un <h:outputLink>.
JSF ne propage pas la ligne sélectionné dans un DataModel s'il n'y a pas réellement de soumission de formulaire.
Seam fourni la notion d'action de page pour aider à résoudre le premier problème, mais cela ne fait rien pour le second problème. Nous pourrionscontourner cela en utilisant l'apporche RESTful en passant le paramètre de requête et en requérant l'objet sélectionné du côté serveur. Dans quelques cas — comme l'application blog en exemple de Seam — c'est en fait la meilleure approche. Le style RESTful supporte le marquepage alors qu'il ne requiere par un état du côté serveur. Dans les autres cas, quand nous ne nous inquiétons pas à propos des marques-pages, l'utilisation de @DataModel et @DataModelSelection est juste plus pratique et plus transparant!
Pour implémenter cette fonctionnalité manquante et pour permettre la propagation de la conversation aussi simple pour gérer, Seam fourni la tag JSF <s:link>.
Le lien peut juste spécifier l'iodentifiant de la vue JSF:
<s:link view="/login.xhtml" value="Login"/>
Or, it may specify an action method (in which case the action outcome determines the page that results):
<s:link action="#{login.logout}" value="Logout"/>
Si vous spécifier les deux : un identifiant de vue JSF et une méthode d'action, la 'vue' sera utilisée à moins que la méthode d'action ne retourne un résultat non-null:
<s:link view="/loggedOut.xhtml" action="#{login.logout}" value="Logout"/>
Le lien propage automatiquement la ligne sélectionnée d'un DataModel en l'utilisant avec <h:dataTable>:
<s:link view="/hotel.xhtml" action="#{hotelSearch.selectHotel}" value="#{hotel.name}"/>
Vous pouvez quitter l'étendue d'une conversation existante:
<s:link view="/main.xhtml" propagation="none"/>
Vous pouvez commencer, finir ou grouper les conversations:
<s:link action="#{issueEditor.viewComment}" propagation="nested"/>
Si le lien commence une conversation, vous pouvez même spécifier l'enchainement de page à utiliser:
<s:link action="#{documentEditor.getDocument}" propagation="begin"
pageflow="EditDocument"/>
L'attribut taskInstance à utiliser dans une liste de tâches jBPM:
<s:link action="#{documentApproval.approveOrReject}" taskInstance="#{task}"/>
(Voir l'application de démonstration du magasin de DVD pour des exemples de cela.)
Enfin, si vous avez besoin d'un "lien" qui soit visualisé comme un bouton, utilisez <s:button>:
<s:button action="#{login.logout}" value="Logout"/>
Il est assez commun d'afficher un message pour l'utilisateur indiquant le succés ou l'echec d'une action. Il est pratique d'utiliser un FacesMessage de JSF pour cela. Malheureusement, une action réussie a besoin souvant d'une redirection du navigateur, et JSF ne peut pas propager un messages faces au travers de redirections. Cela rends les choses un peu plus difficile pour afficher un message de succés en pur JSF.
Le composant de Seam d'étendue conversation livré dénomé facesMessages résoud ce problème. (Vous devez avoir un filtre de redirection de Seam installé.)
@Name("editDocumentAction")
@Stateless
public class EditDocumentBean implements EditDocument {
@In EntityManager em;
@In Document document;
@In FacesMessages facesMessages;
public String update() {
em.merge(document);
facesMessages.add("Document updated");
}
}
Chaque message ajouté à facesMessages est utilisé dans la phase de réponse immédiatement suivante pour la conversation courante. Cela fonctionne même quand il y une conversaiton à exécution longue depuis que Seam conserve les contexte de conversation temporaire au travers de redirections.
Vous pouvez même inclure les expressions EL de JSF dans le label du messages faces:
facesMessages.add("Document #{document.title} was updated");
Vous pouvez afficher le message de manière usuelle, par exemple:
<h:messages globalOnly="true"/>
Ordinairement avec les conversation qui travaille avec des objets persistants, il peut être utile d'utiliser une clef métier explicite au lieu de la version standard, identifiant de conversation de "substitution":
Redirection simple vers une conversation existante
Il peut être utile de rediriger vers une conversation existante si l'utilisateur demande la même opération deux fois. Prenons cet exemple: « Vous êtes sur ebay, au milieu de la phase de paiement pour un cadeau de noël pour vos parents que vous avez gagné. Inutile de dire que vous voulez le leur envoyé immédiatement - vous entrez les défailts sur le paiement mais sans pouvoir vous souvenir de leur adresse. Vous réutilisez accidentellement la même fenètre du navigateur pour trouver leur adresse. Maintenant, vous avez besoin de retourner vers le paiement pour ce truc. »
Avec une conversation explicite, il est vraiment très facile d'avoir un utilisateur qui rejoind une conversation existante, et la reprendra là où il la laissé - tout simplement lui permettre de rejoindre la conversation payForItem avec l'itemId comme identifiant de conversation.
Utilisation des URLs sympatiques
Pour moi cela consiste dans une hierachie navigable (Je peux naviguer en éditant l'URL) et une URL pertinente (comme font les Wiki - donc sans identifier les choses avec des identifiants kabbalistiques). Pour des applications l'utilisation des URLs sympatiques ne sont pas , bien sûr, du tout utile.
Avec une conversation explicite, quand vous allez construire votre système de réservation d'hotel (ou, bien sûr, quelque soit votre application) vous pouvez générer une URL comme http://seam-hotels/book.seam?hotel=BestWesternAntwerpen (bien sûr, quelque soit le paramètre hotel correspondant au modèle de votre domaine doit être unique) et avec une ré-écriture des URLs facilement transformable en http://seam-hotels/book/BestWesternAntwerpen.
Encore mieux!
Les conversations explicites sont définies dans description
<conversation name="PlaceBid"
parameter-name="auctionId"
parameter-value="#{auction.auctionId}"/>
La première chose à noter de la définition ci-dessus est qua la conversation a un nom, dans ce cas PlaceBid. Ce nom identifiant de manière unique cette conversation particulière, et est utilisé par la définition de page pour identifier une conversation sus-nommé pour y participer.
L'attribut suivant, parameter-name définit le paramète de la requête qui va contenir l'identifiant explicite de conversation, en lieu et place du paramètre d'identifiant de conversation par défaut. Dans cet exemple, le parameter-name est auctionId. Ce qui signifie qu'au lieu d'avoir un paramètre de conversation comme cid=123 qui apparait dans l'URL de votre page, il y aura auctionId=765432 à la place.
Le dernier attribut dans la configuration ci-dessus, parameter-value, défini une expression EL utilisée pour évaluer la valeur de la clef métier explicite à utiliser comme identifiant de conversation. Dans cet exemple, l'identifiant de conversation sera une valeur de clef primaire de l'instance auction courante dans l'étendue.
Ensuite, nous définissons quelle page va être dans la convesation dénommée. Ceci est fait en spécifiant que l'attribut conversation pour une définition de page:
<page view-id="/bid.xhtml" conversation="PlaceBid" login-required="true">
<navigation from-action="#{bidAction.confirmBid}"
>
<rule if-outcome="success">
<redirect view-id="/auction.xhtml">
<param name="id" value="#{bidAction.bid.auction.auctionId}"/>
</redirect>
</rule
>
</navigation>
</page
>
Au moment de démarrer, ou de rediriger vers, une conversation explicite, il y a plusieurs options pour spécifier le nom de la conversation explicite. Commençons pas regarder la définition de page suivante:
<page view-id="/auction.xhtml">
<param name="id" value="#{auctionDetail.selectedAuctionId}"/>
<navigation from-action="#{bidAction.placeBid}">
<redirect view-id="/bid.xhtml"/>
</navigation>
</page
>
A partir de là, nous pouvons voir que l'invocation de l'action #{bidAction.placeBid} pour notre vue auction (à partir de là, tous ces exemples sont prit de l'exemple seamBat dans Seam), qui va être redirigier vers /bid.xhtml, qui , comme nous l'avons vu précédemment, est configuré avec la conversation explicite PlaceBid. La déclaration de notre méthode action devrait ressemble à ceci:
@Begin(join = true)
public void placeBid()
Quand les conversations nommées sont spécifiées dans l'élément <page/>, la redirection vers la conversion nommé intervient comme une des règles de navigations, après que la méthode d'action ai été invoquée. Ceci est un problème quand la redirection vers une conversation existante, quand la redirection a besoin d'intervenir après que la méthode d'action ai été invoquée. C'est pourquoi il est nécéssaire de spécifier le nom de la conversation quand l'action est invoquée. Une façon de faire cela est en utilisant la balise s:conversationName :
<h:commandButton id="placeBidWithAmount" styleClass="placeBid" action="#{bidAction.placeBid}">
<s:conversationName value="PlaceBid"/>
</h:commandButton
>
Une autre manière de spécifier l'attribut conversationName est en utilisant aussi bien s:link que s:button:
<s:link value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/>
Le gestionnaire de l'espace de travail est capable de "basculer" les conversations dans une seule fenètre. Seam rend la gestion des espaces de travail complètement transparente au niveau du code Java. Pour activer le gestionnaire de l'espace de travail, tout ce que vous avez à faire est :
Fournir un texte de descriptionpour chaque identifiant de vue (avec l'utilisation de JSF ou des règles de navigation Seam) ou un noeud de page (avec l'utilisation des enchainements de page jPDL). Ce texte descriptif est afficher à l'utilisateur par le commutateur d'espace de travail.
Inclure un ou plusieurs commutateur d'espace de travail standards JSP ou fragment de facelets dans vos pages. Les fragments standards supportent le gestionnaire d'espace de travail via un menu par liste sélectionnable, ou par fil d'ariane.
Quand vous utiliser les règles de navigation Seam ou JSF, Seam bascule d'une conversation en restorant le view-id courrant pour cette conversation. Le texte descriptif pour l'espace de travail est défini dans un fichier appelé pages.xml ainsi Seam s'attend à trouver dans le dossier WEB-INF, tout à côté de faces-config.xml:
<pages>
<page view-id="/main.xhtml">
<description
>Search hotels: #{hotelBooking.searchString}</description>
</page>
<page view-id="/hotel.xhtml">
<description
>View hotel: #{hotel.name}</description>
</page>
<page view-id="/book.xhtml">
<description
>Book hotel: #{hotel.name}</description>
</page>
<page view-id="/confirm.xhtml">
<description
>Confirm: #{booking.description}</description>
</page>
</pages
>
Botez que si le fichier est manquant, l'application Seam continura de fonctionner parfaitement! La seule fonctionnalité manquante sera la capacité de basculer dans les espaces de travail.
Quand vous utilisez une définition d'enchainement de page jPDL, Seam bascule vers une conversation en restorant l'état du processus jBPM courant. Ceci est beaucoup plus flexible depuis qu'il permet au même view-id d'avoir plusieurs descriptions selon le noeud <page> courrant. Le texte descriptif est défini dans le noeud <page>:
<pageflow-definition name="shopping">
<start-state name="start">
<transition to="browse"/>
</start-state>
<page name="browse" view-id="/browse.xhtml">
<description
>DVD Search: #{search.searchPattern}</description>
<transition to="browse"/>
<transition name="checkout" to="checkout"/>
</page>
<page name="checkout" view-id="/checkout.xhtml">
<description
>Purchase: $#{cart.total}</description>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page>
<page name="complete" view-id="/complete.xhtml">
<end-conversation />
</page>
</pageflow-definition
>
Inclure les fragments suivant dans votre page facelets ou JSP pour avoir le menu à liste sélectionnable qui vous permet de basculer vers n'importe quelle conversation courante, ou vers n'importe quelle page de l'application:
<h:selectOneMenu value="#{switcher.conversationIdOrOutcome}">
<f:selectItem itemLabel="Find Issues" itemValue="findIssue"/>
<f:selectItem itemLabel="Create Issue" itemValue="editIssue"/>
<f:selectItems value="#{switcher.selectItems}"/>
</h:selectOneMenu>
<h:commandButton action="#{switcher.select}" value="Switch"/>
Dans cet exemple, nous avons un menu qui inclus un élément pour chaque conversation, regroupé avec deux éléments additionnels qui permet à l'utilisateur de commencer une nouvelle conversation.
Seulement les conversations avec une description (spécifiée dans pages.xml) seront incluses dans le menu à liste sélectionnable.

La liste des conversations est vraiment similaire au commutateur de conversation, exception faite qu'elle est affiché comme un tableau:
<h:dataTable value="#{conversationList}" var="entry"
rendered="#{not empty conversationList}">
<h:column>
<f:facet name="header"
>Workspace</f:facet>
<h:commandLink action="#{entry.select}" value="#{entry.description}"/>
<h:outputText value="[current]" rendered="#{entry.current}"/>
</h:column>
<h:column>
<f:facet name="header"
>Activity</f:facet>
<h:outputText value="#{entry.startDatetime}">
<f:convertDateTime type="time" pattern="hh:mm a"/>
</h:outputText>
<h:outputText value=" - "/>
<h:outputText value="#{entry.lastDatetime}">
<f:convertDateTime type="time" pattern="hh:mm a"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header"
>Action</f:facet>
<h:commandButton action="#{entry.select}" value="#{msg.Switch}"/>
<h:commandButton action="#{entry.destroy}" value="#{msg.Destroy}"/>
</h:column>
</h:dataTable
>
Nous imaginons que nous allons vouloir la personnaliser pour notre application.

Seule les conversations avec une description seront incluse dans la liste.
Il faut noter que la liste de conversation permet à l'utilisateur de détruire les espaces de travail.
Les fils d'ariane sont vraiment très utile dans les applications qui utilisent un modèle de conversations liée. Le fil d'ariane est une list de liens vers les conversations dans la pile de conversation courante:
<ui:repeat value="#{conversationStack}" var="entry">
<h:outputText value=" | "/>
<h:commandLink value="#{entry.description}" action="#{entry.select}"/>
</ui:repeat

Les composants conversationnels ont une limitation mineure; ils ne peuvent être utilisé pour conserver une liaison vers des composants JSF. (Nous préférons générallement ne pas utiliser cette fonctionnalité avec JSF à moins que cela ne soit absolument nécssaire, car cela créer une dépendance forte de la logique applicative vers la vue). Avec une requête de retour,la liaison de composant est mise à jours pendant la phase de restoration de la vue, avant que le contexte conversationnel de Sean ne soit restoré.
Pour contourner ceci, utilisez un composant d'étendue évènement pour conserver la liaison du composant et l'injecter dans le composant d'étendue conversationnel qui en a besoin.
@Name("grid")
@Scope(ScopeType.EVENT)
public class Grid
{
private HtmlPanelGrid htmlPanelGrid;
// getters and setters
...
}
@Name("gridEditor")
@Scope(ScopeType.CONVERSATION)
public class GridEditor
{
@In(required=false)
private Grid grid;
...
}
De même, vous ne pouvez pas injecter un composant d'étendue conversationnel dans un composant d'étendue d'évènement que vous liez avec un contrôle JSF. Ceci inclu les composants livrés de Seam comme facesMessages.
L'alternative est que vous pouvez accéder à l'arbre de composant JSF au traver d'une référence uiComponent implicite. L'exemple suivant accède getRowIndex() du composant UIData qui conserve le tableau de données pendant l'itération, il affiche le numéro de ligne courrant:
<h:dataTable id="lineItemTable" var="lineItem" value="#{orderHome.lineItems}">
<h:column>
Row: #{uiComponent['lineItemTable'].rowIndex}
</h:column>
...
</h:dataTable
>
Les composant UI de JSF sont disponibles avec leurs identifiants de client dans cette table de hachage.
Une discussion général d'appel concurentiel des composants de Seam peut être trouvée dans Section 4.1.10, « Modèle de concurrence ». Ici, nous allons discuter de la situation la plus courante où vous allez rencontrer de la concurence — l'accès aux composants conversationnel depuis les requêtes AJAX. Nous aloons discuter des options que les bibliothèque AJAX cliente devraient fournir pour controler les évènements provenant du client — et nous allons voir l'option que RichFaces vous offre.
Les composants conversationnels ne permettent pas un accès réellement concurentiel à moins que Seam ne mette en file d'attente chaque requête pour les réaliser séquenciellement. Ceci permet à chaque requête d'être exécutée de façon déterministe. Cependant une simple file d'attente n'est pas aussi géniale — en premier lieu, si uen méthode prend, pour une raison quelconque, un très long moment pour se terminer, exécuter encore une par dessus , et encore une autre, tant et plus que le client génère une requête est une mauvaise idée (attaque potentielle par Dénie de Service) et en second, AJAX est souvent utilisé pour fournir une mise à jours rapide d'un status à l'utilisateur, donc continuer à exécuter l'action après un bout de temps n'est pas très utile.
Ainsi, quand vous travaillez à l'intérieur d'une conversation à exécution longue, Seam met en file d'attente les évènements d'action pour une période de temps (le temps de péremption de la requête concurentielle); si il peut exécuter l'évènement dans les temps, il créé une conversation temporaire et affiche le message pour que l'utilisateur sache ce qu'il se passe. Il est donc très important de ne pas saturer le serveur avec des évènements AJAX!
Nous pouvons définir par défaut et précisément le temps de péremption de la requête concurentielle (en ms) dans components.xml:
<core:manager concurrent-request-timeout="500" />
Nous pouvons aussi configurer finement le temps de péremption de la requête concurente sur une base de page-par-page:
<page view-id="/book.xhtml"
conversation-required="true"
login-required="true"
concurrent-request-timeout="2000" />
D'aussi loin que nous discutons des requêtes AJAX qui apparaisent sérialisée à l'utilisateur - le client indique au serveur qu'un évènement intervient, et ensuite re-rends de la partie de la page basé sur le résultat. Cette approche est géniale quand la requête AJAX est légère (la méthode appelé est simple, par exemple, le calcul de la somme d'une colonne de nombre). Mais si nous avons besoin de faire un calcul complexe qui va prendre une minute?
Pour les calculs lourds, vous devrions utiliser une approche basé sur un groupement — le client envoie une requête AJAX vers le serveur, ce qui fait que l'action soit exécuté assynchronement sur le serveur (la response vers le client est immédiate) et le client ensuite regoupe les mises à jours vers le serveur. Ceci est une bonne approche si vous avez une action à exécution longue pour laquelle il est important que chaque action soit exécuté (vous ne voulez pas de péremption).
En premier lieu, vous avez besoin de choisir si vous voulez utiliser la requête la plus simple en "série" ou vous voulez utiliser une approche par regroupement.
SI vous vous décidez pour des requêtes en "séries", alors vous avez besoin d'estimer combien de temps va durer votre requête pour se terminer - est ce plus court que le temps de péremption de la requête concurentielle? Ci ce n'est pas le cas, vous voulez probablement modifier le temps de péremption de la requête concurentielle (comme vu précédemment). Vous voulez probablement mettre en fil d'attente côté client pour prévenir la saturation du serveur avec les requêtes. Si l'évènement intervient souvent (par exemple, pression sur une touche, au survol de champs de saisie) et la mise à jours immédiate du client n'est pas une priorité, vous devriez définir un delais de la requête côté client. Quand le delais de la requête sera passé, tenir compte que l'évènement sera aussi mis en file d'attente du côté serveur.
Au final, la bibliothèque cliente peut fournir une option pour intérrompre une requête de duplication en cours au profit d'une plus récente.
L'utilisation d'un design de style regroupement nécéssite un réglage moins précis. Vous avez juste à indiquer votre méthode d'action avec @Asynchronous et décider d'un intervale de regroupement:
int total;
// This method is called when an event occurs on the client
// It takes a really long time to execute
@Asynchronous
public void calculateTotal() {
total = someReallyComplicatedCalculation();
}
// This method is called as the result of the poll
// It's very quick to execute
public int getTotal() {
return total;
}
Quelquesoit le cas, définissez votre application avec attention pour mettre les requêtes concurentes en file d'attente vers votre composant conversationnel, il ya une risque que le serveur devienne trop surchargé pour être capable d'exécuter toutes les requêtes avant que la requête n'ai à attendre plus longtemps que concurrent-request-timeout. Dans ce cas, Seam déclenchera un ConcurrentRequestTimeoutException qui peut être gérer dans pages.xml. Nous recommendons d'envoyer une erreur HTTP 503:
<exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
<http-error error-code="503" />
</exception
>
Le serveur est habituellement incapable de gérer la requête à cause de la surcharge temporaire ou de la maintenance du serveur. L'implication est que ceci est une condition temporaire qui sera reduite après un délais.
Autrement vous pouvez rediriger vers une page d'erreur:
<exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
<end-conversation/>
<redirect view-id="/error.xhtml">
<message
>The server is too busy to process your request, please try again later</message>
</redirect>
</exception
>
ICEfaces, RichFaces Ajax et Seam Remoting peuvent gérer les codes d'erreurs HTTP. Seam Remoting va faire surgir une boite de dialogue montrant l'erreur HTTP. ICEfaces va indiquer l'erreur dans son composant de status de connection. RichFaces fourni le support le plus complet pour la gestion des erreurs HTTP en fournissant une fonction de rappel définissable pour l'utilisateur. Par exemple, pour afficher le message d'erreur pour l'utilisateur:
<script type="text/javascript">
A4J.AJAX.onError = function(req,status,message) {
alert("An error occurred");
};
</script
>Si au lieu d'un code d'erreur, le serveur rapporte que la vue a expirée, peut être à cause du temps de péremption de la session, vous pouvez utiliser une fonction de rappel séparée dans RichFaces pour gérer ce scénario.
<script type="text/javascript">
A4J.AJAX.onExpired = function(loc,message) {
alert("View expired");
};
</script
>Autre alternative, vous pouvez permettre à RichFaces de gérer l'erreur, dans ce cas l'utilisateur aura un message interogatif qui indiquera "View state could't be restored - reload page?" Vous pouvez personnaliser ce message globallement en définissant la clef du message suivant dans le fichier de ressource livré dans l'application.
AJAX_VIEW_EXPIRED=View expired. Please reload the page.
RichFaces (Ajax4jsf) est une bibliothèque Ajax la plus communément utilisée dans Seam, et fourni tous les controls discutés ci-dessous:
eventsQueue — fourni une file d'attente où les évènements sont placés. Tous les évènements sont mis en attente et les requêtes sont envoyées vers le serveur en série. Ceci est utile si la requête vers le serveur peut prendre un certain temps à s'exécuter(par exemple. un calcul lourd, retrouver de l'information d'une source lente) si el serveur n'est pas saturé.
ignoreDupResponses — ignore la réponse produite par la requête si une requête récente et 'similaire' est déjà dans la file d'attente. ignoreDupResponses="true" ne peut annuler l'exécution de la requête du côté serveur — simplement prévenir les mise à jours inutile du côté client.
Cette option devrait être utilisé avec précaution avec les conversations de Seam qui permettent de faire des requêtes concurentielle multiples.
requestDelay — défini le temps (en ms.) que la requête va rester dans la file d'attente. Si la requête n'est pas exécuté après ce delais, la requête sera envoyée (sans regarder si la réponse a été reçu) ou annuler (si il y a une requête similaire plus récente dans la file d'attente).
Cette option devrait être utilisée avec précaution avec les conversations de Seam qui permettent de faire des requêtes concurentielles multiples. Vous devez être sur que le delais défini (en combinaison avec le temps de péremption de requête concurentielle) est plus long que l'action ne va prendre pour s'exécuter.
<a:poll reRender="total" interval="1000" /> — Regroupe le serveur et re-rends une zone nécéssaire
JBoss jBPM est un moteur de gestion du processus métier pour tout environement Java SE ou EE. jBPM vous permet de vous représenter un processus métier ou une intéraction utilisateur comme un graphe de noeud représentant les états d'attente, les décisions, les tâches, les pages web, etc. Le graphe est défini en utilisant un simple et facile à lire dialecte XML appelé jPDL, et peut être édité et visualisé graphiquement en utilisant un plugin d'Eclipse. jPDL est un language extensible et il est adapté pour une grande variété de problème, depuis la définition de l'enchainement de page d'une application web jusqu'à la gestion des traditionnels enchainement de tâches, tout cela dans un esprit d'orchestration des services dans un environnement SOA.
Les applications Seam utilise jBPM pour deux types de problèmes différents:
Définition d'un enchainement de page inclus dans de complexes intéractions de l'utilisateur. Une définition de processus jPDL définie l'enchainement de page pour une seule conversation. Une conversation Seam est considérée pour être une intérraction à relativement courte exécution avec un seul utilisateur.
Définition d'un processus métier hypercintré. Le processus métier peu s'étendre sur de multiples conversations avec de multiples utilisateurs. Son état est persistant dans la base de données jBPM, donc il est considéré comme à longue exécution. La coordination des activités de multiples utilisateurs est un problème beaucoup plus complexe que l'écriture d'une intéraction avec un seul utilisateur, donc jBPM offre des fonctionnalités sophistiquées pour la gestion des tâches et la considération de multiples chemins d'éxécutions concurents.
Ne vous laisser pas embrouillés par ces deux choses ! Elle fonctionne à des niveaux ou de granularité très différents. L'enchainement de page , de conversation et des tâches toutes ce réfèrent à une seule intéraction avec un seul utilisateur. Un processus métier s'étends sur plusieurs tâches. En outre, les deux applications de jBPM sont totalement ortogonales. Vous pouvez les utiliser ensemble ou indépendamment ou pas du tout.
Vous n'avez pas besoin de connaitre jDPL pour utiliser Seam. Si vous êtes parfaitement heureux de définir l'enchainement de page en utilisant JSF ou les règles de navigation de Seam, et si votre application est plus à connatation données qu'à connotation processus, vous n'avez probablement pas besoin de jBPM. Mais nous allons trouver que penser l'interaction utilisateur en terme de représentation graphique bien définie nous aide à construire des applications plus robustes.
Il y a deux façon de définir un enchainement de page dans Seam:
Utiliser les règles de navigation de JSF ou de Seam - le modèle de navigation sans état
Utiliser le jPDL - le modèle de navigation avec état
De très simple applications n'ont seulement besoin que du modèle de navigation sans état. Des applications très complexes auront besoins des deux modèles à des endroits différents. Chaque modèle a ses forces et ses faiblesses!
Le modèle sans état défini une relation depuis un groupe de résultat nomé et la logique d'un évènement directement dans la page résultat de la vue. Les règles de navigation sont entièrement inconnues des autres états inclus dans l'application à part de la page qui a été la source de l'évènement. Cela signifie que les méthodes d'écoute de l'action doivent parfois prendre la décicsion à propos de l'enchainement de page, alors qu'elles n'ont accès qu'à l'état courrant de l'application.
Voici un exemple de définition d'un enchainement de page en utilisant les règles de navigation de JSF:
<navigation-rule>
<from-view-id
>/numberGuess.jsp</from-view-id>
<navigation-case>
<from-outcome
>guess</from-outcome>
<to-view-id
>/numberGuess.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>win</from-outcome>
<to-view-id
>/win.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>lose</from-outcome>
<to-view-id
>/lose.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
Voici un exemple de définition d'un enchainement de page en utilisant les règles de navigation de Seam:
<page view-id="/numberGuess.jsp">
<navigation>
<rule if-outcome="guess">
<redirect view-id="/numberGuess.jsp"/>
</rule>
<rule if-outcome="win">
<redirect view-id="/win.jsp"/>
</rule>
<rule if-outcome="lose">
<redirect view-id="/lose.jsp"/>
</rule>
</navigation>
</page
>
Si vous trouvez que les règles de navigation beaucoup trop verbeuses, vous pouvez retourner l'identifiant de la vue directement depuis la méthode d'écouteur d'action:
public String guess() {
if (guess==randomNumber) return "/win.jsp";
if (++guessCount==maxGuesses) return "/lose.jsp";
return null;
}
Notez que cela résulte dans une redirection. Vous pouvez même spécifier les paramètres à utiliser pour la redirection:
public String search() {
return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}
Le modèle avec état défini un groupe de transition entre un groupe d'état de l'application logique et nomé. Dans ce modèle, il est possible d'exprimer l'enchainement des interactions utilisateur entièrement dans une définition d'enchainement de page en jPDL et écrire les méthode d'écouteur d'action qui sont complètement ignare du flot d'interaction.
Voici un exemple de définition du flot de page en utilisant jPDL:
<pageflow-definition name="numberGuess">
<start-page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</start-page>
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
<decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
<transition name="true" to="lose"/>
<transition name="false" to="displayGuess"/>
</decision>
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation />
</page>
<page name="lose" view-id="/lose.jsp">
<redirect/>
<end-conversation />
</page>
</pageflow-definition
>

Il y a deux choses que nous pouvons noter immédiatement ici:
Les règles de navigation JSF/Seam sont beaucoup plus simples. (Néanmoins, cela masque le fait que le code Java sous-entendu est plus complexe.)
Le jPDL rend les interactions utilisateurs immédiatement compréhensible, sans qu'il soit même nécéssaire de lire le code JSP ou Java.
En plus, le modèle à état est plus constraint. Pour chaque état logique (chaque étape dans l'enchainement de page), il y a un contrainte sur un groupe de transition possible vers les autres états. Le modèle sans état est un modèle ad hoc qui est efficace pour la navigation relativement sans contrainte, libre de formulaire quand l'utilisateur décide là ou il/elle veux aller ensuite, pas l'application.
La distinction entre navigation avec/sans état est assez similaire à la traditionnel vue de l'intération avec/sans modèle. Maintenant, les applications de Seam ne sont pas en général modale au sens strict de ce mot - en fait, éviter la fonctionnalité modal de l'application est une des raisons principale pour avoir les conversations! Malgré tout, les applications Seam peuvent être, et souvent le sont, modale au niveau de conversation particulière. Il est bien connu que la fonctionnalité modale est quelquechose à éviter autant que possible; il est très difficile de prédire dans quel ordre vos utilisateurs vont vouloir faire les choses! Ainsi, il n'y a pas de doutes que le modèle avec état a sa place.
Le plus grand contraste entre les deux modèles est la fonction du bouton-précédent.
Quand les règles de navigation JSF et Seam sont utilisées, Seam laisse l'utilisateur naviger librement avec les boutons précédent, suivant et rafraichir. Il est de la responsabilité de l'application de s'assurer que l'état conversationnel reste en interne consistant quand cela arrive. L'experience avec la combinaisond'application web comme Struts ou WebWork - qui ne supporte pas le modèle conversationnel - et les modèles de composants sans état comme les beans de session sans état EJB ou le serveur d'application Spring a appris à beaucoup de développers qu'il est presque impossible de le faire! Ainsi, notre expérience est que c'est dans le contexte de Seam qu'il y a un modèle conversaionnel bien définie, soutenu par les beans de de sessions avec état, il est actuellement presque prêt. Habituellement, il est presque aussi simple de combiner l'utilisation no-conversation-view-id avec la vérification de null au démarrage des méthodes d'écouteur d'action. Nous considerons le support de la navigation libre de formulaire presque aussi souhaitable.
Dans ce cas, la déclaration de no-conversation-view-id va dans pages.xml. Il indique a Seam de rediriger vers une page différente si une requête originaire d'une page rendue pendant une conversation et que cette conversation n'existe plus:
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
D'un autre côté, dans le modèle avec état, le bouton "précédent" est interprété comme un retour de transition non-définie vers l'état précédent. Depuis que le modèle avec état se renforce comme un groupe définie de transition depuis l'état courrant, le bouton "précédent" est par défaut interdit dans le modèle avec état! Seam de manière transparent détecte l'utilisation du bouton précédent et bloque toute tentative de réaliser une action depuis une page précédente "périmée" et redirige simplement l'utilisateur vers la page "courante" (et affiche un message faces). Que vous considériez comme une fonctionnalité ou une limitation le modèle sans état cela dépend de votre point de vue: comme un développeur d'application, c'est une fonctionnalité, comme un utilisateur, cela peut être frustrant. Vous pouvez activer la navigation avec le bouton "précédent" depuis un noeud de page particulier en indiquant back="enabled".
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
Cela autorise le bouton "précédent" depuis l'état checkout vers tout état précédent!
Bien sûr, nous avons toujours besoin de définir ce qu'il arrive si une requête originiaire d'une page rendue pendant l'enchainement de page, et la conversation ave l'enchainement de page n'existe plus. Dans ce cas, la déclaration no-conversation-view-id va dans la définition d'enchainement de page:
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled"
no-conversation-view-id="/main.xhtml">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
En pratique, les deux modèles de navigation ont leur spécificité et vous allez rapidement apprendre à les reconnaitre quand vous préférerez un modèle plutôt que l'autre.
Nous avons besoin d'installer les composants jBPM-style de Seam et leurs dirent où ils vont trouver la définition de l'enchainement de page. Nous pouvons spécifier cette configuration de Seam dans components.xml.
<bpm:jbpm />
Vous pouvez aussi explicitement indiquer à Seam où trouver la définition d'enchainement de page. Nous spécifions ceci dans components.xml:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value
>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm
>
Nous "démarrons" un enchainement de pages jPDL-style en indiquant le nom de la définition du processus en utilisation une annotation @Begin, @BeginTask ou @StartTask:
@Begin(pageflow="numberguess")
public void begin() { ... }
De manière alternative, nous pouvons démarrer un enchainement de page en utilisant pages.xml:
<page>
<begin-conversation pageflow="numberguess"/>
</page
>
Si nous commençons un enchainement de page pendant la phase RRENDER_RESPONSE — pendant qu'une méthode @Factory ou @Create, par exemple — nous considerons nous même être à la page qui doit être rendue, et utilisons un noeud <start-page> comme premier noeud dans l'enchainement de page, comme le montre l'exemple ci dessous.
Mais si l'enchainement de page a commencé comme le resulteat de l'invocation d'un écouteur d'action, le retour de lécouteur d'action détermine quel est la première page à être rendue. Dans ca cas, nous utilisons un <start-state> comme premier noeud de l'enchainement de page et déclarons une transition pour chaque sortie possible:
<pageflow-definition name="viewEditDocument">
<start-state name="start">
<transition name="documentFound" to="displayDocument"/>
<transition name="documentNotFound" to="notFound"/>
</start-state>
<page name="displayDocument" view-id="/document.jsp">
<transition name="edit" to="editDocument"/>
<transition name="done" to="main"/>
</page>
...
<page name="notFound" view-id="/404.jsp">
<end-conversation/>
</page>
</pageflow-definition
>
Chaque noeud <page> représente un état où le système est en train d'attendre une saisie de l'utilisateur:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
Le view-id est l'identifiant de la vue JSF. L'élément <redirect/> a le même effet que <redirect/> dans une règle de navigation : à savoir, une fonctionnalité poster-puis-rediriger, pour contourner les problème avec le bouton rafraichir du navigateur. (Notez que Seam propage les contextes de conversation au travers de ces redirection de navigateur. Il n'y a donc pas besoin d'un fabrique de style Ruby on Rails "flash" dans Seam!)
Le nom de transition est le nom de la sortie JSF déclenché en cliquant sur le bouton de commande ou le lien de commande dans numberGuess.jsp.
<h:commandButton type="submit" value="Guess" action="guess"/>
Quand la transition est déclenchée par le clic sur le bouton, jBPM ira activer l'action de transition en appelant la méthode guess() du composant numberGuess. Notez que la syntaxe utilisée pour spécifier les actions dans le jPDL est juste une expression familière JSF EL, et que l'action de transation est juste une méthode d'une composant Seam dans le contexte courrant de Seam. Donc, nous avons exactement le même modèle d'évènement pour jBPM que nous avons déjà pour les évènements JSF! (Le principe d'Un Seul Genre de Truc.)
Dans le cas d'un résultat null (par exemple, un bouton de command sans aucune action définie), Seam ira signaler la transition sans aucun nom si une existe, ou sinon simplement réaffiche la page si toutes les transition ont un nom. Donc nous pourrions légèrement simplifier notre exemple d'enchainement de page et ce bouton:
<h:commandButton type="submit" value="Guess"/>
Qui va déclencher la transition sans-nom suivante:
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
Il est même possible d'avoir un bouton qui appele une méthode d'action, dans ce cas le résultat de l'action va déterminer la tranistion qui doit être prise:
<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
<transition name="correctGuess" to="win"/>
<transition name="incorrectGuess" to="evaluateGuess"/>
</page
>
Malgré cela, il est est à considérer comme un style inférieur, car il déplace la responsabilité du contrôl du flot à l'extérieur de la définition de l'enchainement de page et le place dans un autre composants. Il est franchement meilleur de centraliser ce qui s'y rapporte dans l'enchainement de pages lui même.
Habituellement, nous n'avons pas besoin des fonctionnalités les plus puissantes de jPDL pendant la définition des enchainements de pages. Nous avons besoin du noeud <decision>, malgré tout:
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision
>
Une décision est faite en évaluant une expression JSF EL dans les contextes de Seam.
Nous finissons la conversation en utilisant <end-conversation> ou @End. (Dans les faits, pour la lisibilité, l'utilisation des deux est encouragé.)
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation/>
</page
>
De manière optionnel, nous pouvons finir une tâche, spécifiant un nom de transition jBPM. Dans ce cas, Seam va signaler la fin de la tâche courrante dans le processus métier hypercintré.
<page name="win" view-id="/win.jsp">
<redirect/>
<end-task transition="success"/>
</page
>
Il ets possible de composer des enchainements de page et d'avoir une pause dans l'enchainement de page alors qu'un autre enchainement s'exécute. Le noeud <process-state> met en pausse l'enchainement de page indiqué et comme l'exécution de l'enchainement de page appelé:
<process-state name="cheat">
<sub-process name="cheat"/>
<transition to="displayGuess"/>
</process-state
>
Le flot inclus commence en exécutant un noeud <start-state>. Quand il atteind un noeud <end-state>, l'exécution du flot inclus s'arrête et l'exécution du flot englobant recommence avec la transition définie dans l'élement <process-state>.
Un processus métier est un groupe de tâche bien définis qui doivent être réaliser par des utilisateurs ou des systèmes logiciels en accord avec des règles bien définies à propos de qui peut réaliser une tâche, et quand elle devrait être réalisée. L'intégration de JBPM dans Seam rend facile d'afficher la liste des tâches des utilisateurs et les laisser gérer leurs tâches. Seam permet aussi à l'application stocker l'état associé avec le processus métier dans le contexte BUSINESS_PROCESS et avoir cet état qui soit persistant via les variables du jBPM.
Une simple définition de processus métier ressemble assez à la définition de l'enchainement de page (Un Seul Genre de Truc), exception sauf qu'au lieu d'un noeud <page> , nous avons des noeuds <task-node>. Dans un procéssus métier à exécution longue, les état d'atente sont là où le système est en attente qu'un utilisateur se connecte pour réaliser une tâche.
<process-definition name="todo">
<start-state name="start">
<transition to="todo"/>
</start-state>
<task-node name="todo">
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task>
<transition to="done"/>
</task-node>
<end-state name="done"/>
</process-definition
>

Il est parfaitement possible que nous ayons à la fois des définition de processus métier en jPDL et des définitions d'enchainements de pages en jPDL dans le même projet. Et donc, la relation entre les deux est qu'un seul <task> dans un processus métier correspond à l'ensemble d'un enchainement de page <pageflow-definition>
Nous avons besoin d'installer un jBPM, et de lui dire où trouver les définition de processus métier :
<bpm:jbpm>
<bpm:process-definitions>
<value
>todo.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
Les processus jBPM sont persistant au travers du redémarrage de l'application, avec l'utilisation de Seam dans un environement de production vous ne voudriez pas avoir à installer la définition de processus à chaque fois que l'application démarre. Cependant, dans un environement de production vous allez avoir besoin de détruire le processus jBPM à l'extérieur de Seam. En d'autres termes, installez seulement le processus de définitions dans components.xml pendant le développement de votre application.
Nous avons toujours besoin de connaitre quel utilisateur est actuellement connecté. jBPM "connait" les utilisateur par leur identifiant d'acteur et par leurs identifiants de groupe d'acteur. Nous spécifions les identifiant d'acteur courrant en utilisant le composant livré dans Seam nommé actor:
@In Actor actor;
public String login() {
...
actor.setId( user.getUserName() );
actor.getGroupActorIds().addAll( user.getGroupNames() );
...
}