SeamFramework.orgCommunity Documentation

Seam - Les composants contextuels

Un Serveur d'application pour Java Enterprise

2.2.3-SNAPSHOT


Introduction à JBoss Seam
1. Contribuez à Seam
1. Seam Tutorial
1.1. Using the Seam examples
1.1.1. Running the examples on JBoss AS
1.1.2. Running the examples on Tomcat
1.1.3. Running the example tests
1.2. Your first Seam application: the registration example
1.2.1. Understanding the code
1.2.2. How it works
1.3. Clickable lists in Seam: the messages example
1.3.1. Understanding the code
1.3.2. How it works
1.4. Seam and jBPM: the todo list example
1.4.1. Understanding the code
1.4.2. How it works
1.5. Seam pageflow: the numberguess example
1.5.1. Understanding the code
1.5.2. How it works
1.6. A complete Seam application: the Hotel Booking example
1.6.1. Introduction
1.6.2. Overview of the booking example
1.6.3. Understanding Seam conversations
1.6.4. The Seam Debug Page
1.7. Nested conversations: extending the Hotel Booking example
1.7.1. Introduction
1.7.2. Understanding Nested Conversations
1.8. A complete application featuring Seam and jBPM: the DVD Store example
1.9. Bookmarkable URLs with the Blog example
1.9.1. Using "pull"-style MVC
1.9.2. Bookmarkable search results page
1.9.3. Using "push"-style MVC in a RESTful application
2. Démarrage avec Seam en utilisant seam-gen
2.1. Avant de démarrer
2.2. Configurer un nouveau projet Eclipse
2.3. Création d'une nouvelle action
2.4. Création d'un formulaire avec une action
2.5. Generation d'une application depuis une base de données existante
2.6. Génération d'une application depuis des entitées JPA/EJB3 existantes
2.7. Déploiement d'une application avec un EAR
2.8. Seam et le déploiement incrémental à chaud
2.9. Utilisation de Seam avec JBoss 4.0
2.9.1. Installation de JBoss 4.0
2.9.2. Installation de JSF 1.2 RI
3. Démarrer avec Seam en utilisant JBoss Tools
3.1. Avant de commencer
3.2. Configuration d'un nouveau projet Seam
3.3. Création d'une nouvelle action
3.4. La création d'un formulaire avec une action
3.5. La génération d'une application depuis une base de données existante
3.6. Seam et le déploiement incrémental à chaud avec JBoss Tools
4. Le modèle de composant contextuel
4.1. Les contextes de Seam
4.1.1. Le contexte sans état
4.1.2. Le contexte évenementiel
4.1.3. Le contexte de Page
4.1.4. Le contexte conversationnel
4.1.5. Le contexte de Session
4.1.6. Le contexte de processus métier
4.1.7. Le contexte d'Application
4.1.8. Les variables de contexte
4.1.9. Priorité dans la recherche de contexte
4.1.10. Modèle de concurrence
4.2. Les composants de Seam
4.2.1. Les beans de session sans état
4.2.2. Les Beans de session avec état
4.2.3. Les beans entité
4.2.4. JavaBeans
4.2.5. Les beans conducteur-de-message
4.2.6. L'intercepteur
4.2.7. Les noms de component
4.2.8. Définition de l'étendue de composant
4.2.9. Les composants avec des rôles multiples
4.2.10. Les composants livrés
4.3. La bijection
4.4. Les méthode du cycle de vie
4.5. Installation conditionelle
4.6. Mettre des traces
4.7. Les interfaces Mutable et @ReadOnly
4.8. Fabrique et composants gestionnaire
5. Configuring Seam components
5.1. Configuring components via property settings
5.2. Configuring components via components.xml
5.3. Fine-grained configuration files
5.4. Configurable property types
5.5. Using XML Namespaces
6. Evènements, intercepteurs et gestion des exceptions
6.1. Les évènements de Seam
6.2. Les actions de page
6.3. Les paramètres de page
6.3.1. La liaison des paramètres de requêtes avec le modèle
6.4. La propagation des paramètres de requêtes
6.5. La ré-écriture des URL avec les paramètres de page
6.6. La conversion et la validation
6.7. La navigation
6.8. Les fichiers bien dimmensionnés pour la navigation, les actions de page et les paramêtres
6.9. Les évènements conducteur de composant
6.10. Les évènements contextuels
6.11. Les intercepteurs de Seam
6.12. La gestion des exceptions
6.12.1. Les exceptions et les transactions
6.12.2. Activer la gestion d'exception de Seam
6.12.3. Utilisation des annotations pour la gestion d'exception
6.12.4. Utilisation d'XML pour la gestion d'exception
6.12.5. Quelques exceptions communes
7. Les conversations et le gestionnaire de l'espace de travail
7.1. Le modèle conversationnel de Seam
7.2. Les conversations liées
7.3. Démarrage des conversations avec les requêtes GET
7.4. Demander une conversation à exécution longue
7.5. L'utilisation de <s:link> et de <s:button>
7.6. Message de succès
7.7. Utilisation d'un identifiant de conversation "explicite"
7.8. La création d'une conversation explicite
7.9. Redirection vers une conversation explicite
7.10. Le gestionnaire d'espace de travail
7.10.1. Le gestionaire d'espace de trvail et la navigation JSF
7.10.2. Le gestionnaire d'espace de travail et l'enchainement de page jPDL
7.10.3. Le commutateur de conversation
7.10.4. La liste de conversation
7.10.5. Le fil d'ariane
7.11. Les composants conversationnels et la liaison avec les composants JSF
7.12. Les appels concurentiels de composants conversationnels
7.12.1. Comment allons nous construire notre application AJAX conversationnelle?
7.12.2. La gestion des erreurs
7.12.3. RichFaces (Ajax4jsf)
8. L'enchainement des pages et les processus métiers
8.1. L'enchainement de page dans Seam
8.1.1. Les deux modèles de navigation
8.1.2. Seam et le bouton précédent
8.2. Utilisation des enchainements de page jPDL
8.2.1. Installation des enchainements de page
8.2.2. Démarrage des enchainements de pages
8.2.3. Les noeuds de page et transitions
8.2.4. Controler le flot
8.2.5. Terminer le flot
8.2.6. La composition de l'enchainement de page
8.3. La gestion des processus métier dans Seam
8.4. Utilisation des définitions de processus métier jPDL
8.4.1. Installation des définition de processus
8.4.2. Initialisation des identifiants des acteurs
8.4.3. Initialisation d'un processus métier
8.4.4. Assigner une tâche
8.4.5. Liste des tâches
8.4.6. Réalisation d'une tâche
9. Seam and Object/Relational Mapping
9.1. Introduction
9.2. Seam managed transactions
9.2.1. Disabling Seam-managed transactions
9.2.2. Configuring a Seam transaction manager
9.2.3. Transaction synchronization
9.3. Seam-managed persistence contexts
9.3.1. Using a Seam-managed persistence context with JPA
9.3.2. Using a Seam-managed Hibernate session
9.3.3. Seam-managed persistence contexts and atomic conversations
9.4. Using the JPA "delegate"
9.5. Using EL in EJB-QL/HQL
9.6. Using Hibernate filters
10. JSF form validation in Seam
11. L'intégration avec Groovy
11.1. Introduction à Groovy
11.2. L'écriture d'applications Seam en Groovy
11.2.1. Ecriture de composants en Groovy
11.2.2. seam-gen
11.3. Le déploiement
11.3.1. Le déploiement de code Groovy
11.3.2. Le déploiement de fichier .groovy natif au moment du déploiement
11.3.3. seam-gen
12. Writing your presentation layer using Apache Wicket
12.1. Adding Seam to your wicket application
12.1.1. Bijection
12.1.2. Orchestration
12.2. Setting up your project
12.2.1. Runtime instrumentation
12.2.2. Compile-time instrumentation
12.2.3. The @SeamWicketComponent annotation
12.2.4. Defining the Application
13. Le serveur d'application Seam
13.1. Introduction
13.2. Les objets Home
13.3. Le objets Query
13.4. Les objets Controleur
14. Seam et Jboss Rules
14.1. Installation des règles
14.2. L'utilisation des règles d'un composant de Seam
14.3. L'utilisation des règles depuis une définition de processus jBPM
15. Security
15.1. Overview
15.2. Disabling Security
15.3. Authentication
15.3.1. Configuring an Authenticator component
15.3.2. Writing an authentication method
15.3.3. Writing a login form
15.3.4. Configuration Summary
15.3.5. Remember Me
15.3.6. Handling Security Exceptions
15.3.7. Login Redirection
15.3.8. HTTP Authentication
15.3.9. Advanced Authentication Features
15.4. Identity Management
15.4.1. Configuring IdentityManager
15.4.2. JpaIdentityStore
15.4.3. LdapIdentityStore
15.4.4. Writing your own IdentityStore
15.4.5. Authentication with Identity Management
15.4.6. Using IdentityManager
15.5. Error Messages
15.6. Authorization
15.6.1. Core concepts
15.6.2. Securing components
15.6.3. Security in the user interface
15.6.4. Securing pages
15.6.5. Securing Entities
15.6.6. Typesafe Permission Annotations
15.6.7. Typesafe Role Annotations
15.6.8. The Permission Authorization Model
15.6.9. RuleBasedPermissionResolver
15.6.10. PersistentPermissionResolver
15.7. Permission Management
15.7.1. PermissionManager
15.7.2. Permission checks for PermissionManager operations
15.8. SSL Security
15.8.1. Overriding the default ports
15.9. CAPTCHA
15.9.1. Configuring the CAPTCHA Servlet
15.9.2. Adding a CAPTCHA to a form
15.9.3. Customising the CAPTCHA algorithm
15.10. Security Events
15.11. Run As
15.12. Extending the Identity component
15.13. OpenID
15.13.1. Configuring OpenID
15.13.2. Presenting an OpenIdDLogin form
15.13.3. Logging in immediately
15.13.4. Deferring login
15.13.5. Logging out
16. Internationnalisation, les langues locales et les thèmes
16.1. Internationnalisation de votre application.
16.1.1. La configuration du serveur d'application
16.1.2. Les chaines de caractères de l'application traduites
16.1.3. D'autres réglages pour l'encodage
16.2. Les locales
16.3. Les labels
16.3.1. La définition des labels
16.3.2. L'affichage des labels
16.3.3. Les messages Faces
16.4. Les fuseaux horaires
16.5. Les thèmes
16.6. La préservation des préférences de langue et de thème via des cookies
17. Seam Text
17.1. Basic fomatting
17.2. Entering code and text with special characters
17.3. Links
17.4. Entering HTML
17.5. Using the SeamTextParser
18. Génération iText PDF
18.1. Utilisation du support PDF
18.1.1. La création d'un document
18.1.2. Les éléments d textes basiques
18.1.3. Entêtes et pieds de page
18.1.4. Les chapitres et les sections
18.1.5. Les listes
18.1.6. Les tableaux
18.1.7. Les constantes du document
18.2. Diagrammes
18.3. Les codes Bar.
18.4. Remplissage de formulaires
18.5. Le rendu des composants Swing/AWT
18.6. La configuration de iText
18.7. Pour plus de documentation
19. The Microsoft® Excel® spreadsheet application
19.1. Le support d'The Microsoft® Excel® spreadsheet application
19.2. La création d'une simple classeur de travail
19.3. Les Workbooks
19.4. Les feuilles de calculs
19.5. Les colonnes
19.6. Les cellules
19.6.1. La validation
19.6.2. Masque de formatage
19.7. Les formules
19.8. Les images
19.9. Les hyperliens
19.10. Les entêtes et les pieds de page
19.11. Les zones d'impressions et les titres
19.12. Les commandes du classeur de calcul
19.12.1. Le regroupement
19.12.2. Saut de page
19.12.3. La fusion
19.13. Exportateur de tableau de données
19.14. Les polices et les calques
19.14.1. Les liens vers les feuilles de styles
19.14.2. Les polices
19.14.3. Les bordures
19.14.4. Arrière plan
19.14.5. Les réglages de la colonne
19.14.6. Les réglages de la cellule
19.14.7. L'exportateur de base de données
19.14.8. Les exemples de calques
19.14.9. Les limitations
19.15. Internationalisation
19.16. Les liens et de la documentation additionnelle
20. RSS support
20.1. Installation
20.2. Generating feeds
20.3. Feeds
20.4. Entries
20.5. Links and further documentation
21. Email
21.1. Creating a message
21.1.1. Attachments
21.1.2. HTML/Text alternative part
21.1.3. Multiple recipients
21.1.4. Multiple messages
21.1.5. Templating
21.1.6. Internationalisation
21.1.7. Other Headers
21.2. Receiving emails
21.3. Configuration
21.3.1. mailSession
21.4. Meldware
21.5. Tags
22. Asynchronisme et messagerie
22.1. Messagerie dans Seam
22.1.1. La configuration
22.1.2. L'expédition de messages
22.1.3. Réception des messages en utilisant tout bean conducteur de message
22.1.4. La réception des message dans le client
22.2. Asynchronisme
22.2.1. Le sméthodes assynchrones
22.2.2. Les méhtodes assynchrones avec le Dispatcher Quartz
22.2.3. Les évènements assynchrones
22.2.4. La gestion des exceptions pour les appels assynchrones
23. Mise en cache
23.1. Utilisation de JBossCache dans Seam
23.2. La mise en cache de fragment de page
24. Web Services
24.1. Configuration and Packaging
24.2. Conversational Web Services
24.2.1. A Recommended Strategy
24.3. An example web service
24.4. RESTful HTTP webservices with RESTEasy
24.4.1. RESTEasy configuration and request serving
24.4.2. Resources as Seam components
24.4.3. Securing resources
24.4.4. Mapping exceptions to HTTP responses
24.4.5. Exposing entities via RESTful API
24.4.6. Testing resources and providers
25. Remoting
25.1. Configuration
25.2. The "Seam" object
25.2.1. A Hello World example
25.2.2. Seam.Component
25.2.3. Seam.Remoting
25.3. Client Interfaces
25.4. The Context
25.4.1. Setting and reading the Conversation ID
25.4.2. Remote calls within the current conversation scope
25.5. Batch Requests
25.6. Working with Data types
25.6.1. Primitives / Basic Types
25.6.2. JavaBeans
25.6.3. Dates and Times
25.6.4. Enums
25.6.5. Collections
25.7. Debugging
25.8. Handling Exceptions
25.9. The Loading Message
25.9.1. Changing the message
25.9.2. Hiding the loading message
25.9.3. A Custom Loading Indicator
25.10. Controlling what data is returned
25.10.1. Constraining normal fields
25.10.2. Constraining Maps and Collections
25.10.3. Constraining objects of a specific type
25.10.4. Combining Constraints
25.11. Transactional Requests
25.12. JMS Messaging
25.12.1. Configuration
25.12.2. Subscribing to a JMS Topic
25.12.3. Unsubscribing from a Topic
25.12.4. Tuning the Polling Process
26. Seam et le Google Web Toolkit
26.1. La configuration
26.2. La préparation de votre composant
26.3. Interception deu widget GWT vers un composant de Seam
26.4. Les cibles Ant GWT
27. Spring Framework integration
27.1. Injecting Seam components into Spring beans
27.2. Injecting Spring beans into Seam components
27.3. Making a Spring bean into a Seam component
27.4. Seam-scoped Spring beans
27.5. Using Spring PlatformTransactionManagement
27.6. Using a Seam Managed Persistence Context in Spring
27.7. Using a Seam Managed Hibernate Session in Spring
27.8. Spring Application Context as a Seam Component
27.9. Using a Spring TaskExecutor for @Asynchronous
28. L'intégration Guice
28.1. La création d'un composant hybride Seam-Guice
28.2. La configuration d'une injection
28.3. L'utilisation de multiples injecteurs
29. Hibernate Search
29.1. Introduction
29.2. La configuration
29.3. Utilisation
30. Configuring Seam and packaging Seam applications
30.1. Basic Seam configuration
30.1.1. Integrating Seam with JSF and your servlet container
30.1.2. Using Facelets
30.1.3. Seam Resource Servlet
30.1.4. Seam servlet filters
30.1.5. Integrating Seam with your EJB container
30.1.6. Don't forget!
30.2. Using Alternate JPA Providers
30.3. Configuring Seam in Java EE 5
30.3.1. Packaging
30.4. Configuring Seam in J2EE
30.4.1. Boostrapping Hibernate in Seam
30.4.2. Boostrapping JPA in Seam
30.4.3. Packaging
30.5. Configuring Seam in Java SE, without JBoss Embedded
30.6. Configuring Seam in Java SE, with JBoss Embedded
30.6.1. Installing Embedded JBoss
30.6.2. Packaging
30.7. Configuring jBPM in Seam
30.7.1. Packaging
30.8. Configuring SFSB and Session Timeouts in JBoss AS
30.9. Running Seam in a Portlet
30.10. Deploying custom resources
31. Les annotations Seam
31.1. Les annotations pour la définition des composants.
31.2. Les annotations pour les bijections
31.3. Les annotations pour les méthodes de cycle de vie.
31.4. Les annotations pour la démarcation de contexte
31.5. Les annotations utilisées avec les composants JavaBean Seam dans une environnement JEE
31.6. Les annotations pour les exceptions
31.7. Les annotations pour Seam Remoting
31.8. Les annotations pour les intercepteurs de Seam.
31.9. Les annotations pour l'asynchronicité
31.10. Les annotations utilisés avec JSF
31.10.1. Les annotations pour dataTable
31.11. Les metas-annotations pour le databinding
31.12. Les annotations pour le packaging
31.13. Les annotations pour l'intégration avec le conteneur de Servlets
32. Les composants livrés par Seam
32.1. Les composant d'injection de contexte
32.2. Les composants liés à JSF
32.3. Les composants utilitaires
32.4. Les composants pour l'internationnalisation et les thèmes
32.5. Components for controlling conversations
32.6. jBPM-related components
32.7. Security-related components
32.8. JMS-related components
32.9. Mail-related components
32.10. Infrastructural components
32.11. Miscellaneous components
32.12. Special components
33. Les contrôles JSF de Seam
33.1. Les balises
33.1.1. Les contrôle de navigation
33.1.2. Les convertisseurs et les validateurs
33.1.3. Le formatage
33.1.4. Le texte de Seam
33.1.5. Le support de formulaire
33.1.6. Divers
33.2. Les annotations
34. JBoss EL
34.1. Les expressions paramétisées
34.1.1. Utilisation
34.1.2. Les limitations et les astuces
34.2. La projection
35. Mise en cluster et Mise en pause EJB
35.1. Mise en cluster
35.1.1. La programmation pour la mise en cluster
35.1.2. Le déploiement d'une application Seam dans un cluster de JBoss AS avec la réplication de session
35.1.3. La validation de service distributée d'une application s'exécutant dans un cluster de JBoss AS
35.2. Mise en pause EJB et le ManagedEntityInterceptor
35.2.1. La frictions entre la mise en pause et la persistance
35.2.2. Cas d'utilisation #1: Survivre à la mise en pause EJB
35.2.3. Cas d'utilisation #2: La survie de la réplication de la session HTTP
35.2.4. Emballage du ManagedEntityInterceptor
36. Performance Tuning
36.1. Bypassing Interceptors
37. Testing Seam applications
37.1. Unit testing Seam components
37.2. Integration testing Seam components
37.2.1. Using mocks in integration tests
37.3. Integration testing Seam application user interactions
37.3.1. Configuration
37.3.2. Using SeamTest with another test framework
37.3.3. Integration Testing with Mock Data
37.3.4. Integration Testing Seam Mail
38. Seam tools
38.1. jBPM designer and viewer
38.1.1. Business process designer
38.1.2. Pageflow viewer
39. Seam on BEA's Weblogic
39.1. Installation and operation of Weblogic
39.1.1. Installing 10.3
39.1.2. Creating your Weblogic domain
39.1.3. How to Start/Stop/Access your domain
39.1.4. Setting up Weblogic's JSF Support
39.2. The jee5/booking Example
39.2.1. EJB3 Issues with Weblogic
39.2.2. Getting the jee5/booking Working
39.3. The jpa booking example
39.3.1. Building and deploying jpa booking example
39.3.2. What's different with Weblogic 10.x
39.4. Deploying an application created using seam-gen on Weblogic 10.x
39.4.1. Running seam-gen setup
39.4.2. What to change for Weblogic 10.X
39.4.3. Building and Deploying your application
40. Seam on IBM's WebSphere AS v7
40.1. WebSphere AS environment and version recommendation
40.2. Configuring the WebSphere Web Container
40.3. Seam and the WebSphere JNDI name space
40.3.1. Strategy 1: Specify which JNDI name Seam must use for each Session Bean
40.3.2. Strategy 2: Override the default names generated by WebSphere
40.3.3. Strategy 3: Use EJB references
40.4. Configuring timeouts for Stateful Session Beans
40.5. The jee5/booking example
40.5.1. Building the jee5/booking example
40.5.2. Deploying the jee5/booking example
40.5.3. Deviation from the original base files
40.6. The jpa booking example
40.6.1. Building the jpa example
40.6.2. Deploying the jpa example
40.6.3. Deviation from the generic base files
41. Seam avec le serveur d'application GlassFish
41.1. L'environement GlassFish et les informations de déploiement
41.1.1. Installation
41.2. L'exemple jee5/booking
41.2.1. Compilation de l'exemple jee5/booking
41.2.2. Le déploiement de l'application dans GlassFish
41.3. L'exemple de réservation jpa
41.3.1. La construction de l'exemple jpa
41.3.2. Le déploiement de l'exemple jpa
41.3.3. Ce qui est différent pour GlassFish v2 UR2
41.4. Le déploiement d'une application générée par seam-gen sur GlassFish v2 UR2
41.4.1. Exécution du configurateur seam-gen
41.4.2. Les modifications nécéssaires pour le déploiement dans GlassFish
42. Les dépendances
42.1. Les dépendances du JDK
42.1.1. Les considérations sur le JDK6 de Sun
42.2. Les dépendans de projet
42.2.1. Noyau
42.2.2. Les RichFaces
42.2.3. Seam Mail
42.2.4. Seam PDF
42.2.5. Seam Microsoft® Excel®
42.2.6. Le support des RSS de Seam
42.2.7. JBoss Rules
42.2.8. JBPM
42.2.9. GWT
42.2.10. Spring
42.2.11. Groovy
42.3. La gestion des dépendances en utilisant Maven

