SeamFramework.orgCommunity Documentation

Seam - Componenti Contestuali

Un framework per Java Enterprise

2.1.2-SNAPSHOT


Introduzione a JBoss Seam
1. Contribuire a Seam
1. Tutorial di Seam
1.1. Utilizzo degli esempi di Seam
1.1.1. Eseguire gli esempi in JBoss AS
1.1.2. Eseguire gli esempi in Tomcat
1.1.3. Eseguire i test degli esempi
1.2. La prima applicazione di Seam: esempio di registrazione
1.2.1. Capire il codice
1.2.2. Come funziona
1.3. Liste cliccabili in Seam: esempio di messaggi
1.3.1. Capire il codice
1.3.2. Come funziona
1.4. Seam e jBPM: esempio di lista todo
1.4.1. Capire il codice
1.4.2. Come funziona
1.5. Seam pageflow: esempio di indovina-numero
1.5.1. Capire il codice
1.5.2. Come funziona
1.6. Un'applicazione Seam completa: esempio di Prenotazione Hotel
1.6.1. Introduzione
1.6.2. Panoramica sull'esempio di prenotazione
1.6.3. Capire le conversazioni in Seam
1.6.4. La pagina di debug di Seam
1.7. Conversazioni Annidate: estendere l'esempio di Prenotazione Hotel
1.7.1. Introduzione
1.7.2. Capire le Conversazioni Annidate
1.8. Un'applicazione completa di Seam e jBPM: esempio di Negozio DVD
1.9. URL segnalibro con l'esempio Blog
1.9.1. Utilizzo di MVC "pull"-style
1.9.2. Pagina bookmarkable dei risultati di ricerca
1.9.3. Uso di MVC push in un'applicazione RESTful
2. Iniziare con Seam usando seam-gen
2.1. Prima di iniziare
2.2. Comfigurare un nuovo progetto Eclipse
2.3. Creazione di una nuova azione
2.4. Creazione di una form con un'azione
2.5. Generazione di un'applicazione da database esistente
2.6. Generazione di un'applicazione da entity JPA/EJB3 già esistenti
2.7. Eseguire il deploy dell'applicazione come EAR
2.8. Seam e hot deploy incrementale
2.9. Uso di Seam con JBoss 4.0
2.9.1. Install JBoss 4.0
2.9.2. Installare JSF 1.2 RI
3. Iniziare con Seam usando JBoss Tools
3.1. Prima di iniziare
3.2. Configurare un nuovo progetto Seam
3.3. Creazione di una nuova azione
3.4. Creazione di una form con un'azione
3.5. Generare un'applicazione da un database esistente
3.6. Seam ed l'hot deploy incrementale con JBoss Tools
4. Il modello a componenti contestuali
4.1. Contesti di Seam
4.1.1. Contesto Stateless
4.1.2. Contesto Evento
4.1.3. Contesto Pagina
4.1.4. Contesto Conversazione
4.1.5. Contesto Sessione
4.1.6. Contesto processo di Business
4.1.7. Contesto Applicazione
4.1.8. Variabili di contesto
4.1.9. Priorità di ricerca del contesto
4.1.10. Modello di concorrenza
4.2. Componenti di Seam
4.2.1. Bean di sessione stateless
4.2.2. Bean di sessione stateful
4.2.3. Entity bean
4.2.4. JavaBeans
4.2.5. Message-driven bean
4.2.6. Intercettazione
4.2.7. Nomi dei componenti
4.2.8. Defining the component scope
4.2.9. Componenti con ruoli multipli
4.2.10. Componenti predefiniti
4.3. Bijection
4.4. Metodi del ciclo di vita
4.5. Installazione condizionale
4.6. Logging
4.7. The Mutable interface and @ReadOnly
4.8. Componenti factory e manager
5. Configurare i componenti Seam
5.1. Configurare i componenti tramire impostazioni di proprietà
5.2. Configurazione dei componenti tramite components.xml
5.3. File di configurazione a grana fine
5.4. Tipi di proprietà configurabili
5.5. Uso dei namespace XML
6. Eventi, interceptor e gestione delle eccezioni
6.1. Eventi di Seam
6.2. Azioni di pagina
6.3. Parametri di pagina
6.3.1. Mapping request parameters to the model
6.4. Parametri di richiesta che si propagano
6.5. URL rewriting with page parameters
6.6. Conversion and Validation
6.7. Navigazione
6.8. Fine-grained files for definition of navigation, page actions and parameters
6.9. Eventi guidati da componenti
6.10. Eventi contestuali
6.11. Interceptor Seam
6.12. Gestione delle eccezioni
6.12.1. Eccezioni e transazioni
6.12.2. Abilitare la gestione delle eccezioni di Seam
6.12.3. Uso delle annotazioni per la gestione delle eccezioni
6.12.4. Uso di XML per la gestione delle eccezioni
6.12.5. Alcune eccezioni comuni
6.13. conversation-required
7. Conversazioni e gestione del workspace
7.1. Il modello di conversazioni di Seam
7.2. Conversazioni innestate
7.3. Avvio di conversazioni con richieste GET
7.4. Usando <s:link> e <s:button>
7.5. Messaggi di successo
7.6. Id di una conversazione naturale
7.7. Creazione di una conversazione naturale
7.8. Redirezione alla conversazione naturale
7.9. Gestione del workspace
7.9.1. Gestione del workspace e navigazione JSF
7.9.2. Gestione del workspace e pageflow jPDL
7.9.3. Lo switcher delle conversazioni
7.9.4. La lista delle conversazioni
7.9.5. Breadcrumbs
7.10. Componenti conversazionali ed associazione ai componenti JSF
7.11. Chiamare concorrenti ai componenti conversazionali
7.11.1. Come si può progettare la nostra applicazione AJAX conversazionale?
7.11.2. Gestione degli errori
7.11.3. RichFaces (Ajax4jsf)
8. Pageflows e processi di business
8.1. Pageflow in Seam
8.1.1. I due modelli di navigazione
8.1.2. Seam ed il pulsante indietro
8.2. Utilizzo dei pageflow jPDL
8.2.1. Installazione dei pageflow
8.2.2. Avvio dei pageflow
8.2.3. Nodi e transizioni di pagina
8.2.4. Controllo del flusso
8.2.5. Fine del flusso
8.2.6. Composizione dei pageflow
8.3. La gestione del processo di business in Seam
8.4. Uso di jPDL nella definizione del processo di business
8.4.1. Installazione delle definizioni di processo
8.4.2. Inizializzazione degli actor id
8.4.3. Iniziare un processo di business
8.4.4. Assegnazione task
8.4.5. Liste di task
8.4.6. Esecuzione di un task
9. Seam e Object/Relational Mapping
9.1. Introduzione
9.2. Transazioni gestite da Seam
9.2.1. Disabilitare le transazioni gestite da Seam
9.2.2. Configurazione di un gestore di transazioni Seam
9.2.3. Sincronizzazione delle transazioni
9.3. Contesti di persistenza gestiti da Seam
9.3.1. Using a Seam-managed persistence context with JPA
9.3.2. Uso delle sessioni Hibernate gestite da Seam
9.3.3. Seam-managed persistence contexts and atomic conversations
9.4. Usare il JPA "delegate"
9.5. Uso di EL in EJB-QL/HQL
9.6. Uso dei filtri Hibernate
10. Validazione delle form JSF in Seam
11. Integrazione con Groovy
11.1. Introduzione a Groovy
11.2. Scrivere applicazioni Seam in Groovy
11.2.1. Scrivere componenti Groovy
11.2.2. seam-gen
11.3. Esecuzione
11.3.1. Eseguire il codice Groovy
11.3.2. Esecuzione di file .groovy durante lo sviluppo
11.3.3. seam-gen
12. Scrivere la parte di presentazione usando Apache Wicket
12.1. Aggiungere Seam ad un'applicazione Wicket
12.1.1. Bijection
12.1.2. Orchestrazione
12.2. Impostare il progetto
12.2.1. Definire l'applicazione
13. Seam Application Framework
13.1. Introduzione
13.2. Oggetti Home
13.3. Oggetti Query
13.4. Oggetti controllori
14. Seam e JBoss Rules
14.1. Installazione delle regole
14.2. Utilizzo delle regole da un componente SEAM
14.3. Utilizzo delle regole da una definizione di processo jBPM
15. Sicurezza
15.1. Panoramica
15.2. Disabilitare la sicurezza
15.3. Autenticazione
15.3.1. Configurare un componente Authenticator
15.3.2. Scrivere un metodo di autenticazione
15.3.3. Scrivere una form di accesso
15.3.4. Riepilogo della configurazione
15.3.5. Ricordami su questo computer
15.3.6. Gestire le eccezioni della sicurezza
15.3.7. Redirezione alla pagina di accesso
15.3.8. Autenticazione HTTP
15.3.9. Caratteristiche di autenticazione avanzate
15.4. Gestione delle identità
15.4.1. Configurare l'IdentityManager
15.4.2. JpaIdentityStore
15.4.3. LdapIdentityStore
15.4.4. Scrivere il proprio IdentityStore
15.4.5. L'autenticazione con la gestione delle identità
15.4.6. Usare IdentityManager
15.5. Messaggi di errore
15.6. Autorizzazione
15.6.1. Concetti principali
15.6.2. Rendere sicuri i componenti
15.6.3. La sicurezza nell'interfaccia utente
15.6.4. Rendere sicure le pagine
15.6.5. Rendere sicure le entità
15.6.6. Annotazioni tipizzate per i permessi
15.6.7. Annotazioni tipizzate per i ruoli
15.6.8. Il modello di autorizzazione dei permessi
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. Internazionalizzazione, localizzazione e temi
16.1. Internazionalizzare un'applicazione
16.1.1. Configurazione dell'application server
16.1.2. Traduzione delle stringhe dell'applicazione
16.1.3. Altre impostazioni per la codifica
16.2. Traduzioni
16.3. Etichette
16.3.1. Definire le etichette
16.3.2. Mostrare le etichette
16.3.3. Messaggi Faces
16.4. Fusi orari
16.5. Temi
16.6. Registrare la scelta della lingua e del tema tramite cookies
17. Seam Text
17.1. Formattazione di base
17.2. Inserire codice e testo con caratteri speciali
17.3. Link
17.4. Inserire codice HTML
18. Generazione di PDF con iText
18.1. Utilizzo del supporto PDF
18.1.1. Creazione di un documento
18.1.2. Elementi base per il testo
18.1.3. Intestazioni e pié di pagina
18.1.4. Capitoli e Sezioni
18.1.5. Liste
18.1.6. Tabelle
18.1.7. Costanti nei documenti
18.2. Grafici
18.3. Codici a barre
18.4. Form da riempire
18.5. Componenti per i rendering Swing/AWT
18.6. Configurazione di iText
18.7. Ulteriore documentazione
19. The Microsoft® Excel® spreadsheet application
19.1. Supporto The Microsoft® Excel® spreadsheet application
19.2. Creazione di un semplice workbook
19.3. Workbooks
19.4. Worksheets
19.5. Colonne
19.6. Celle
19.6.1. Validazione
19.6.2. Maschere per il formato
19.7. Formule
19.8. Immagini
19.9. Hyperlinks
19.10. Intestazioni e pié di pagina
19.11. Stampa di aree e titoli
19.12. Comandi per i fogli di lavoro (worksheet)
19.12.1. Raggruppamento
19.12.2. Interruzioni di pagina
19.12.3. Fusione (merge)
19.13. Esportatore di datatable
19.14. Font e layout
19.14.1. Link ai fogli di stile
19.14.2. Font
19.14.3. Bordi
19.14.4. Background
19.14.5. Impostazioni colonna
19.14.6. Impostazioni cella
19.14.7. L'exporter delle datatable
19.14.8. Esempi di layout
19.14.9. Limitazioni
19.15. Internazionalizzazione
19.16. Link ed ulteriore documentazione
20. Supporto RSS
20.1. Installazione
20.2. Generare dei feed
20.3. I feed
20.4. Elementi
20.5. Link e ulteriore documentazione
21. Email
21.1. Creare un messaggio
21.1.1. Allegati
21.1.2. HTML/Text alternative part
21.1.3. Destinatari multipli
21.1.4. Messaggi multipli
21.1.5. Comporre template
21.1.6. Internazionalizzazione
21.1.7. Altre intestazioni
21.2. Ricevere email
21.3. Configurazione
21.3.1. mailSession
21.4. Meldware
21.5. Tag
22. Asincronicità e messaggistica
22.1. Asincronicità
22.1.1. Metodi asincroni
22.1.2. Metodi asincroni con il Quartz Dispatcher
22.1.3. Eventi asincroni
22.1.4. Gestione delle eccezione da chiamate asincrone
22.2. Messaggistica in Seam
22.2.1. Configurazione
22.2.2. Spedire messaggi
22.2.3. Ricezione dei messaggi usando un bean message-driven
22.2.4. Ricezione dei messaggi nel client
23. Gestione della cache
23.1. Usare la cache in Seam
23.2. Cache dei frammenti di pagina
24. Web Service
24.1. Configurazione ed impacchettamento
24.2. Web Service conversazionali
24.2.1. Una strategia raccomandata
24.3. Esempio di web service
24.4. Webservice RESTful HTTP con RESTEasy
24.4.1. RESTEasy configuration and request serving
24.4.2. Risorse e provider come componenti Seam
24.4.3. Mapping exceptions to HTTP responses
25. Remoting
25.1. Configurazione
25.2. L'oggetto "Seam"
25.2.1. Esempio Hello World
25.2.2. Seam.Component
25.2.3. Seam.Remoting
25.3. Valutazione delle espressioni EL
25.4. Interfacce client
25.5. Il contesto
25.5.1. Impostazione e lettura dell'ID di conversazione
25.5.2. Remote calls within the current conversation scope
25.6. Richieste batch
25.7. Lavorare con i tipi di dati
25.7.1. Primitives / Basic Types
25.7.2. JavaBeans
25.7.3. Date e orari
25.7.4. Enums
25.7.5. Collections
25.8. Debugging
25.9. Gestione delle eccezioni
25.10. Il messaggio di caricamento
25.10.1. Cambiare il messaggio
25.10.2. Nascondere il messaggio di caricamento
25.10.3. A Custom Loading Indicator
25.11. Controlling what data is returned
25.11.1. Constraining normal fields
25.11.2. Constraining Maps and Collections
25.11.3. Constraining objects of a specific type
25.11.4. Combining Constraints
25.12. Richieste transazionali
25.13. Messaggistica JMS
25.13.1. Configurazione
25.13.2. Iscriversi ad un Topic JMS
25.13.3. Disiscriversi da un Topic
25.13.4. Tuning the Polling Process
26. Seam e il Google Web Toolkit
26.1. Configurazione
26.2. Preparare i componenti
26.3. Collegare un componente GWT ad un componente Seam
26.4. Target Ant per 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. Hibernate Search
28.1. Introduzione
28.2. Configurazione
28.3. Utilizzo
29. Configurare Seam ed impacchettare le applicazioni Seam
29.1. Configurazione base di Seam
29.1.1. Integrazione di Seam con JSF ed il servlet container
29.1.2. Usare Facelets
29.1.3. Resource Servlet di Seam
29.1.4. Filtri servlet di Seam
29.1.5. Integrating Seam with your EJB container
29.1.6. Non dimenticare!
29.2. Uso di provider JPA alternativi
29.3. Configurazione di Seam in java EE 5
29.3.1. Packaging
29.4. Configurare Seam in J2EE
29.4.1. Boostrapping di Hibernate in Seam
29.4.2. Boostrapping di JPA in Seam
29.4.3. Packaging
29.5. Configurazione di Seam in java EE 5 senza JBoss Embedded
29.6. Configurazione di Seam in java EE 5 con JBoss Embedded
29.6.1. Installare JBoss Embedded
29.6.2. Packaging
29.7. Configurazione jBPM in Seam
29.7.1. Packaging
29.8. Configurazione di SFSB e dei timeout di sessione in JBoss AS
29.9. Esecuzione di Seam in un Portlet
29.10. Deploying custom resources
30. Annotazioni di Seam
30.1. Annotazioni per la definizione di un componente
30.2. Annotazioni per la bijection
30.3. Annotazioni per i metodi del ciclo di vita dei componenti
30.4. Annotazioni per la demarcazione del contesto
30.5. Annotazioni per l'uso con i componenti JavaBean di Seam in ambiente J2EE
30.6. Annotazioni per le eccezioni
30.7. Annotazioni per Seam Remoting
30.8. Annotazioni per gli interceptor di Seam
30.9. Annotazioni per l'asincronicità
30.10. Annotazioni per l'uso di JSF
30.10.1. Annotazioni per l'uso con dataTable
30.11. Meta-annotationi per il databinding
30.12. Annotazioni per i pacchetti
30.13. Annotazioni per l'integrazione con un servlet container
31. Componenti Seam predefiniti
31.1. Componenti per l'iniezione del contesto
31.2. Componenti JSF
31.3. Componenti d'utilità
31.4. Componenti per l'internazionalizzazione ed i temi
31.5. Componenti per il controllo delle conversazioni.
31.6. Componenti per jBPM
31.7. Componenti per la sicurezza
31.8. Componenti per JMS
31.9. Componenti relativi alla Mail
31.10. Componenti infrastrutturali
31.11. Componenti misti
31.12. Componenti speciali
32. Controlli JSF di Seam
32.1. Tag
32.1.1. Controlli di navigazione
32.1.2. Convertitori e Validatori
32.1.3. Formattazione
32.1.4. Seam Text
32.1.5. Supporto per le form
32.1.6. Altro
32.2. Annotazioni
33. JBoss EL
33.1. Espressioni parametrizzate
33.1.1. Utilizzo
33.1.2. Limitazioni e suggerimenti
33.2. Proiezione
34. Tuning delle performance
34.1. Bypassare gli interceptor
35. Test delle applicazioni Seam
35.1. Test d'unità dei componenti Seam
35.2. Test d'integrazione dei componenti Seam
35.2.1. Uso dei mock nei test d'intergrazione
35.3. Test d'integrazione delle interazioni utente in applicazioni Seam
35.3.1. Configurazione
35.3.2. Uso di SeamTest con un altro framework di test
35.3.3. Test d'integrazione con Dati Mock
35.3.4. Test d'integrazione di Seam Mail
36. Strumenti di Seam
36.1. Visualizzatore e designer jBPM
36.1.1. Designer del processo di business
36.1.2. Visualizzatore Pageflow
37. Seam on BEA's Weblogic
37.1. Installation and operation of Weblogic
37.1.1. Installing 10.3
37.1.2. Creating your Weblogic domain
37.1.3. How to Start/Stop/Access your domain
37.1.4. Setting up Weblogic's JSF Support
37.2. The jee5/booking Example
37.2.1. EJB3 Issues with Weblogic
37.2.2. Getting the jee5/booking Working
37.3. The jpa booking example
37.3.1. Building and deploying jpa booking example
37.3.2. What's different with Weblogic 10.x
37.4. Deploying an application created using seam-gen on Weblogic 10.x
37.4.1. Running seam-gen setup
37.4.2. What to change for Weblogic 10.X
37.4.3. Building and Deploying your application
38. Seam on IBM's Websphere AS
38.1. Websphere AS environment and deployment information
38.1.1. Installation versions
38.2. The jee5/booking example
38.2.1. Configuration file changes
38.2.2. Building the jee5/booking example
38.2.3. Deploying the application to Websphere
38.3. The jpa booking example
38.3.1. Building the jpa example
38.3.2. Deploying the jpa example
38.3.3. What's different for Websphere AS V7
38.4. Deploying an application created using seam-gen on Websphere V7
38.4.1. Running seam-gen Setup
38.4.2. Changes needed for deployment to Websphere
39. Seam on GlassFish application server
39.1. GlassFish environment and deployment information
39.1.1. Installazione
39.2. The jee5/booking example
39.2.1. Building the jee5/booking example
39.2.2. Deploying the application to GlassFish
39.3. The jpa booking example
39.3.1. Building the jpa example
39.3.2. Deploying the jpa example
39.3.3. What's different for GlassFish v2 UR2
39.4. Deploying an application generated by seam-gen on GlassFish v2 UR2
39.4.1. Running seam-gen Setup
39.4.2. Changes needed for deployment to GlassFish
40. Dipendenze
40.1. Dipendenze JDK
40.1.1. Considerazioni su JDK 6 di Sun
40.2. Dipendenze del progetto
40.2.1. Core
40.2.2. RichFaces
40.2.3. Seam Mail
40.2.4. Seam PDF
40.2.5. Seam Microsoft® Excel®
40.2.6. Supporto Seam RSS
40.2.7. JBoss Rules
40.2.8. JBPM
40.2.9. GWT
40.2.10. Spring
40.2.11. Groovy
40.3. Gestione delle dipendenze usando Maven