Seam est un serveur d'application pour Java EE5. Il a été inspiré par les principes suivants:

Une seule sorte de "truc"

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.

Intégrer JSF avec EJB3.0

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.

Integration AJAX

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.

Processus métier intégré comme un premier constructeur de classe

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.

Gestionnaire d'état déclaratif

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 bijection

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.

Gestionnaire d'espace de travail et navigation multi-fenétrée

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!

Préfère les annotations au XML

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).

L'intégration des tests est facile

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.

La spécification n'est pas perfaite

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.

Il y a bien plus pour une application web que de confectionner des pages HTML

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.

Prêt au départ maintenant!

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 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 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-example, so for the registration example the URL would be http://localhost:8080/jboss-seam-registration/. The same is true for examples that deploy as a WAR, as mentioned in the previous section.

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

(1)@Entity

(2)@Name("user")
(3)@Scope(SESSION)
(4)@Table(name="users")
public class User implements Serializable
{
   private static final long serialVersionUID = 1881413500711441951L;
   
(5)   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;
   }
   
(6)   public User() {}
   
(7)   @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;
   }
   
(8)   @Id @NotNull @Length(min=5, max=15)
   public String getUsername()
   {
      return username;
   }
   public void setUsername(String username)
   {
      this.username = username;
   }
}
1