Seam è un'application framework per Java Enterprise. Si ispira ai seguenti principi:

Unico tipo di "cosa"

Seam definisce un modello uniforme a componenti per tutte le business logic dell'applicazione. Un componente Seam può essere stateful, con uno stato associato ad uno dei tanti contesti ben-definiti, che includono long-running, persistenza, contesto del processo di business e il contesto conversazionale, che viene preservato lungo le diverse richieste web durante l'interazione dell'utente.

Non c'è alcuna distinzione in Seam tra i componenti del livello presentazione ed i componenti di business logic. Si può stratificare l'applicazione secondo una qualsiasi architettura a proprio piacimento, piuttosto che essere forzati a modellare la logica dell'applicazione in uno schema innaturale con una qualsiasi combinazione di framework aggrovigliati come avviene oggi.

A differenza dei componenti J2EE o del semplice Java EE, i componenti Seam possono simultaneamente accedere allo stato associato alla richiesta web e allo stato mantenuto nelle risorse transazionali (senza il bisogno di propagare manualmente lo stato della richiesta web attraverso i parametri). Si potrebbe obbiettare che la stratificazione dell'applicazione imposta dalla vecchia piattaforma J2EE fosse una Cosa Buona. Bene, niente vieta di creare un'architettura a strati equivalente usando Seam — la differenza è che tu decidi l'architettura dell'applicazione e decidi quali sono i layer e come lavorano assieme

Integrazione di JSF con EJB 3.0

JSF e EJB 3.0 sono due delle migliori caratteristiche di Java EE 5. EJB3 è nuovo modello a componenti per la logica di business e di persistenza lato server. Mentre JSF è un eccezionale modello a componenti per il livello di presentazione. Sfortunatamente, nessuno dei due componenti da solo è capace di risolvere tutti i problemi. Invece JSE e EJB3 utilizzati assieme funzionano meglio. Ma la specifica Java EE 5 non fornisce alcuno standard per integrare i due modelli a componenti. Fortunatamente, i creatori di entrambi i modelli hanno previsto questa situazione ed hanno elaborato delle estensioni allo standard per consentire un ampiamento ed un'integrazione con altri framework.

Seam unifica i modelli a componenti di JSF e EJB3, eliminando il codice colla, e consentendo allo sviluppatore di pensare al problema di business.

E' possibile scrivere applicazioni Seam dove "qualsiasi cosa" sia un EJB. Questo potrebbe essere sorprendente, se si è abituati a pensare agli EJB come ad oggetti cosiddetti a grana-grossa, "di peso massimo". Comunque la versione 3.0 ha completamente cambiato la natura di EJB dal punto di vista dello sviluppatore. Un EJB è un oggetto a grana-fine — non più complesso di un JavaBean con annotazioni. Seam incoraggia ad usare i session bean come action listener JSF!

Dall'altro lato, se si preferisce non adottare EJB 3.0 adesso, è possibile non farlo. Virtualmente ogni classe java può essere un componente Seam, e Seam fornisce tutte le funzionalità che ci si attende da un "lightweight" container, ed in più, per ogni componente, EJB o altro.

AJAX integrato

Seam supporta le migliori soluzioni open source AJAX basate su JSF: JBoss RichFaces e ICEFaces. Queste soluzioni ti permettono di aggiungere funzionalità AJAX all'interfaccia utente senza il bisogno di scrivere codice JavaScript.

In alternativa Seam fornisce al suo interno uno strato remoto di JavaScript che ti consente di chiamare i componenti in modo asincrono da JavaScript lato client senza il bisogno di uno strato di azione intermedio. Si può anche sottoscrivere topic JMS lato server e ricevere messaggi tramite push AJAX.

Nessuno di questi approcci funzionerebbe bene, se non fosse per la gestione interna di Seam della concorrenza e dello stato, la quale assicura che molte richieste AJAX a grana fine (fine-grained) concorrenti e asincrone vengano gestite in modo sicuro ed efficiente lato server.

Processo di business come primo costrutto di classe

Opzionalmente Seam può fornire una gestione trasparente del processo di business tramite jBPM. Non ci crederai quanto è facile implementare workflow complessi, collaborazioni ed una gestione dei compiti utilizzando jBPM e Seam.

Seam consente pure di definire il pageflow del livello di presentazione utilizzando lo stesso linguaggio (jPDL) che jBPM utilizza per la definizione dei processi di business.

JSF fornisce un ricco ed incredibile modello a eventi per il livello di presentazione. Seam migliora questo modello esponendo gli eventi del processo di business jBPM attraverso lo stesso identico meccanismo di gestione eventi, dando un modello a eventi uniforme per l'intero modello a componenti di Seam.

Gestione dichiarativa dello stato

Siamo tutti abituati al concetto di gestione dichiarativa delle transazioni e sicurezza dichiarativa fin dai primi giorni di EJB. EJB3 introduce anche la gestione dichiarativa del contesto di persistenza. Ci sono tre esempi di un ampio problema di gestione dello stato che è associato ad un particolare contesto, mentre tutte i dovuti cleanup avvengono quando il contesto termina. Seam porta oltre il concetto di gestione dichiarativa dello stato e lo applica allo stato dell'applicazione. Tradizionalmente le applicazioni J2EE implementano manualmente la gestione dello stato con il get e set della sessione servlet e degli attributi di richiesta. Questo approccio alla gestione dello stato è l'origine di molti bug e problemi di memoria (memory leak) quando l'applicazione non riesce a pulire gli attributi di sessione, o quando i dati di sessione associati a diversi workflow collidono all'interno di un'applicazione multi-finestra. Seam ha il potenziale per eliminare quasi interamente questa classe di bug.

La gestione dichiarativa dello stato dell''applicazione è resa possibile grazie alla ricchezza del modello di contesto definito da Seam. Seam estende il modello di contesto definito dalla specifica servlet — richiesta, sessione, applicazione — con due nuovi contesti — conversazione e processo di business — che sono più significatici dal punto di vista della logica di business.