The EJB3 standard @Entity annotation indicates that the User class is an entity bean.

2

A Seam component needs a component name specified by the @Name annotation. This name must be unique within the Seam application. When JSF asks Seam to resolve a context variable with a name that is the same as a Seam component name, and the context variable is currently undefined (null), Seam will instantiate that component, and bind the new instance to the context variable. In this case, Seam will instantiate a User the first time JSF encounters a variable named user.

3

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 @Scope annotation. The User bean is a session scoped component.

4

The EJB standard @Table annotation indicates that the User class is mapped to the users table.

5

name, password and username are the persistent attributes of the entity bean. All of our persistent attributes define accessor methods. These are needed when this component is used by JSF in the render response and update model values phases.

6

An empty constructor is both required by both the EJB specification and by Seam.

7

The @NotNull and @Length annotations are part of the Hibernate Validator framework. Seam integrates Hibernate Validator and lets you use it for data validation (even if you are not using Hibernate for persistence).

8

The EJB standard @Id annotation indicates the primary key attribute of the entity bean.


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    (1)
@Name("register")
public class RegisterAction implements Register
{
   @In
   private Use(2)r user;
   
   @PersistenceContext
   private Ent(3)ityManager em;
   
   @Logger
   private Log(4) log;
   
   public String register()
   {          (5)
      List existing = em.createQuery(
         "select username from User where username = #{user.username}")
         .getR(6)esultList();
         
      if (existing.size()==0)
      {
         em.persist(user);
         log.info("Registered new user #{user.username}");
         retur(7)n "/registered.xhtml";
      }       (8)
      else
      {
         FacesMessages.instance().add("User #{user.username} already exists");
         retur(9)n null;
      }
   }

}
1