Resterai stupito di come molte cose divengano più semplici non appena inizia ad usare le conversazioni. Hai mai sofferto nell'utilizzo dell'associazione lazy in una soluzione ORM come Hibernate o JPA? I contesti di Seam di persistenza basati sulle conversazioni ti consentiranno di vedere raramente una LazyInitializationException. Hai mai avuto problemi con il pulsante di aggiornamento? Con il pulsante indietro? Con una form inviata due volte? Con la propagazione di messaggi attraverso un post-then-redirect? La gestione delle conversazioni di Seam risolve questi problemi senza che tu debba pensarci. Questi sono tutti sintomi di un'architettura errata di gestione dello stato che è stata prevalente fin dai primi giorni della comparsa del web.

Bijection

La nozione di Inversione del Controllo o dependency injection esiste in entrambi JSF e EJB3, così come in numerosi così chiamati lightweight container. La maggior parte di questi container predilige l'injection di componenti che implementano servizi stateless. Anche quando l'injection di componenti stateful è supportata (come in JSF), è virtualmente inutile per la gestione dello stato dell'applicazione poiché lo scope del componente stateful non può essere definita con sufficiente flessibilità e poiché i componenti appartenenti a scope più ampi potrebbero non essere iniettati nei componenti appartenenti a scope più ristretti.

La Bijection differisce da IoC poiché è dinamica, contestuale, e bidirezionale. E' possibile pensare ad essa come un meccanismo per la denominazione di variabili contestuali (nomi in vari contesti legati al thread corrente) in attributi dei componenti. La bijection consente l'autoassemblamento dei componenti da parte del container. Permette pure che un componente possa in tutta sicurezza e semplicità manipolare il valore di una variabile di contesto, solamente assegnandola ad un attributo del componente.

Gestione del workspace e navigazione multi-finestra

Le applicazioni Seam consentono all'utente di passare liberamente a più tab del browser, ciascuno associato ad una conversazione differente ed isolata. Le applicazioni possono addirittura avvantaggiarsi della gestione del workspace, consentendo all'utente di spostarsi fra le varie conversazioni (workspace) all'interno del singolo tab del browser. Seam fornisce non solo una corretta funzionalità multi-finestra, ma anche funzionalità multi-finestra in una singola finestra!

Preferenza delle annotazioni all'XML

Tradizionalmente la comunità Java è sempre stata in uno stato di profonda confusione su quali tipologie di meta-informazione debbano essere considerate configurazione. J2EE ed i più noti lightweight container hanno entrambi fornito descrittori per il deploy basati su XML per cose che sono configurabili tra differenti deploy del sistema e per altri tipi di cose o dichiarazioni che non sono facilmente esprimibili in Java. Le annotazioni Java 5 hanno cambiato tutto questo.

EJB 3.0 sceglie le annotazioni e la "configurazione tramite eccezione" come il miglior modo per fornire informazioni al container in una forma dichiarativa. Sfortunatamente JSF è fortemente dipendente da file XML di configurazione molto lunghi. Seam estende le annotazioni fornite da EJB 3.0 con un set di annotazioni per la gestione dichiarativa dello stato e la demarcazione dichiarativa del contesto. Questo consente di eliminare le noiose dichiarazioni JSF dei bean gestiti e riduce l'XML richiesto alla sola informazione che veramente appartiene a XML (le regole di navigazione JSF).

I test d'integrazione sono facili

I componenti Seam, essendo semplici classi Java, sono per natura unità testabili. Ma per le applicazioni complesse, il test dell'unità (testing unit) da solo è insufficiente. Il test d'integrazione (integration testing) è tradizionalmente stato complicato e difficile per le applicazioni web Java. Seam fornisce la testabilità delle applicazioni come caratteristica essenziale del framework. Si potranno facilmente scrivere test JUnit o TestNG che riproducano tutta l'interazione con l'utente, provando tutti i componenti del sistema separati dalla vista (pagina JSP o Facelet). Si potranno eseguire questi test direttamente dentro il proprio IDE, dove Seam automaticamente eseguirà il deploy dei componenti EJB usando JBoss Embedded.

Le specifiche non sono perfette

Pensiamo che l'ultima incarnazione di Java EE sia ottima. Ma sappiamo che non sarà mai perfetta. Dove ci sono dei buchi nella specifica (per esempio limitazioni nel ciclo di vita JSF per le richieste GET), Seam li risolve. E gli autori di Seam stanno lavorando con i gruppi esperti JCP per assicurare che queste soluzioni siano incorporate nelle prossime revisioni degli standard.

Una web application non genera soltanto pagine html ma fa molto di più

I web framework di oggi pensano troppo poco. Ti consentono di estrarre gli input dell'utente da una form e di metterlo in un oggetto Java. E poi ti abbandonano. Un vero web framework dovrebbe indirizzarsi verso problemi come la persistenza, la concorrenza, l'asincronicità, la gestione dello stato, la sicurezza, le email, la messaggistica, la generazione di PDF e grafici, il workflow, il rendering di wikitext, i web service, il caching e altro ancora. Dopo aver provato Seam, si resterà stupiti di come questi problemi vengano semplificati...

Seam integra JPA e Hibernate3 per la persistenza, EJB Timer Service e Quartz per l'asincronicità, jBPM per i workflow, JBoss Rules per le regole di business, Meldware Mail per le email, Hibernate Search e Lucene per la ricerca full text, JMS per la messaggistica e JBoss Cache per il caching delle pagine. Seam pone un framework di sicurezza basato sulle regole sopra JAAS JBoss Rules. Ci sono anche le librerie JSP per la creazione dei PDF, le mail in uscita, i grafici ed il testo wiki. I componenti Seam possono essere chiamati in modo sincrono come un Web Service, oppure in modo asincrono da JavaScript lato client o da Google Web Toolkit o, sicuramente, direttamente da JSF.

Inizia ora!

Seam funziona in qualsiasi application server Java EE, e perfino in Tomcat. Se il proprio ambiente supporta EJB 3.0, benissimo! Altrimenti, nessun problema, si può utilizzare la gestione delle transazioni interna a Seam con JPA o Hibernate3 per la persistenza. Oppure si può fare il deploy di JBoss Embedded in Tomcat, ed ottenere pieno supporto per EJB 3.0.

La combinazione di Seam, JSF e EJB3 è il modo più semplice per scrivere una complessa applicazione web in Java. Ci si stupirà di quanto poco codice viene richiesto!

Visita SeamFramework.org per scoprire come contribuire a Seam!

Seam fornisce un ampio numero di applicazioni d'esempio per mostrare l'uso delle varie funzionalità di Seam. Questo tutorial ti guiderà attraverso alcuni di questi esempi per aiutarti nell'apprendimento di Seam. Gli esempi di Seam sono posizionati nella sottodirectory examples della distribuzione Seam. L'esempio di registrazione, che è il primo esempio che vediamo, si trova nella directory examples/registration.

Ciascun esempio ha la medesima struttura di directory:

Le applicazioni d'esempio girano sia su JBoss AS sia su Tomcat senza configurazioni aggiuntive. Le sezioni seguenti spiegano la procedura in entrambi i casi. Nota che tutti gli esempi sono costruiti ed eseguiti da build.xml di Ant, e quindi ti servirà installata una versione recente di Ant prima di iniziare.

Questi esempi sono configurati anche per essere usati in Tomcat 6.0. Occorreràseguire le istruzioni in Sezione 29.6.1, «Installare JBoss Embedded» per installare JBoss Embedded in Tomcat 6.0. JBoss Embedded è richiesto per eseguire le demo di Seam che usano componenti EJB3 in Tomcat. Ci sono anche esempio di applicazioni non-EJB3 che possono funzionare in Tomcat senza JBoss Embedded.

Occorrerà impostare al percorso di Tomcat la variabile tomcat.home, la quale si trova nel file condiviso build.properties della cartella padre nell'installazione di Seam.

Dovrai usare un diverso target Ant per utilizzare Tomcat. Usa ant tomcat.deploy nella sotto-directory d'esempio per il build ed il deploy in Tomcat.

Con Tomcat gli esempi vengono deployati con URL del tipo /jboss-seam-example, così per l'esempio di registrazione, l'URL sarebbe http://localhost:8080/jboss-seam-registration/. Lo stesso vale per gli esempi che vengono deployati come WAR, come già detto nella precedente sezione.

L'esempio di registrazione è una semplice applicazione per consentire all'utente di memorizzare nel database il proprio username, il nome vero e la password. L'esempio non vuole mostrare tutte le funzionalità di Seam. Comunque mostra l'uso di un EJB3 session bean come JSF action listener e la configurazione base di Seam.

Andiamo piano, poiché ci rendiamo conto che EJB 3.0 potrebbe non essere familiare.

La pagina iniziale mostra una form molto semplice con tre campi d'input. Si provi a riempirli e ad inviare la form. Verrà salvato nel database un oggetto user.

Questo esempio è implementato con due template Facelets, un entity bean e un session bean stateless. Si guardi ora il codice, partendo dal "basso".

Occorre un entity bean EJB per i dati utente. Questa classe definisce persistenza e validazione in modo dichiarativo tramite le annotazioni. Ha bisogno anche di altre annotazioni per definire la classe come componente Seam.

Esempio 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

L'annotazione EJB3 standard @Entity indica che la classe User è un entity bean.

2

Un componente Seam ha bisogno di un nome componente specificato dall'annotazione @Name . Questo nome deve essere unico all'interno dell'applicazione Seam. Quando JSF chiede a Seam di risolvere una variabile di contesto son un nome che corrisponde ad un componente Seam, e la variabile di contesto è indefinita (null), Seam istanzia quel componente e lo associa la nuova istanza alla variabile di contesto. In questo caso Seam istanzierà uno User la prima volta che JSF incontrarerà una variabile chiamata user.

3

Quando Seam istanzia un componente, associa la nuova istanza alla variabile di contesto nel contesto di default del componente. Il contesto di default viene specificato usando l'annotazione @Scope . Il bean User è un componente con scope di sessione.

4

L'annotazione standard EJB @Table indica che la classe User è mappata sulla tabella users.

5

name, password e username sono gli attributi di persistenza dell'entity bean. Tutti gli attributi di persistenza definiscono i metodi d'accesso. Questi sono necessari quando il componente viene usato da JSF nelle fasi di generazione risposta (render response) e aggiornamento dei valori del modello (update model values).

6

Un costruttore vuoto è richiesto sia dalla specifica EJB sia da Seam.

7

Le annotazioni @NotNull e @Length sono parte del framework Hibernate Validator. Seam integra Hibernate Validator e consente di usarlo per la validazione dei dati (anche se non viene usato Hibernate per la persistenza).

8

L'annotazione standard EJB @Id indica l'attributo di chiave primaria di un entity bean.


Le cose più importanti da notare in quest'esempio sono le annotazioni @Name e @Scope. Queste annotazioni stabiliscono che questa classe è un componente Seam.

Si vedrà sotto che le proprietà della classe User sono legate direttamente ai componenti JSF e sono popolati da JSF durante la fase di aggiornamento dei valori del modello ("update model values"). Non occorre nessun codice colla per copiare i dati avanti ed indietro tra le pagine JSP ed il modello di dominio degli entity bean.

Comunque, gli entity bean non dovrebbero occuparsi della gestione delle transazioni o dell'accesso al database. Quindi non si può usare questo componente come action listener JSF. Per questo occorre un session bean.

La maggior parte delle applicazioni Seam utilizzano session bean come action listener JSF (si possono utilizzare JavaBean se si vuole).

C'è esattamente una azione JSF nell'applicazione ed un metodo di session bean attaccato ad essa. In questo caso si utilizzerà un bean di sessione stateless, poiché tutto lo stato associato all'azione è mantenuto dal bean User.

Questo è l'unico codice veramente interessante nell'esempio!

Esempio 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

L'annotazione EJB @Stateless marca questa classe come session bean stateless.

2

L'annotazione @In marca un attributo del bean come iniettato da Seam. In questo caso, l'attributo viene iniettato da una variabile di contesto chiamata user (il nome della variabile istanza).

3

L'annotazione EJB standard @PersistenceContext è usata per iniettare l'entity manager EJB3.

4

L'annotazione @Logger di Seam è usata per iniettare l'istanza Log del componente.

5

Il metodo action listener utilizza l'API EJB3 standard EntityManager per interagire con il database, e restituisce l'esito JSF. Notare che, poiché questo è un session bean, si inizia automaticamente una transazione quando viene chiamato il metodo register(), e viene eseguito il commit quando questo completa.

6

Si noti che Seam consente di utilizzare espressioni JSF EL dentro EJB-QL. Sotto il coperchio, questo proviene da un'ordinaria chiamata JPA setParameter() sull'oggetto standard Query. Interessante, vero?

7

L'API Log consente facilmente di mostrare i messaggi di log, i quali possono anche impiegare espressioni JSF EL.

8

I metodi JSF action listener restituiscono un esito di tipo stringa, che determina quale pagina verrà mostrata come successiva. Un estio null (o un metodo action listener di tipo void) regenera la pagina precedente. Nel semplice JSF, è normale impiegare sempre una regola di navigazione JSF per determinare l'id della vista JSF dall'esito. Per applicazioni complesse quest'azione indiretta (indirection) è sia utile sia una buona pratica. Comunque, per ogni esempio semplice come questo, Seam consente di usare l'id della vista JSF come esito, eliminando l'uso della regola di navigazione. Si noti che quando viene usato l'id della vista come esito, Seam esegue sempre un redirect del browser.