The EJB @Stateless annotation marks this class as a stateless session bean.

2

The @In annotation marks an attribute of the bean as injected by Seam. In this case, the attribute is injected from a context variable named user (the instance variable name).

3

The EJB standard @PersistenceContext annotation is used to inject the EJB3 entity manager.

4

The Seam @Logger annotation is used to inject the component's Log instance.

5

The action listener method uses the standard EJB3 EntityManager API to interact with the database, and returns the JSF outcome. Note that, since this is a session bean, a transaction is automatically begun when the register() method is called, and committed when it completes.

6

Notice that Seam lets you use a JSF EL expression inside EJB-QL. Under the covers, this results in an ordinary JPA setParameter() call on the standard JPA Query object. Nice, huh?

7

The Log API lets us easily display templated log messages which can also make use of JSF EL expressions.

8

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.

9

Seam provides a number of built-in components to help solve common problems. The FacesMessages component makes it easy to display templated error or success messages. (As of Seam 2.1, you can use StatusMessages instead to remove the semantic dependency on JSF). Built-in Seam components may be obtained by injection, or by calling the instance() method on the class of the built-in component.


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.

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.


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.


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:


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.


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.


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.

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.

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(1)t<Message> messageList;
   
   @DataModelSelection
   @Out(requir(2)ed=false)
   private Mes(3)sage message;
   
   @PersistenceContext(type=EXTENDED)
   private Ent(4)ityManager em;
   
   @Factory("messageList")
   public void(5) findMessages()
   {
      messageList = em.createQuery("select msg from Message msg order by msg.datetime desc")
                      .getResultList();
   }
   
   public void select()
   {          (6)
      message.setRead(true);
   }
   
   public void delete()
   {          (7)
      messageList.remove(message);
      em.remove(message);
      message=null;
   }
   
   @Remove
   public void(8) destroy() {}

}
1