9

Seam fornisce un numero di componenti predefiniti per aiutare a risolvere problemi comuni. Il componente FacesMessages agevola la visualizzazione di messaggi template di errore o di successo. (Da Seam 2.1, si puà impiegare StatusMessages invece di rimuovere la dipendenza semantica di JSF.) I componenti Seam predefiniti possono essere ottenuti tramite iniezione, o chiamando il metodo instance() sulla classe del componente predefinito.


Si noti che questa volta non si è esplicitamente specificato uno @Scope. Ciascun tipo di componente Seam ha uno scope di default se non esplicitamente specificato. Per bean di sessione stateless, lo scope di default è nel contesto stateless, che è l'unico valore sensato.

L'action listenere del bean di sessioni esegue la logica di persistenza e di business per quest'applicazione. In applicazioni più complesse, può essere opportuno separare il layer di servizio. Questo è facile da farsi in Seam, ma è critico per la maggior parte delle applicazioni web. Seam non forza nell'impiego di una particolare strategia per il layering dell'applicazione, consentendo di rimanere semplici o complessi a proprio piacimento.

Si noti che in questa semplice applicazione, abbiamo reso le cose di gran lunga più complicate di quanto necessario. Se si fossero impiegati i controllori di Seam, si sarebbe eliminato molto codice dell'applicazione. Comunque non avremmo avuto molto da spiegare.

Le pagine di vista di per un'applicazione Seam possono essere implementate usando qualsiasi tecnologia supporti JSF. In quest'esempio si usa Facelets, poiché noi pensiamo sia migliore di JSP.


L'unica cosa che qua è specifica di Seam è il tag <s:validateAll>. Questo componente JSF dice a JSFdi validare tutti i campi d'input contenuti con le annotazioni di Hibernate Validator specificate nell'entity bean.


Questa è una semplice pagina JSF che utilizza EL. Qua non c'è niente di specifico di Seam.

"Poiché questa è la prima applicazione vista, si prenderanno in esame i descrittori di deploy. Ma prima di iniziare, vale la pena di notare che Seam apprezza molto una configurazione minimale. Questi file di configurazione verranno creati al momento della creazione di un'applicazione Seam. Non sarà mai necessario metter mano alla maggior parte di questi file. Qua vengono presentati solo per aiutare a capire tutti pezzi dell'esempio preso in considerazione.

Se in precedenza si sono utilizzati altri framework Java, si è abituati a dichiarare le classi componenti in un qualche file XML che gradualmente cresce sempre più e diventa sempre più ingestibile man mano che il progetto evolve. Si resterà sollevati dal sapere che Seam non richiede che i componenti dell'applicazione siano accompagnati da file XML. La maggior parte delle applicazioni Seam richiede una quantità molto piccola di XML che non aumenta man mano che il progetto cresce.

Tuttavia è spesso utile fornire una qualche configurazione esterna per qualche componente (particolarmente per i componenti predefiniti di Seam). Ci sono due opzioni, ma l'opzione più flessibile è fornire questa configurazione in un file chiamato components.xml, collocato nella directory WEB-INF. Si userà il file components.xml per dire a Seam dove trovare i componenti EJB in JNDI:


Questo codice configura una proprietà chiamata jndiPattern di un componente Seam predefinito chiamato org.jboss.seam.core.init. Il divertente simbolo @ viene impiegato poiché lo script di build Ant vi mette al suo posto il corretto JDNI pattern al momento del deploy dell'applicazione, ricavato dal file components.properties. Maggiori informazioni su questo processo in Sezione 5.2, «Configurazione dei componenti tramite components.xml».

Il layer di presentazione dell'applicazione verrà deployato in un WAR. Quindi sarà necessario un descrittore di deploy web.


Il file web.xml configura Seam e JSF. La configurazione vista qua è più o meno la stessa in tutte le applicazioni Seam.

La maggior parte delle applicazioni Seam utilizza le viste JSF come layer di presentazione. Così solitamente si avrà bisogno di faces-config.xml. In ogni caso noi utilizzeremo Facelets per definire le nostre viste, così avremo bisogno di dire a JSF di usare Facelets come suo motore di template


Si noti che non occorre alcuna dichiarazione di managed bean JSF! I managed bean sono componenti Seam annotati. Nelle applicazioni Seam, faces-config.xml è usato meno spesso che nel semplice JSF. Qua, viene usato per abilitare Faceltes come gestore di viste al posto di JSP.

Infatti una volta configurati tutti i descrittori base, l'unico XML necessario da scrivere per aggiungere nuove funzionalità ad un'applicazione Seam è quello per l'orchestrazione (orchestration): regole di navigazione o definizione di processi jBPM. Un punto fermo di Seam è che flusso di processo e configurazione dei dati siano le uniche cose che veramente appartengano alla sfera dell'XML.

Questo semplice esempio non è neppure stato necessario usare una regola di navigazione, poiché si è deciso di incorporare l'id della vista nel codice dell'azione.

Quando la form viene inviata, JSF chiede a Seam di risolvere la variabile chiamata user. Poiché non c'è alcun valore associato a questo nome (in un qualsiasi contesto Seam), Seam istanzia il componente user e restituisce a JSF un'istanza dell'entity bean User dopo averla memorizzata nel contesto Seam di sessione.

I valori di input della form vengono ora validati dai vincoli di Hibernate Validator specificati nell'entity User. Se i vincoli vengono violati, JSF rivisualizza la pagina, Altrimenti, JSF associa i valori di input alle proprietò dell'entity bean User.

Successivamente JSF chiede a Seam di risolvere la variabile chiamata register. Seam utilizza il pattern JNDI menzionato in precedenza per localizzare il session bean stateless, lo impiega come componente Seam tramite il wrap e lo restituisce. Seam quindi presenta questo componente a JSF e JSF invoca il metodo action listener register().

Ma Seam non ha ancora terminato. Seam intercetta la chiamata al metodo e inietta l'entity User dal contestosessione di Seam, prima di consentire all'invocazione di continuare.

Il metodo register() controlla se esiste già un utente lo username inserito. Se è così, viene accodato un errore al componente FacesMessages, e viene restituito un esito null, causando la rivisualizzazione della pagina. Il componente FacesMessages interpola l'espressione JSF incorporata nella stringadi messaggio e aggiunge un FacesMessage JSF alla vista.

Se non esiste nessun utente con tale username, l'esito di "/registered.xhtml" causa un redirect del browser verso la pagina registered.xhtml. Quando JSF arriva a generare la pagina, chiede a Seam di risolvere la variabile chiamata user ed utilizza il valori di proprietà dell'entity User restituito dallo scope di sessione di Seam.

Le liste cliccabili dei risultati di ricerca del database sono una parte così importante di qualsiasi applicazione online che Seam fornisce una funzionalità speciale in cima a JSF per rendere più facile l'interrogazione dei dati usando EJB-QL o HQL e la mostra comelista cliccabile usando il JSF <h:dataTable>. I messaggi d'esempio mostrano questa funzionalità.

L'esempio di lista messaggi ha un entity bean, Message, un session bean, MessageListBean ed una JSP.

Come nel precedente esempio, esiste un session bean, MessageManagerBean, che definisce i metodi di action listener per i due bottoni della form. Uno di questi seleziona un messaggio dalla lista, e mostra tale messaggio. L'altro cancella il messaggio. Finora non è molto diverso dal precedente esempio.

Ma MessageManagerBean è anche responsabile per il recupero della lista dei messaggi la prima volta che si naviga nella pagina della lista messaggi. Ci sono vari modi in cui l'utente può navigare nella pagina, e non tutti sono preceduti da un'azione JSF — l'utente può avere un memorizzato la pagina, per esempio. Quindi il compito di recuperare la lista messaggi avviene in un metodo factory di Seam, invece che in un metodo action listener.

Si vuole memorizzare la lista dei messaggi tra le varie richieste server, e quindi questo session bean diventerà stateful.

Esempio 1.11. MessageManagerBean.java

@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean implements Serializable, MessageManager
{
   @DataModel
   private Lis(1)t<Message
> messageList;
   
   @DataModelS(2)election
   @Out(requir(3)ed=false)
   private Message message;
   
   @Persistenc(4)eContext(type=EXTENDED)
   private EntityManager em;
   
   @Factory("m(5)essageList")
   public void findMessages()
   {
      messageList = em.createQuery("select msg from Message msg order by msg.datetime desc")
                      .getResultList();
   }
   
   public void(6) select()
   {
      message.setRead(true);
   }
   
   public void(7) delete()
   {
      messageList.remove(message);
      em.remove(message);
      message=null;
   }
   
   @Remove    (8)
   public void destroy() {}

}
1

L'annotazione @DataModel espone alla pagina JSF un attributo di tipo java.util.List come istanza di javax.faces.model.DataModel. Questo permette di usare la lista in un <h:dataTable> di JSF con link cliccabili per ogni riga. In questo caso il DataModel è reso disponibile in una variabile con contesto sessione chiamata messageList.

2

L'annotazione @DataModelSelection dice a Seam di iniettare l'elemento List che corrisponde al link cliccato.

3

L'annotazione @Out espone direttamente alla pagina il valore selezionato. Ogni volta che una riga della lista viene selezionata, il Message viene iniettato nell'attributo del bean stateful, e in seguito viene fatta l'outjection nella variabile con contesto evento chiamata message.

4

Questo bean stateful ha un contesto di persistenza EJB3 esteso. I messaggi recuperati nella query rimangono nello stato gestito finché esiste il bean, quindi ogni chiamata di metodo conseguente al bean può aggiornarli senza il bisogno di chiamare esplicitamente l'EntityManager.

5

La prima volta che si naviga in un pagina JSP, non c'è alcun valore nella variabile di contesto messageList. L'annotazione @Factory dice a Seam di creare un'istanza di MessageManagerBean e di invocare il metodo findMessages() per inizializzare il valore. findMessages() viene chiamato metodo factory di messages.

6

Il metodo action listener select() marca il Message selezionato come letto e lo aggiorna nel database.

7

Il metodo action listener delete() rimuove il Message dal database.

8

Tutti i componenti Seam bean di sessione stateful devono avere un metodo senza parametri marcato @Remove che Seam utilizza per rimuovere il bean stateful quando termina il contesto di Seam, e viene pulito tutto lo stato lato server.


Si noti che questo è un componente Seam di sessione. E' associato alla sessione di login dell'utente e tutte le richieste da una login di sessione condividono la stessa istanza del componente. (Nelle applicazioni Seam, solitamente si usano componenti con scope di sessione in maniera contenuta.)

La pagina JSP è un semplice utilizzo del componente JSF <h:dataTable>. Ancora nulla di specifico di Seam.

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

La prima volta che si naviga nella pagina messages.jsp, la pagina proverà a risolvere la variabile di contesto messageList. Poiché questa variabile non è inizializzata, Seam chiamerà il metodo factory findMessages(), che esegue la query del database e mette i risultati in un DataModel di cui verrà fatta l'outjection. Questo DataModel fornisce i dati di riga necessari per generare la <h:dataTable>.

Quando l'utente clicca il <h:commandLink>, JSF chiama l'action listener select(). Seam intercetta questa chiamata ed inietta i dati di riga selezionati nell'attributo del componente messageManager. L'action listener viene eseguito, marcando come letto il Message selezionato. Alla fine della chiamata, Seam esegue l'outjection del Message selezionato nella variabile di contesto chiamata message. Poi il container EJB committa la transazione ed i cambiamenti a message vengono comunicati al database. Infine la pagina vienere rigenerata, rimostrando la lista dei messaggi e mostrando sotto il messaggio selezionato.

Se l'utente clicca <h:commandButton>, JSF chiama l'action listener delete(). Seam intercetta questa chiamata ed inietta i dati selezionati nell'attributo message del componente messageList. L'action listener viene eseguito, rimuovendo dalla lista il Message, e chiamando anche il metodo remove() dell'EntityManager. Alla fine della chiamata, Seam aggiorna la variabile di contesto messageList e pulisce la variabile di contesto chiamata message. Il container EJB committa la transazione e cancella Message dal database. Infine la pagina viene rigenerata, rimostrando la lista dei messaggi.

jBPM fornisce una funzionalità sofisticata per il workflow e la gestione dei task. Per provare come jBPM si integra con Seam, viene mostrata l'applicazione "todo list". Poiché gestire liste di task è la funzione base di jBPM, non c'è praticamente alcun codice Java in quest'esempio.

La parte centrale dell'esempio è la definizione del processo jBPM. Ci sono anche due pagine JSP e due banalissimi JavaBean (Non c'è alcuna ragione per usare session bean, poiché questi non accedono al database, e non hanno un comportamento transazionale). Cominciamo con la definizione del processo:


Se viene impiegato l'editor per le definizioni di processo fornito da JBossIDE, questa apparirà così:

Questo documento definisce il processo di business come un grafo di nodi. Questo è un processo di business molto banale: c'è un task da eseguire e quando questo viene completato, il processo termina.

Il primo javaBean gestisce la pagina login.jsp. Il suo compito è quello di inizializzare l'id actor jBPM usando il componente actor. Nelle applicazioni occorrerà autenticare l'utente.


Qua si vede l'uso di @In per iniettare il componente predefinito Actor.

Lo stesso JSP è banale:


Il secondo JavaBean è responsabile per l'avvio delle istanze del processo di business e della fine dei task.