The @DataModel annotation exposes an attibute of type java.util.List to the JSF page as an instance of javax.faces.model.DataModel. This allows us to use the list in a JSF <h:dataTable> with clickable links for each row. In this case, the DataModel is made available in a session context variable named messageList.

2

The @DataModelSelection annotation tells Seam to inject the List element that corresponded to the clicked link.

3

The @Out annotation then exposes the selected value directly to the page. So every time a row of the clickable list is selected, the Message is injected to the attribute of the stateful bean, and the subsequently outjected to the event context variable named message.

4

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 EntityManager.

5

The first time we navigate to the JSP page, there will be no value in the messageList context variable. The @Factory annotation tells Seam to create an instance of MessageManagerBean and invoke the findMessages() method to initialize the value. We call findMessages() a factory method for messages.

6

The select() action listener method marks the selected Message as read, and updates it in the database.

7

The delete() action listener method removes the selected Message from the database.

8

All stateful session bean Seam components must have a method with no parameters marked @Remove that Seam uses to remove the stateful bean when the Seam context ends, and clean up any server-side state.


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.)

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:


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.


Here we see the use of @In to inject the built-in Actor component.

The JSP itself is trivial:


The second JavaBean is responsible for starting business process instances, and ending tasks.


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.


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>

Note

Seam provides a default JSF date converter for converting a string to a date (no time). Thus, the converter is not necessary for the field bound to #{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(1) name="displayGuess" view-id="/numberGuess.jspx">
      <redirect/>
      <transit(2)ion name="guess" to="evaluateGuess">
         <acti(3)on expression="#{numberGuess.guess}"/>
      </transition>
      <transition name="giveup" to="giveup"/>
      <transition name="cheat" to="cheat"/>
   </start-page>
              (4)
   <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>