In un esempio più realistico @StartTask e @EndTask non apparirebbero nello stesso metodo, poiché solitamente c'è del lavoro da fare in un'applicazione prima che il task venga terminato.

Infine, il cuore dell'applicazione è in todo.jsp:

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

Si prenda un pezzo alla volta.

La pagina renderizza una lista di task prelevati da un componente di Seam chiamato taskInstanceList. La lista è definita dentro una form JSF.


Ciascun elemento della lista è un'istanza della classe jBPM TaskInstance. Il codice seguente mostra semplicemente le proprietà di interesse per ogni task della lista. Per consentire all'utente di aggiornare i valori di descrizione, priorità e data di ultimazione, si usano i controlli d'input.


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

Nota

Seam fornisce di default un converter JSF di date per convertire una stringa in una data (no tempo). Quindi , il converter non è necessario per un'associazione di campo a #{task.dueDate}.

Questo pulsante termina il task chiamando il metodo d'azione annotato con @StartTask @EndTask. Inoltre passa l'id del task come parametro di richiesta a Seam.


<h:column>
    <s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column
>

Si noti che questo sta usando un controllo JSF Seam <s:button> del pacchetto seam-ui.jar. Questo pulsante è usato per aggiornare le proprietà dei task. Quando la form viene aggiornata, Seam e jBPM renderanno persistenti i cambiamenti ai task. Non c'è bisogno di alcun metodo action listener:


<h:commandButton value="Update Items" action="update"/>

Viene usata una seconda form per creare nuovi item, chiamando il metodo d'azione annotato con @CreateProcess.


<h:form id="new">
    <div>
        <h:inputText value="#{todoList.description}"/>
        <h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
    </div>
</h:form
>

Dopo la login, todo.jsp utilizza il componente taskInstanceList per mostrare un tabella con i compiti da eseguire da parte dell'utente corrente. Inizialmente non ce ne sono. Viene presentata anche una form per l'inserimento di una nuova voce. Quando l'utente digita il compito da eseguire e preme il pulsante "Create New Item", viene chiamato #{todoList.createTodo}. Questo inizia il processo todo, così come definito in todo.jpdl.xml.

L'istanza di processo viene creata a partire dallo stato di start ed immediatamente viene eseguita una transizione allo stato todo, dove viene creato un nuovo task. La descrizione del task viene impostata in base all'input dell'utente, che è stato memorizzato in #{todoList.description}. Poi il task viene assegnato all'utente corrente, memorizzato nel componente Seam chiamato actor. Si noti che in quest'esempio il processo non ha ulteriori stati di processo. Tutti gli stati sono memorizzati nella definizione del task. Il processo e le informazioni sul task sono memorizzati nel database alla fine della richiesta.

Quando todo.jsp viene rivisualizzata, taskInstanceList trova il task appena creato. Il task viene mostrato in un h:dataTable. Lo stato interno del task è mostrato in ciascuna colonna: #{task.description}, #{task.priority}, #{task.dueDate}, ecc... Questi campi possono essere tutti editati e salvati nel database.

Ogni elemento todo ha anche un pulsante "Done", che chiama #{todoList.done}. Il componente todoList sa a quale task si riferisce il pulsante, poiché ogni s:button specifica taskInstance="#{task}", che si riferisce al task per quella particolare linea della tabella. Le annotazioni @StartTast e @EndTask obbligano seam a rendere attivo il task e a completarlo. Il processo originale quindi transita verso lo stato done, secondo la definizione del processo, dove poi termina. Lo stato del task e del processo sono entrambi aggiornati nel database.

Quando todo.jsp viene di nuovo visualizzata, il task adesso completato non viene più mostrato in taskInstanceList, poiché questo componente mostra solo i task attivi per l'utente.

Per le applicazioni Seam con una navigazione relativamente libera, le regole di navigazione JSF/Seam sono un modo perfetto per definire il flusso di pagine. Per applicazioni con uno stile di navigazione più vincolato, specialmente per interfacce utente più stateful, le regole di navigazione rendono difficile capire il flusso del sistema. Per capire il flusso occorre mettere assieme le pagine, le azioni e le regole di navigazione.

Seam consente di usare la definizione di processo con jPDL per definire il flusso di pagine. L'esempio indovina-numero mostra come fare.

Quest'esempio è implementato usando un JavaBean, tre pagine JSP ed una definizione di pageflow jPDL. Iniziamo con il pageflow:

Esempio 1.20. pageflow.jpdl.xml

<<?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 (1)doctype-root-element="html" 
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              (2)doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directi(3)ve.page contentType="text/html"/>
  <html>
  <head>
    <title
>Guess a number...</title>
    <link href(4)="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
>
1

L'elemento <page> definisce uno stato di attesa dove il sistema mostra una particolare vista JSF ed attende input da parte dell'utente. view-id è lo stesso id view usato nelle regole di navigazione nel pure JSF. L'attributo redirect dice a Seam di usare il post-then-redirect quando si passa ad un'altra pagina. (Questo capita con gli URL dei browser.)

2

L'elemento <transition> chiama un esito JSF. La transizione è lanciata quando un'azione JSF ha tale esito. L'esecuzione quindi procederà verso il successivo nodo del grafo pageflow, dopo l'invocazione di una qualsiasi azione di transizione jBPM.

3

Una transizione <action> è come un'azione JSF, tranne che avviene quando si verifica una transizione jBPM. L'azione di transizione può invocare qualsiasi componente Seam.

4

Un nodo <decision> divide il pageflow e determina il successivo nodo da eseguire valutando un'espressione JSF EL.


Ecco come appare il pageflow nell'editor di pageflow di JBoss Developer Studio:

Ora che abbiamo visto il pageflow, è molto facile capire il resto dell'applicazione.

Ecco la pagina principale dell'applicazione, numberGuess.jspx:

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

Si noti come il pulsante di comando chiama la transizione guess invece di chiamare direttamente un'azione.

La pagina win.jspx è prevedibile:


lose.jspx è più o meno uguale, quindi si passa oltre.

Infine diamo un'occhiata al codice dell'applicazione:

Esempio 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

La prima volta che una pagina JSP richiede un componente numberGuess, Seam ne crea uno nuovo, ed il metodo @Create viene invocato, consentendo che il componente si inizializzi.


Il file pages.xml inizia una conversazione Seam (maggiori informazioni più avanti), e specifica la definizione pageflow da usare per il flusso delle pagine della conversazione.


Come si può vedere, questo componente Seam è pura logica di business! Non ha bisogno di sapere niente riguardo il flusso delle interazioni utente. Questo rende il componente potenzialmente più riutilizzabile.

Si analizzerà ora il flusso base dell'applicazione. Il gioco comincia con la vista numberGuess.jspx. Quando la pagina viene mostrata la prima volta, la configurazione pages.xml porta ad iniziare la conversazione ed associa il pageflow numberGuess a tale conversazione. Il pageflow inizia con un tag start-page che è uno stato d'attesa, e poi viene visualizzata la pagina numberGuess.xhtml.

La vista fa riferimento al componente numberGuess, provocando la creazione di una nuova istanza e la sua memorizzazione all'interno della conversazione. Viene chiamato il metodo @Create che inizializza lo stato del gioco. La vista mostra un h:form per consentire all'utente di editare #{numberGuess.currentGuess}.

Il pulsante "Guess" lancia l'azione guess. Seam usa il pageflow per gestire l'azione, la quale impone che il pageflow transiti allo stato evaluateGuess, innanzitutto invocando #{numberGuess.guess} che aggiorna il contatore ed i suggerimenti più alto/più basso nel componente numberGuess.

Lo stato evaluateGuess controlla il valore di #{numberGuess.correctGuess} e le transizioni agli stati win o evaluatingRemainingGuesses. Si assumache ilnumero sia sbagliato, nel qual caso il pageflow transita verso evaluatingRemainingGuesses. Questo è anche uno stato di decisione, che testa lo stato #{numberGuess.lastGuess} per determinare se l'utente ha ulteriori tentativi oppure no. Se ne ha (lastGuess è falso), si torna allo stato originale displayGuess. Infine si raggiunge lo stato page, e quindi viene mostrata la pagina associata /numberGuess.jspx. Poiché la pagina ha un elemento redirect, Seam invia un redirect al browser dell'utente, ricominciando il processo.

Non si analizzerà ulteriormente lo stato, tranne per notare che se in una richiesta futura venisse presa la transizione win oppure lose, l'utente verrebbe portato a /win.jspx oppure /lose.jspx. Entrambi gli stati specificano che Seam debba terminare la conversazione, liberandosi dello stato del gioco e di quello del pageflow, prima di reindirizzare l'utente alla pagina finale.

L'esempio indovina-numero contiene anche i pulsanti Giveup (abbandona) e Cheat (imbroglia). Si dovrebbe essere facilmente in grado di tracciare lo stato pageflow per le relative azioni. Si presti attenzione alla transizione cheat, che carica un sotto-processo per gestire tale flusso. Sebbene sia superfluo per quest'applicazione, questo dimostra come pageflow complessi possano venire spezzati in parti più piccole per renderle più facili da capire.

La struttura del progetto è identica al precedente, per installare e deployare quest'applicazione, si faccia riferimento a Sezione 1.1, «Utilizzo degli esempi di Seam». Una volta avviata l'applicazione, si può accedere a questa puntando il browser all'indirizzo http://localhost:8080/seam-booking/

L'applicazione utilizza sei bean di sessione per implementare la logica di business per le funzionalità nella lista.

  • AuthenticatorAction fornisce la logica per l'autenticazione della login.

  • BookingListAction recupera le prenotazioni esistenti per l'utente attualmente loggato.

  • ChangePasswordAction aggiorna la password per l'utente attualmente loggato.

  • HotelBookingAction implementa le funzionalità di prenotazione e conferma. Questa funzionalità è implementata come conversazione, e quindi è una delle classi più interessanti dell'applicazione.

  • HotelSearchingAction implementa la funzionalità di ricerca hotel.

  • RegisterAction registra un nuovo utente di sistema.

Tre entity bean implementano il modello di dominio di persistenza dell'applicazione.

  • Hotel è un entity bean che rappresenta un hotel

  • Booking è l'entity bean che rappresenta una prenotazione esistente

  • User è un entity bean che rappresenta un utente che può fare una prenotazione

Si incoraggia a guardare il codice sorgente a piacimento. In questo tutorial ci concentreremo su alcune particolari funzionalità: ricerca hotel, selezione, prenotazione e conferma. Dal punto di vista dell'utente, tutto - dalla selezione dell'hotel alla conferma dellaprenotazione - è un'unica continua unità di lavoro, una conversazione. La ricerca, comunque, non è una parte della conversazione. L'utente può selezionare più hotel dalla stessa pagina dei risultati, in diversi tab del browser.

La maggior parte delle architetture delle applicazioni non ha alcun costrutto per rappresentare una conversazione. Questo causa enormi problemi nella gestione dello stato conversazionale. Solitamente le applicazioni web Java usano una combinazione di diverse tecniche. Alcuni stati possono essere trasferiti nell'URL. Ciò che non può è messo o in HttpSession o mandato a database dopo ogni richiesta, e ricostruito dal database all'inizio di ogni nuova richiesta.

Poiché il database è il livello meno scalabile, questo risulta essere spesso ad un livello inaccettabile di scalabilità. La latenza è un ulteriore problema, dovuto al traffico extra verso e dal database ad ogni richiesta. Per ridurre questo traffico ridondante, le applicazioni Java spesso introducono una cache di dati (di secondo livello) che mantiene i dati comunemente acceduti tra le varie richieste. Questa cache è necessariamente inefficiente, poiché l'invalidazione è basato su una policy LRU invece di essere basata su quando l'utente termina di lavorare con i dati. Inoltre, poiché la cache è condivisa da diverse transazioni concorrenti, si è introdotta una schiera di problemi associati al fatto di mantenere lo stato della cache consistente con il database.

Ora si consideri lo stato mantenuto nella HttpSession. HttpSession è un ottimo posto per i veri dati di sessione, cioè dati che sono comuni a tutte le richieste che l'utente fa con l'applicazione. Comunque, non è un posto dove vanno memorizzati i dati riguardanti serie individuali di richieste. L'uso della sessione si complica velocemente quando si ha a che fare con il pulsante indietro e con le finestre multiple. In cima a questo, senza una programmazione attenta, i dati nella sessione HTTP posso crescere parecchio, rendendo la sessione HTTP difficile da tenere assieme. Lo sviluppo di meccanismi per isolare lo stato della session associata a differenti conversazioni concorrenti, e l'aggiunta di meccanismi di sicurezza per assicurare che lo stato della conversazione venga distrutto quando l'utente interrompe una delle conversazioni chiudendo una finestra del browser non è una questione per gente poco coraggiosa. Fortunatamente con Seam non occorre preoccuparsi di queste problematiche.

Seam introduce il contesto conversazionale come first class construct. Si può mantenere in modo sicuro lo stato conversazionale in questo contesto ed essere certi che avrà un ciclo di vita ben definito. Ancor meglio non servirà mandare continuamente avanti ed indietro i dati tra server e database, poiché il contesto di conversazione è una cache naturale di dati su cui l'utente sta lavorando.

In quest'applicazione si userà il contesto di conversazione per memorizzare i session bean stateful. C'è un'antica credenza nella comunità Java che ritiene che i session bean stateful siano nocivi alla scalabilità. Questo poteva essere vero nei primissimi giorni di Java Enterprise, ma oggi non è più vero. I moderni application server hanno meccanismi estremamente sofisticati per la replicazione dello stato dei session bean stateful. JBoss AS, per esempio, esegue una replicazione a grana fine, replicando solo quei valori degli attributi bean che sono cambiati. Si noti che tutti gli argomenti tecnici tradizionali per cui i bean stateful sono inefficienti si applicano allo stesso modo alla HttpSession, e quindi risulta fuorviante la pratica di cambiare stato dai componenti (session bean stateful) del business tier alla sessione web per cercare di migliorare le performance. E' certamente possibile scrivere applicazioni non scalabili usando session bean stateful non in modo corretto, o usandoli per la cosa sbagliata. Ma questo non significa che non si debba mai. Se non si è convinti, Seam consente di usare POJO invece dei session bean statefull. Con Seam la scelta è vostra.

L'applicazione di esempio prenotazione mostra come i componenti stateful con differenti scope possano collaborare assieme per ottenere comportamenti complessi. La pagina principale dell'applicazione consente all'utente di cercare gli hotel. I risultati di ricerca vengono mantenuti nello scope di sessione di Seam. Quando l'utente naviga in uno di questi hotel, inizia una conversazione ed il componente con scope conversazione chiama il componente con scope sessione per recuperare l'hotel selezionato.

L'esempio di prenotazione mostra anche l'uso di RichFaces Ajax per implementare un comportamento rich client senza usare Javascript scritto a mano.

La funzionalità di ricerca è implementata usando un session bean statefull con scope di sessione, simile a quello usato nell'esempio di lista messaggi.

Esempio 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

L'annotazione EJB standard @Stateful identifica questa classe come un bean di sessione stateful. Bean di sessione stateful hanno di default uno scope legato al contesto di conversazione.

2

L'annotazione @Restrict applica al componente una restrizione di sicurezza. Restringe l'accesso al componente consentendolo solo agli utenti loggati. Il capitolo sicurezza spiega con maggior dettaglio la sicurezza in Seam.

3

L'annotazione @DataModel espone una List come ListDataModel JSF. Questo facilita l'implementazione di liste cliccabili per schermate di ricerca. In questo caso, la lista degli hotel è esposta nella pagina come ListDataModel all'interno della variabile di conversazione chiamata hotels.

4

L'annotazione standard EJB @Remove specifica che un bean di session stateful deve essere rimosso ed il suo stato distrutto dopo l'invocazione del metodo annotato. In Seam tutti i bean di sessione stateful devono definire un metodo senza parametri marcato con @Remove. Questo metodo verrà chiamato quando Seam distrugge il contesto di sessione.


La pagina principale dell'applicazione è una pagina Facelets. Guardiamo al frammento relativo alla ricerca hotel:

Esempio 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}" 
              (1)      style="width: 165px;">
         <a:support event="onkeyup" actionListener="#{hotelSearch.find}" 
                    reRender="searchResults" />
       </h:inputText>
       &#160;
       <a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}" 
              (2)          reRender="searchResults"/>
       &#160;
       <a:status>
          <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> (3)
    
</div>

<a:outputPanel id="searchResults">
  <div class="section">
    <h:outputText value="No Hotels Found"
                  rendered="#{hotels != null and hotels.rowCount==0}"/>
    <h:dataTable id="hotels" value="#{hotels}" var="hot" 
                 rendered="#{hotels.rowCount
>0}">
        <h:column>
            <f:facet name="header"
>Name</f:facet>
            #{hot.name}
        </h:column>
        <h:column>
            <f:facet name="header"
>Address</f:facet>
            #{hot.address}
        </h:column>
        <h:column>
            <f:facet name="header"
>City, State</f:facet>
            #{(4)hot.city}, #{hot.state}, #{hot.country}
        </h:column
> 
        <h:column>
            <f:facet name="header"
>Zip</f:facet>
            #{hot.zip}
        </h:column>
        <h:column>
            <f:facet name="header"
>Action</f:facet>
            <s:link id="viewHotel" value="View Hotel" 
                    action="#{hotelBooking.selectHotel(hot)}"/>
        </h:column>
    </h:dataTable>
    <s:link value="More results" action="#{hotelSearch.nextPage}" 
            rendered="#{hotelSearch.nextPageAvailable}"/>
  </div>
</a:outputPanel
>    
1

Il tag RichFaces Ajax <a:support> consente ad un event action listener JSF di essere chiamato da XMLHttpRequest asincrono quando avviene un evento JavaScript onkeyup. Ancor meglio, l'attributo reRender consente di rigenerare un frammento di pagina JSF e di eseguire un aggiornamento parziale quando si riceve una risposta asincrona.

2

Il tag RichFaces Ajax <a:status> consente di mostrare un'immagine animata mentre si attende la restituzione di richieste asincrone.

3

Il tag RichFaces Ajax <a:outputPanel> definisce una regione della pagina che può essere rigenerata da una richiesta asincrona.

4

Il tag Seam <s:link> consente di attaccare un action listener JSF ad un link HTML ordinario (non-JavaScript). Il vantaggio rispetto al JSF <h:commandLink> è che mantiene le operazioni "Apri in nuova finestra" and "Apri in nuova scheda". Si noti inoltre che è stato usato un method binding con un parametro: #{hotelBooking.selectHotel(hot)}. Questo non è possibile con lo standard Unified EL, ma Seam fornisce un'estensione a EL che consente l'uso dei parametri sul qualsiasi espressione di method binding.

Se ci si chiede come avvenga la navigazione, si possono trovare tutte le regole in WEB-INF/pages.xml; questo viene discusso in Sezione 6.7, «Navigazione».


Questa pagina mostra i risultati di ricerca in modo dinamico man mano si digita, e consente di scegliere un hotel e passarlo al metodo selectHotel() di HotelBookingAction, che è il posto in cui veramente succede qualsosa di interessante.

Vediamo ora come l'applicazione d'esempio usa un bean di sessione stateful con scope di conversazione per ottenere una naturale cache di dati persistenti relativi alla conversazione. Il seguente codice d'esempio è abbastanza lungo. Ma se si pensa a questo come una lista di azioni che implementano vari passi della conversazione, risulta comprensibile. Si legga la classe dalla cima verso il fondo, come se fosse un racconto.

Esempio 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

Questo bean utilizza un contesto di persistenza esteso EJB3, e quindi ogni istanza di entity rimane gestita per l'intero ciclo di vita del session bean stateful.

2

L'annotazione @Out dichiara che il valore dell'attributo viene outjected in una variablie di contesto dopo le invocazioni del metodo. In questo caso, la variabile di contesto chiamata hotel verrà impostata al valore della variabile d'istanza hotel dopo che viene completata ciascuna invocazione dell'action listener.

3

L'annotazione @Begin specifica che il metodo annotato inizi una conversazione long-running, e quindi l'attuale contesto della conversazione non verrà distrutto alla fine della richiesta. Invece verrà riassociato ad ogni richiesta dalla finestra attuale e distrutto o dopo un timeout dovuto all'inattività della conversazione o dopo l'invocazione di un metodo annotato con @End.

4

L'annotazione @End specifica che il metodo annotato finisca l'attuale conversazione long-running, e quindi il contesto della conversazione attuale verrà distrutto alla fine della richiesta.

5

Questo metoto EJB di rimozione verrà chiamato quando Seam distruggerà il contesto della conversazione. Non si dimentichi di definire questo metodo!


HotelBookingAction contiene tutti i metodi action listenet che implementano, selezione, prenotazione e conferma, e mantiene lo stato relativo a questo lavoro nelle variabili di istanza. Pensiamo che questo codice sia molto più pulito e semplice degli attributi get e set in HttpSession.

Ancor meglio, un utente può avere conversazioni multiple isolate per ogni sessione di login. Si provi! Loggarsi, eseguire una ricerca e navigare in diverse pagine d'hotel in diverse schede del browser. Si sarà in grado di lavorare e creare due differenti prenotazioni contemporaneamente. Se una conversazione viene lasciata a lungo inattiva, Seam andrà in timeout e distruggerà lo stato di quella conversazione. Se, dopo la chiusura di una conversazione, si premerà il pulsante indietro per tornare alla pagina precedente e si eseguirà un'azione, Seam si accorgerà che la conversazione è già terminata, e rimanderà l'utente alla pagina di ricerca.

Le conversazioni long-running rendono semplice mantenere la consistenza dello stato in un'applicazione anche in presenza di operazioni con finestre multiple o con il pulsante indietro. Sfrotunatamente, iniziare e finire una conversazione long-running non è sempre sufficiente. A seconda dei requisiti dell'applicazione, le inconsistenze tra le aspettative dell'utente ed il reale stato dell'applicazione possono comunque sussistere.

L'applicazione prenotazione annidata estende le caratteristiche dell'applicazione prenotazione hotel aggiungendo la selezione della stanza. Ogni hotel ha camere disponibili con delle descrizioni che l'utente può scegliere. Questo richiede l'aggiunta di una pagina di selezione camera nel flusso di prenotazione hotel.

L'utente adesso ha l'opzione di selezionare una camera disponibile da aggiungere alla prenotazione. Come per l'applicazione precedentemente vista, questo porta a problemi di consistenza dello stato. Come per la memorizzazione dello stato in HTTPSession, se una variabile di conversazione cambia, questo influenza tutte le finestre che operano dentro lo stesso contesto di conversazione.

Per dimostrare questo si supponga che l'utente cloni la schermata di selezione delle camera in una nuova finestra. L'utente quindi seleziona la Wonderful Room e procede alla schermata di conferma. Per vedere solamente quando costa vivere alla grande, l'utente ritorna alla finestra originale, seleziona la Fantastic Suite ed procede quindi alla conferma. Dopo aver visto il costo totale, l'utente decide che la praticità vince e ritorna alla finestra della Wonderful Room per procedere alla conferma.

In questo scenario, se semplicemente si memorizza lo stato nella conversazione non si è protetti da operazioni a finestre multiple all'interno della stessa conversazione. Le conversazioni innestate consentono di ottenere un comportamento corretto quando il contesto può variare all'interno della stessa conversazione.

Si veda ora come l'esempio di prenotazione innestata estenda il comportamento dell'applicazione di prenotazione hotel tramite l'utilizzo di conversazioni innestate. Ancora, si può leggere la classe dalla cima verso il fondo, come un racconto.

Esempio 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("a(1)vailableRooms")
   public void loadAvailableRooms()
   {
      availableRooms = hotel.getAvailableRooms(booking.getCheckinDate(), booking.getCheckoutDate());
      log.info("Retrieved #0 available rooms", availableRooms.size());
   }

   public BigDecimal getExpectedPrice()
   {
      log.info("Retrieving price for room #0", roomSelection.getName());
      
      return booking.getTotal(roomSelection);
   }
              (2)
   @Begin(nested=true)
   public String selectPreference()
   {
      log.info("Room selected");
              (3)
      this.room = 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(before(4)Redirect=true)
   public String cancel()
   {
      log.info("ending conversation");

      return "cancel";
   }

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

L'istanza hotel viene iniettata dal contesto conversazione. L'hotel viene caricato tramite un contesto di persistenza esteso cosicché l'entity rimanga gestito lungo la conversazione. Questo consente di caricare in modo lazy la availableRooms tramite il metodo @Factory semplicemente seguendo l'associazione.

2

Quando si incontra @Begin(nested=true) , viene aggiunta una conversazione innestata allo stask delle conversazioni. Dentro una conversazione innestata, i componenti hanno accesso a tutto lo stato della conversazione più esterna, ma il settaggio di valori nel container dello stato delle conversazione innestata non influenza la conversazione più esterna. In aggiunta, le conversazioni esterne possono esistere in modo concorrente sopra la stessa conversazione più esterna, consentendo per ciascuna uno stato indipendente.

3

roomSelection viene messa in outjection nella conversazione tramite @DataModelSelection. Si noti che, poiché la conversazione innestata haun contesto indipendente, roomSelection è impostata solo nella nuova conversazione innestata. Dovesse l'utente selezionare un'altra preferenza in un'altra finestra o scheda, una nuova conversazione innestata verrebbe generata.

4

L'annotazione @End rimuove la conversazione dallo stack (pop) e ripristina la conversazione più esterna. roomSelection viene distrutta assieme al contesto della conversazione.


Quando si inizia una conversazione innestata, questa viene messa nello stack delle conversazioni. Nell'esempio nestedbooking, lo stack consiste in una conversazione long-running più esterna (la prenotazione) e ciascuna delle conversazioni innestate (selezione camere).

Esempio 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}"/>
              (1)
            <br/><br/>
            
            <h:dataTable value="#{availableRooms}" var="room" 
                    rendered="#{availableRooms.rowCount 
> 0}">
                <h:column>
                    <f:facet name="header"
>Name</f:facet>
                    #{room.name}
                </h:column>
                <h:column>
                    <f:facet name="header"
>Description</f:facet>
                    #{room.description}
                </h:column>
                <h:column>
              (2)      <f:facet name="header"
>Per Night</f:facet>
                    <h:outputText value="#{room.price}">
                        <f:convertNumber type="currency" currencySymbol="$"/>
                    </h:outputText>
                </h:column>
                <h:column>
                    <f:facet name="header"
>Action</f:facet>
              (3)      <h:commandLink id="selectRoomPreference" 
                        action="#{roomPreference.selectPreference}"
>Select</h:commandLink>
                </h:column>
            </h:dataTable>
        </div>
        <div class="entry">
            <div class="label"