1

The <page> element defines a wait state where the system displays a particular JSF view and waits for user input. The view-id is the same JSF view id used in plain JSF navigation rules. The redirect attribute tells Seam to use post-then-redirect when navigating to the page. (This results in friendly browser URLs.)

2

The <transition> element names a JSF outcome. The transition is triggered when a JSF action results in that outcome. Execution will then proceed to the next node of the pageflow graph, after invocation of any jBPM transition actions.

3

A transition <action> is just like a JSF action, except that it occurs when a jBPM transition occurs. The transition action can invoke any Seam component.

4

A <decision> node branches the pageflow, and determines the next node to execute by evaluating a JSF EL expression.


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:


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    (1)
   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;
   }
   
}
1

The first time a JSP page asks for a numberGuess component, Seam will create a new one for it, and the @Create method will be invoked, allowing the component to initialize itself.


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.


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 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     (1)
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@Restrict("#{i(2)dentity.loggedIn}")
public class HotelSearchingAction implements HotelSearching
{
   
   @PersistenceContext
   private EntityManager em;
   
   private String searchString;
   private int pageSize = 10;
   private int page;
   
   @DataModel (3)
   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;
   }
              (4)
   @Remove
   public void destroy() {}
}
1

The EJB standard @Stateful annotation identifies this class as a stateful session bean. Stateful session beans are scoped to the conversation context by default.