>&#160;</div>
            <div class="input">
                <s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
            </div>
        </div
>    
    </h:form>
</div>
1

Quando richiesto da EL, #{availableRooms} viene caricata dal metodo @Factory definito in RoomPreferenceAction. Il metodo @Factory verrà eseguito solo una volta per caricare i valore nel contesto attuale come istanza @DataModel .

2

L'invocazione dell'azione #{roomPreference.selectPreference} ha come risultato la selezione della riga e la sua impostazione in @DataModelSelection. Questo valore è quindi messo in outjection nel contesto della conversazione innestata.

3

Un cambiamento alle date semplicemente riporta a /book.xhtml. Si noti che ancora non è stata innestata alcuna conversazione (non è stata selezionata nessuna camera), e quindi la conversazione attuale può essere ristabilita. Il componente <s:button > semplicemente propaga la conversazione corrente quando viene mostrata la vista /book.xhtml.


Ora che si è visto come innestare una conversazione, vediamo come si può confermare la prenotazione una volta selezionata la camera. Questo può essere ottenuto semplicemente estendendo il comportamento di HotelBookingAction.

Esempio 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

Annotare un'azione con @End(root=true) termina la conversazione radice che distrugge effettivamente tutto lo stack delle conversazioni. Quando una conversazione termina, terminano anche le sue conversazioni innestate. Poiché la conversazione radice è quella che inizia tutto, questo è un modo semplice per distruggere e rilasciare tutto lo stato associato al workspace una volta confermata la prenotazione.

2

roomSelection è associata solamente a booking su conferma dell'utente. Mentre l'outjection dei valori nel contesto della conversazione innestata non impatta sulla conversazione più esterna, qualsiasi oggetto iniettato dalla conversazione più esterna viene iniettato per referimento. Questo significa che qualsiasi cambiamento degli oggetti si rifletterà nella conversazione padre così come in ogni altra conversazione innestata.

3

Annotando semplicemente l'azione di cancellazione con @End(root=true, beforeRedirect=true) , è possibile distruggere e rilasciare tutto lo stato associato al workspace prima di redirigere l'utente alla vista della selezione hotel.


Prova il deploy dell'applicazione, apri più finestre o tab e prova combinzioni di vari hotel con varie opzioni di camera. La conferma risulterà sempre nel giusto hotel e con la corretta opzione grazie al modello di conversazioni innestate.

Seam facilita l'implementazione di applicazioni che mantengano lo stato lato server. Comunque lo stato lato server non è sempre appropriato, specialmente per funzionalità che lavorano per il contenuto. Per questo genere di problemi spesso si vuole mantenere lo stato dell'applicazione nell'URL affiché ogni pagina possa essere acceduta in qualsiasi momento attraverso un segnalibro. L'esempio Blog mostra come implementare un'applicazione che supporti i segnalibri, anche nel caso di pagine con risultati di ricerca. Questo esempio mostra come Seam può gestire nell'URL lo stato di un'applicazione, così come Seam può riscrivire questi URL.

L'esempio Blog mostra l'uso di MVC di tipo "pull", dove invece di usare metodi action listener per recuperare i dati e preparare i dati per la vista, la vista preleva (pull) i dati dai componenti quando viene generata.

Questo frammento della pagina facelets index.xhtml mostra una lista di messaggi recenti al blog:


Se si arriva in quaste pagina da un segnalibro, come viene inizializzato #{blog.recentBlogEntries} usato da <h:dataTable>? Blog viene recuperato in modo lazy — "tirato" — quando serve, da un componente Seam chiamato blog. Questo è il flusso di controllo opposto a quello usato nei tradizionali framework web basati sull'azione, come ad esempio Struts.


Finora va bene, ma cosa succede se si memorizza il risultato di un invio di form, come ad esempio una pagina di risultati di ricerca?

L'esempio Blog ha una piccola form in alto a destra di ogni pagina, che consente all'utente di cercare le entry del blog. E' definito in un file, menu.xhtml, incluso nel template facelets, template.xhtml:


Quindi la form avrebbe dovuto essere così:


<div id="search">
   <h:form>
      <h:inputText value="#{searchAction.searchPattern}"/>
      <h:commandButton value="Search" action="searchResults"/>
   </h:form>
</div
>

Ma quando viene fatto il redirect, occorre includere i valori sottomessi con la form dentro l'URL per ottenere un URL memorizzabile come ad esempio http://localhost:8080/seam-blog/search/. JSF non fornisce un modo semplice per farlo, ma Seam sì. Per ottenere questo si usano due funzionalità di Seam: i parametri di pagina e la riscrittura dell'URL. Entrambi sono definiti in WEB-INF/pages.xml:


Il parametro di pagina istruisce Seam a fare collegare il parametro di richiesta chiamato searchPattern al valore di #{searchService.searchPattern}, sia quando arriava una richiesta per la pagina di ricerca, sia quando viene generato un link alla pagina di ricerca. Seam si prende la responsabilità di mantenere il link tra lo stato dell'URL e lo stato dell'applicazione, mentre voi, come sviluppatori, non dovete preoccuparvene.

Senza riscrittura, l'URL di una ricerca di un termine book sarebbe http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book. Questo può andare bene, ma Seam può semplificare l'URL usando una regola di riscrittura. La prima regola, per il pattern /search/{searchPattern}, dice che in ogni volta che si ha un URL per search.xhtml con un parametro di richiesta searchPattern, si può semplificare quest'URL. E quindi l'URL visto prima, http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book viene riscritto come http://localhost:8080/seam-blog/search/book.

Come per i parametri di pagina, la riscrittura dell'URL è bidirezionale. Questo significa che Sean inoltra le richieste di URL più semplici alla giusta vista e genera automaticamente la vista più semplice per voi. Non serve preoccuparsi della costruzione dell'URL. Viene tutto gestito in modo trasparente dietro. L'unico requisito è che per usare la riscrittura dell'URL, occorre abilitare il filtro di riscrittura in components.xml.

<web:rewrite-filter view-mapping="/seam/*" />

Il redirect di porta alla pagina search.xhtml:


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

Il quale usa ancora MVC di tipo "pull" per recuperare i risultati di ricerca usando 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;
   }
}

Alcune volte ha più senso usare MVC push-style per processare pagine RESTful, e quindi Seam fornisce la nozione di azione di pagina. L'esempio di Blog utilizza l'azione di pagina per pagina di entry del blog, entry.xhtml. Notare che questo è un pò forzato, sarebbe stato più facile usare anche qua lo stile MVC pull-style.

Il componente entryAction funziona come una action class in un framework tradizionale orientato alle azioni e push-MVC come 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);
   }
   
}

Le azione nella pagina vengono anche dichiarate 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
>

Notare che l'esempio utilizza azioni di pagina per la validazione e per il conteggio delle pagine visitate. Si noti anche l'uso di un parametro nel binding di metodo all'interno della azione di pagina. Questa non è una caratteristiva standard di JSF EL, ma Seam consente di usarla, non solo per le azioni di pagina, ma anche nei binding di metodo JSF.

Quando la pagina entry.xhtml viene richiesta, Seam innanzitutto lega il parametro della pagina blogEntryId al modello. Si tenga presente che a causa della riscrittura dell'URL, il nome del parametro blogEntryId non verrà mostrato nell'URL. Seam quindi esegue l'azione, che recupera i dati necessari — blogEntry — e li colloca nel contesto di evento di Seam. Infine, viene generato il seguente:


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

Se l'entry del blog non viene trovata nel database, viene lanciata l'eccezione EntryNotFoundException. Si vuole che quest'eccezione venga evidenziata come errore 404, non 505, e quindi viene annotata la classe dell'eccezione:

@ApplicationException(rollback=true)

@HttpError(errorCode=HttpServletResponse.SC_NOT_FOUND)
public class EntryNotFoundException extends Exception
{
   EntryNotFoundException(String id)
   {
      super("entry not found: " + id);
   }
}

Un'implementazione alternativa dell'esempio non utilizza il parametro nel 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
>

E' una questione di gusti su quale implementazione tu preferisca.

La demo del blog mostra anche una semplice autenticazione di password, un invio di un post al blog, un esempio di caching frammentato della pagina e la generazione di atom feed.

La distribuzione Seam comprende una utility da linea di comando che facilita la configurazione di un progetto eclipse, la generazione di un semplice codice skeleton Seam, ed il reverse engineer di un'applicazione da un database esistente.

Questo è il modo più semplice di sporcarti le mani con Seam e di preparare il colpo in canna per la prossima volta che ti troverai intrappolato in ascensore con uno di quei noiosi tipi di Ruby-on-Rail che farneticano quanto magnifico e meraviglioso sia l'ultimo giochino che hanno scoperto per realizzare applicazioni completamente banali che schiaffano delle cose nel database.

In questa relase, seam-gen funziona meglio per coloro che hanno JBoss AS. Si può usare il progetto generato con altri server J2EE o Java EE 5 facendo alcuni cambiamenti alla configurazione del progetto.

Si può usare seam-gen senza Eclipse, ma in questo tutorial, si vuole mostrare l'uso assieme ad Eclipse per il debugging ed i test. Se non si vuole installare Eclipse, si può seguire comunque questo tutorial - tutti i passi possono essere eseguiti da linea di comando.

Seam-gen è essenzialmente uno script Ant avvolto attorno a Hibernate Tools, assieme a qualche template. Questo facilita la sua personalizzazione nel caso ce ne sia bisogno.

Assicurarsi di avere JDK 5 o JDK 6 (vedere Sezione 40.1, «Dipendenze JDK» per maggiori dettagli), JBoss AS 4.2 e Ant 1.6, con una versione recente di Eclipse, il plugin JBoss IDE di Eclipse ed il plugin TestNG per Eclipse correttamente installati prima di avviare. Aggiungere l'installazione di JBoss alla vista di JBoss Server in Eclipse. Avviare JBoss in modalità debug. Infine, avviare da comando all'interno della directory dove si è scompattato la distribuzione Seam.

JBoss ha un supporto sofisticato per l'hot re-deploy di WAR e EAR. Sfortunatamente, a causa di bug alla JVM, ripetuti redeploy di un EAR—situazione frequente durante lo sviluppo—causano un perm gen space della JVM. Per questa ragione, in fase di sviluppo si raccomanda di eseguire JBoss in una JVM avente parecchio perm gen space. Se si esegue JBoss da JBoss IDE, si può configurare questo nella configurazione di lancio del server, sotto "VM arguments". Si suggeriscono i seguenti valori:

-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m

Se non si ha molta memoria disponibile, si raccomanda come minimo:

-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m

Se si esegue JBoss da linea di comando, si possono configurare le opzioni JVM in bin/run.conf.

Se non si vuole avere a che fare con queste cose adesso, si lasci stare—ce ne si occuperà quando capiterà la prima OutOfMemoryException.

La prima cosa da fare è configurare seam-gen per il proprio ambiente: la directory di installazione JBoss AS, il workspace di Eclipse, e la connessione del database. E' facile, si digiti:

cd jboss-seam-2.0.x
seam setup

E verranno richieste le informazioni necessarie:

~/workspace/jboss-seam$ ./seam setup
Buildfile: build.xml

init:

setup:
     [echo] Welcome to seam-gen :-)
    [input] Enter your Java 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.2.GA] [C:/Program Files/jboss-4.2.2.GA]
/Applications/jboss-4.2.2.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 $ 

Il tool fornisce dei valori di default, che possono essere accettati semplicemente premendo Invio alla richiesta.

La scelta più importante da fare è tra il deploy EAR e il deploy WAR del progetto. I progetti EAR supportano EJB 3.0 e richiedono Java EE 5. I progetti WAR non supportano EJB 3.0, ma possono essere deployati in ambienti J2EE. L'impacchettamento di un WAR è più semplice da capire. Se si installa un application server predisposto per EJB3, come JBoss, si scelga ear. Altrimenti si scelga war. Assumeremo per il resto del tutorial che la scelta sia il deploy EAR, ma si potranno compiere gli stessi passi per il deploy WAR.

Se si sta lavorando con un modello di dati esistente, ci si assicuri di dire a seam-ger che le tabelle esistono già nel database.

Le impostazioni vengono memorizzate in seam-gen/build.properties, ma si possono anche modificare eseguendo semplicemente una seconda volta seam setup.

Ora è possibile creare un nuovo progetto nella directory di workspace di Eclipse, digitando:

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>

Questo copia i jar Seam, i jar dipendenti ed il driver JDBC nel nuovo progetto Eclipse, e genera tutte le risorse necessarie ed il file di configurazione, i file template di facelets ed i fogli di stile, assieme ai metadati di Eclipse e allo script per il build di Ant. Il progetto Eclipse verrà automaticamente deployato in una struttura di directory esplosa in JBoss AS non appena si aggiungerà il progetto usando New -> Project... -> General -> Project -> Next, digitando Project name (helloworld in questo caso), e poi cliccando Finish. Non selezionare Java Project dallo wizard New Project.

Se in Eclipse la JDK di default non è Java SE 5 o Java SE 6 JDK, occorre selezionare un JDK compatibile Java SE 5 usando Project -> Properties -> Java Compiler.

in alternativa, si può eseguire il deploy del progetto dal di fuori di Eclipse digitando seam explode.

Si vada in http://localhost:8080/helloworld per vedere la pagina di benvenuto. Questa è una pagina facelets, view/home.xhtml, che utilizza il template view/layout/template.xhtml. In Eclipse si può modificare questa pagina, oppure il template, e vedere immediatamente i risultati, cliccando il pulsante aggiorna del browser.