2

The @Restrict annotation applies a security restriction to the component. It restricts access to the component allowing only logged-in users. The security chapter explains more about security in Seam.

3

The @DataModel annotation exposes a List as a JSF ListDataModel. This makes it easy to implement clickable lists for search screens. In this case, the list of hotels is exposed to the page as a ListDataModel in the conversation variable named hotels.

4

The EJB standard @Remove annotation specifies that a stateful session bean should be removed and its state destroyed after invocation of the annotated method. In Seam, all stateful session beans must define a method with no parameters marked @Remove. This method will be called when Seam destroys the session context.


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}" 
              (1)      reRender="searchResults" />
       </h:inputText>
       &#160;
       <a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}" 
                        reRender="searchResults"/>
       &#160;
       <a:stat(2)us>
          <f:facet name="start">
             <h:graphicImage value="/img/spinner.gif"/>
          </f:facet>
       </a:status>
       <br/>
       <h:outputLabel for="pageSize">Maximum results:</h:outputLabel>&#160;
       <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(3) 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(4):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>    
1

The RichFaces Ajax <a:support> tag allows a JSF action event listener to be called by asynchronous XMLHttpRequest when a JavaScript event like onkeyup occurs. Even better, the reRender attribute lets us render a fragment of the JSF page and perform a partial page update when the asynchronous response is received.

2

The RichFaces Ajax <a:status> tag lets us display an animated image while we wait for asynchronous requests to return.

3

The RichFaces Ajax <a:outputPanel> tag defines a region of the page which can be re-rendered by an asynchronous request.

4

The Seam <s:link> tag lets us attach a JSF action listener to an ordinary (non-JavaScript) HTML link. The advantage of this over the standard JSF <h:commandLink> is that it preserves the operation of "open in new window" and "open in new tab". Also notice that we use a method binding with a parameter: #{hotelBooking.selectHotel(hot)}. This is not possible in the standard Unified EL, but Seam provides an extension to the EL that lets you use parameters on any method binding expression.

If you're wondering how navigation occurs, you can find all the rules in WEB-INF/pages.xml; this is discussed in Section 6.7, « La navigation ».


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(1)eContext(type=EXTENDED)
   private EntityManager em;
   
   @In 
   private User user;
   
   @In(required=false) @Out
   private Hotel hotel;
   
   @In(required=false) 
   @Out(requir(2)ed=false)
   private Booking booking;
     
   @In
   private FacesMessages facesMessages;
      
   @In
   private Events events;
   
   @Logger 
   private Log log;
   
   private boolean bookingValid;
   
   @Begin     (3)
   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       (4)
   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    (5)
   public void destroy() {}
1

This bean uses an EJB3 extended persistence context, so that any entity instances remain managed for the whole lifecycle of the stateful session bean.

2

The @Out annotation declares that an attribute value is outjected to a context variable after method invocations. In this case, the context variable named hotel will be set to the value of the hotel instance variable after every action listener invocation completes.

3

The @Begin annotation specifies that the annotated method begins a long-running conversation, so the current conversation context will not be destroyed at the end of the request. Instead, it will be reassociated with every request from the current window, and destroyed either by timeout due to conversation inactivity or invocation of a matching @End method.

4

The @End annotation specifies that the annotated method ends the current long-running conversation, so the current conversation context will be destroyed at the end of the request.

5

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.

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(1) 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(2)ed=true)
   public String selectPreference()
   {
      log.info("Room selected");
      
      this.roo(3)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(4)ng cancel()
   {
      log.info("ending conversation");

      return "cancel";
   }

   @Destroy @Remove                                                                      
   public void destroy() {}    
}
1

The hotel instance is injected from the conversation context. The hotel is loaded through an extended persistence context so that the entity remains managed throughout the conversation. This allows us to lazily load the availableRooms through an @Factory method by simply walking the association.

2

When @Begin(nested=true) is encountered, a nested conversation is pushed onto the conversation stack. When executing within a nested conversation, components still have access to all outer conversation state, but setting any values in the nested conversation’s state container does not affect the outer conversation. In addition, nested conversations can exist concurrently stacked on the same outer conversation, allowing independent state for each.

3

The roomSelection is outjected to the conversation based on the @DataModelSelection. Note that because the nested conversation has an independent context, the roomSelection is only set into the new nested conversation. Should the user select a different preference in another window or tab a new nested conversation would be started.

4

The @End annotation pops the conversation stack and resumes the outer conversation. The roomSelection is destroyed along with the conversation context.


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/>
              (1)
            <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>
              (2)  <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">&#160;</div>
            <d(3)iv class="input">
                <s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
            </div>
        </div>    
    </h:form>
</div>
1

When requested from EL, the #{availableRooms} are loaded by the @Factory method defined in RoomPreferenceAction. The @Factory method will only be executed once to load the values into the current context as a @DataModel instance.

2

Invoking the #{roomPreference.selectPreference} action results in the row being selected and set into the @DataModelSelection. This value is then outjected to the nested conversation context.

3

Revising the dates simply returns to the /book.xhtml. Note that we have not yet nested a conversation (no room preference has been selected), so the current conversation can simply be resumed. The <s:button > component simply propagates the current conversation when displaying the /book.xhtml view.


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(1) 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);
              (2)
      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(3)rue, beforeRedirect=true)
   public void cancel() {}
   
   @Destroy @Remove
   public void destroy() {}
}
1

Annotating an action with @End(root=true) ends the root conversation which effectively destroys the entire conversation stack. When any conversation is ended, its nested conversations are ended as well. As the root is the conversation that started it all, this is a simple way to destroy and release all state associated with a workspace once the booking is confirmed.

2

The roomSelection is only associated with the booking on user confirmation. While outjecting values to the nested conversation context will not impact the outer conversation, any objects injected from the outer conversation are injected by reference. This means that any changing to these objects will be reflected in the parent conversation as well as other concurrent nested conversations.

3

By simply annotating the cancellation action with @End(root=true, beforeRedirect=true) we can easily destroy and release all state associated with the workspace prior to redirecting the user back to the hotel selection view.


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.

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:


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.


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:


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:


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&#160;
    <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.

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:

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:

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 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.

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.

JBoss Tools permet le déploiement incrémental à chaud de :

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:

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.

Les 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

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.

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.

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.

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.

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.

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 :

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;
    }
    
    ... 
}

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 :

@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) :

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.

The components.xml file is a bit more powerful than property settings. It lets you:

A components.xml file may appear in one of three different places:

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.

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:

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.

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:

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 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:

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
>

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:

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.

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.

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:

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 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.

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:

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"/>

Quand Seam détermine que cette page est requise à l'extérieure d'une conversation à exécution longue, les actions suivantes interviennent:

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>.

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 :

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 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
>

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:

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:

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:

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 "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.

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>