Non si abbia paura dell'XML, i documenti di configurazione generati nella directory di progetto. Per la maggiore parte delle volte sono standard per Java EE, parti che servono per creare la prima volta e poi non si guarderanno più, ed al 90% sono sempre le stesse per ogni progetto Seam. (Sono così facili da scrivere che anche seam-gen può farlo.)

Il progetto generato include tre database e le configurazioni per la persistenza. I file persistence-test.xml e import-test.sql vengono usati quando di eseguono i test di unità TestNG con HSQLDB. Lo schema del database ed i dati di test in import-test.sql vengono sempre esportati nel database prima dell'esecuzione dei test. I file myproject-dev-ds.xml, persistence-dev.xml e import-dev.sql sono usati per il deploy dell'applicazione nel database di sviluppo. Lo schema può essere esportato automaticamente durante il deploy, a seconda che si sia detto a seam-gen che si sta lavorando con un database esistente. I file myproject-prod-ds.xml, persistence-prod.xml e import-prod.sql sono usati per il deploy dell'applicazione nel database di produzione. Lo schema non viene esportato automaticamente durante il deploy.

Se si è abituati ad usare un framework web action-style, ci si domanderà come in Java sia possibile creare una semplice pagina web con un metodo d'azione stateless. Se si digita:

seam new-action

Seam chiederà alcune informazioni, e genererà per il progetto una nuova pagina facelets ed i componenti Seam.

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>

Poiché è stato aggiunto un nuovo componente Seam, occorre riavviare il deploy della directory esplosa. E' possibile farlo digitando seam restart, od eseguendo il target restart nel file build.xml del progetto generato all'interno di Eclipse. Un altro modo per forzare il riavvio è editare in Eclipse il file resources/META-INF/application.xml. Si noti che non occorre riavviare JBoss ogni volta che cambia l'applicazione.

Adesso si vada in http://localhost:8080/helloworld/ping.seam e si clicchi il pulsante. Si può vedere il codice sottostante l'azione guardando il progetto nella directory src. Si metta un breakpoint nel metodo ping(), e si clicchi nuovamente il pulsante.

Infine si cerchi il file PingTest.xml nel pacchetto dei test e si eseguano i test d'integrazione usando il plugin TestNG di Eclipse. In alternativa, si eseguano i test usando seam test od il target test del build generato.

Quando si fa il deploy di un'applicazione Seam come directory esplosa, si ottiene il supporto al deploy a caldo (hot deploy) durante il deploy. Occorre abilitare la modalità debug sia in Seam sia in Facelets, aggiungendo questa linea a components.xml:


<core:init debug="true"
>

Ora i seguenti file potranno essere rideployati senza riavviare l'applicazione:

Ma se si vuole cambiare il codice Java, occorre comunque riavviare nuovamente l'applicazione. (In JBoss questo può essere ottenuto toccando il descrittore di deploy: application.xml per un deploy con EAR, oppure web.xml per un deploy WAR.)

Ma se si vuole velocizzare il ciclo modifica/compila/testa, Seam supporta il redeploy incrementale dei compoenti JavaBean. Per usarlo occorre fare il deploy dei componenti JavaBean nella directory WEB-INF/dev, cosicché vengano caricati da uno speciale classloader di Seam, invece del classloader WAR o EAR.

Occorre essere consapevoli delle seguenti limitazioni:

Se si crea un progetto WAR usando seam-gen, il deploy incrementale a caldo è già abilitato per le classi collocate nella directory dei sorgenti src/hot. Comunque seam-gen non supporta il deploy incrementale a caldo per i progetti EAR.

JBoss Tool è una collezione di plugin Eclipse. JBoss Tool è un wizard per la creazione di progetti Seam, Content Assist per Unified Expression Language (EL) sia in facelets e codice Java, un editor grafico per jPDL, un editor grafico per i file di configurazione di Seam, supporta l'esecuzione dei test di integrazione di Seam dall'interno di Eclipse, e molto altro.

In breve, se sei un utilizzatore di Eclipse, allora vorrai JBoss Tools!

JBoss Tools, come con seam-gen, funziona meglio con JBoss AS, ma è possibile con alcuni accorgimenti far girare l'applicazione in altri application server. I cambiamenti sono più o meno quelli descritti più avanti per seam-gen in questa guida.

Avviare Eclipse e selezionare la prospettiva Seam.

Si vada in File -> New -> Seam Web Project.

Primo, inserire un nome per il nuovo progetto. Per questo tutorial si userà helloworld .

Ora, occorre dire a JBoss Tools dell'esistenza di JBoss AS. Questo è un processo in due fasi, primo occorre definire un runtime, assicurarsi di selezionare JBoss AS 4.2:

Inserire un nome per il runtime, e localizzarlo sul proprio hard disk:

Poi, occorre definire un server in cui JBoss Tools possa fare il deploy. Assicurarsi di selezionare ancora JBoss AS 4.2, ed anche il runtime appena definito:

Alla successiva schermata si dia un nome al server e si prema Finish:

Assicurarsi che siano selezionati il runtime ed il server appena creati, selezionare Dynamic Web Project with Seam 2.0 (technology preview) e premere Next:

Le prossime 3 schermate consentono di personalizzare ulteriormente il proprio progetto, ma per noi i valori di default vanno bene. Si prema Next fino ad arrivare alla schermata finale.

Il primo passo è dire a JBoss Tools quale download di Seam si vuole usare. Aggiungere un nuovo Seam Runtime - assicurarsi di dare un nome, e selezionare 2.0 come versione:

La scelta più importante da fare è tra deploy EAR e deploy WAR del proprio progetto. I progetti EAR supportano EJB 3.0 e richiede Java EE 5. I progetti WAR non supportano EJB 3.0, ma possono essere deployati in ambiente J2EE. Anche l'impacchettamento di un WAR è semplice da capire. Se si è installato un application server pronto per EJB3 come JBoss, si scelga EAR. Altrimenti, si scelga WAR. Assumeremo per il resto del tutorial che si sia scelto il deploy WAR, ma si possono seguire gli stessi passi per un deploy EAR.

Poi, si selezioni il tipo di database. Assumeremo di avere installato MySQL, con uno schema esistente. Occorrerà dire a JBoss Tools del database, selezionare MySQL come database, e creare un nuovo profilo di connessione. Selezionare Generic JDBC Connection:

Si metta un nome:

JBoss Tools non è fornito assieme ai driver dei database, quindi occorre specificare dove è collocato il driver MySQL JDBC. Si prema il pulsante ....

Trovare MySQL 5 e premere Add...:

Scegliere il template MySQL JDBC Driver:

Trovare il jar sul proprio computer scegliendo Edit Jar/Zip:

Riguardare lo username e la password usate per la connessione, e se corretti, premere Ok.

Infine scegliere il driver appena creato:

Se si sta lavorando con un modello di dati esistenti, assicurarsi di segnalare a JBoss Tools che le tabelle esistono già nel database.

Riguardare lo username e la password usate per la connessione, testare la connessione usando il pulsante Test Connection, e se funzionante, premere Finish:

Infine, rivedere i nomi dei pacchetti per i bean generati, e se si è soddisfatti, cliccare su Finish:

JBoss ha un supporto molto sofisticato per il redeploy a caldo (hot deploy) di WAR e EAR. Sfortunatamente, a causa di alcuni bug nella JVM, ripetuti redeploy di un EAR - comuni durante lo sviluppo - possono portare eventualmente ad errori di perm gen space nella JVM. Per questa ragione, raccomandiamo di eseguire JBoss in fase di sviluppo dentro una JVM con un ampio perm gen space. Suggeriamo i seguenti valori:

-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512
      

Se la memoria disponibile è poca, questa è la quantità minima suggerita:

-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256
      

Trovare il server in JBoss Server View, cliccare col tasto destro sul server e selezionare Edit Launch Configuration:

Poi si cambino gli argomenti VM:

Se non si vuole avere a che fare con queste configurazioni, non occorre farlo - si ritorni in questa sezione quando si vedrà la prima OutOfMemoryException.

Per avviare JBoss, e fare il deploy del progetto, cliccare col destro sul server creato e cliccare quindi Start, (o Debug per avviare in modalità Debug):

Non si abbia paura dei documenti di configurazione in XML che sono stati creati nella directory di progetto. La maggior parte riguardano Java EE, e sono creati la prima volta e poi non vengono più toccati. Al 90% sono sempre gli stessi nei vari progetti Seam.

JBoss Tools supporta il deploy a caldo incrementale di:

out of the box.

Ma se si volesse cambiare il codice Java, servirebbe eseguire un riavvio completo dell'applicazione facendo un Full Publish.

Ma sesi vuole veramente un ciclo veloce modifica/compila/testa, Seam supporta il redeploy incrementale dei componenti JavaBean. Per usare questa funzionalità, occorre fare il deploy dei componenti JavaBean nella directory WEB-INF/dev, affinché vengano caricati da uno speciale classloader di Seam, anziché dal classloader dei WAR e EAR.

Occorre essere consapevoli delle seguenti limitazioni:

Sesi crea un progetto WAR usando JBoss Tools, l'hot deploy incrementale è disponibile di default per le classi contenute nella directory sorgente src/action. Comunque, JBoss Tools non supporta il deploy incrementale a caldo per progetti EAR.

I due concetti di base in Seam sono la nozione di contesto e la nozione di componente. I componenti sono oggetti stateful, solitamente EJB, e un'istanza di un componente viene associata al contesto, con un nome in tale contesto. La bijection fornisce un meccanismo per dare un alias ai nomi dei componenti interni (variabili d'istanza) associati ai nomi dei contesti, consentendo agli alberi dei componenti di essere dinamicamente assemblati e riassemblati da Seam.

Segue ora la descrizione dei contesti predefiniti in Seam.

I contesti di Seam vengono creati e distrutti dal framework. L'applicazione non controlla la demarcazione dei contesti tramite esplicite chiamate dell'API Java. I contesti sono solitamente impliciti. In alcuni casi, comunque, i contesti sono demarcati tramite annotazioni.

I contesti base di Seam sono:

Si riconosceranno alcuni di questi contesti dai servlet e dalle relative specifiche. Comunque due di questi potrebbero risultare nuovi: conversation context, e business process context. La gestione dello stato nelle applicazioni web è così fragile e propenso all'errore che i tre contesti predefiniti (richiesta, sessione ed applicazione) non sono significativi dal punto di vista della logica di business. Una sessione utente di login, per esempio, è praticamente un costrutto arbitrario in termini di workflow dell'applicazione. Quindi la maggior parte dei componenti Seam hanno scope nei contesti di conversazione e business process, poiché sono i contesti più significativi in termini di applicazione.

Ora si analizza ciascun contesto.

Il contesto conversazione è un concetto fondamentale in Seam. Una conversazione è una unità di lavoro dal punto di vista dell'utente. Può dar vita a diverse interazioni con l'utente, diverse richieste, e diverse transazioni di database. Ma per l'utente, una conversazione risolve un singolo problema. Per esempio, "Prenota hotel", "Approva contratto", "Crea ordine" sono tutte conversazioni. Si può pensare alla conversazione come all'implementazione di un singolo "caso d'uso" o "user story", ma la relazione non è esattamente uguale.

A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.

It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!

Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.

Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.

Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.

Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.

Seam serializza il processo delle richieste concorrenti che prendono posto nello stesso contesto di conversazione long-running, nello stesso processo.

Alternatively, Seam may be configured to keep conversational state in the client browser.

Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing threadsafeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.

This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.

The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.

Since the session context is multithreaded, and often contains volatile state, session scope components are always protected by Seam from concurrent access so long as the Seam interceptors are not disabled for that component. If interceptors are disabled, then any thread-safety that is required must be implemented by the component itself. Seam serializes requests to session scope session beans and JavaBeans by default (and detects and breaks any deadlocks that occur). This is not the default behaviour for application scoped components however, since application scoped components do not usually hold volatile state and because synchronization at the global level is extremely expensive. However, you can force a serialized threading model on any session bean or JavaBean component by adding the @Synchronized annotation.

This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.

Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.

All seam components need a name. We can assign a name to a component using the @Name annotation:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    ... 
}

This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.

@Name is not the only way to define a component name, but we always need to specify the name somewhere. If we don't, then none of the other Seam annotations will function.

Whenever Seam instantiates a component, it binds the new instance to a variable in the scope configured for the component that matches the component name. This behavior is identical to how JSF managed beans work, except that Seam allows you to configure this mapping using annotations rather than XML. You can also programmatically bind a component to a context variable. This is useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable. Be careful, though, because through a programmatic assignment, it's possible to overwrite a context variable that has a reference to a Seam component, potentially confusing matters.

For very large applications, and for built-in seam components, qualified component names are often used to avoid naming conflicts.

@Name("com.jboss.myapp.loginAction")

@Stateless
public class LoginAction implements Login { 
    ... 
}

We may use the qualified component name both in Java code and in JSF's expression language:


<h:commandButton type="submit" value="Login"
                 action="#{com.jboss.myapp.loginAction.login}"/>

Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml file:


<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

All of the built-in Seam components have qualified names but can be accessed through their unqualified names due to the namespace import feature of Seam. The components.xml file included in the Seam JAR defines the following namespaces.

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

When attempting to resolve an unqualified name, Seam will check each of those namespaces, in order. You can include additional namespaces in your application's components.xml file for application-specific namespaces.