SeamFramework.orgCommunity Documentation

Seam - Componenti Contestuali

Un framework per Java Enterprise

2.1.2.CR2


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
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. Definire lo scope di un componente
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. L'interfaccia Mutable e @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. Conversione e validazione
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. Integrazione con il framework Spring
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. Integrazione con Guice
28.1. Creazione di un componente ibrido Seam-Guice
28.2. Configurare un injector
28.3. Uso di injector multipli
29. Hibernate Search
29.1. Introduzione
29.2. Configurazione
29.3. Utilizzo
30. Configurare Seam ed impacchettare le applicazioni Seam
30.1. Configurazione base di Seam
30.1.1. Integrazione di Seam con JSF ed il servlet container
30.1.2. Usare Facelets
30.1.3. Resource Servlet di Seam
30.1.4. Filtri servlet di Seam
30.1.5. Integrating Seam with your EJB container
30.1.6. Non dimenticare!
30.2. Uso di provider JPA alternativi
30.3. Configurazione di Seam in java EE 5
30.3.1. Packaging
30.4. Configurare Seam in J2EE
30.4.1. Boostrapping di Hibernate in Seam
30.4.2. Boostrapping di JPA in Seam
30.4.3. Packaging
30.5. Configurazione di Seam in java EE 5 senza JBoss Embedded
30.6. Configurazione di Seam in java EE 5 con JBoss Embedded
30.6.1. Installare JBoss Embedded
30.6.2. Packaging
30.7. Configurazione jBPM in Seam
30.7.1. Packaging
30.8. Configurazione di SFSB e dei timeout di sessione in JBoss AS
30.9. Esecuzione di Seam in un Portlet
30.10. Deploying custom resources
31. Annotazioni di Seam
31.1. Annotazioni per la definizione di un componente
31.2. Annotazioni per la bijection
31.3. Annotazioni per i metodi del ciclo di vita dei componenti
31.4. Annotazioni per la demarcazione del contesto
31.5. Annotazioni per l'uso con i componenti JavaBean di Seam in ambiente J2EE
31.6. Annotazioni per le eccezioni
31.7. Annotazioni per Seam Remoting
31.8. Annotazioni per gli interceptor di Seam
31.9. Annotazioni per l'asincronicità
31.10. Annotazioni per l'uso di JSF
31.10.1. Annotazioni per l'uso con dataTable
31.11. Meta-annotationi per il databinding
31.12. Annotazioni per i pacchetti
31.13. Annotazioni per l'integrazione con un servlet container
32. Componenti Seam predefiniti
32.1. Componenti per l'iniezione del contesto
32.2. Componenti JSF
32.3. Componenti d'utilità
32.4. Componenti per l'internazionalizzazione ed i temi
32.5. Componenti per il controllo delle conversazioni.
32.6. Componenti per jBPM
32.7. Componenti per la sicurezza
32.8. Componenti per JMS
32.9. Componenti relativi alla Mail
32.10. Componenti infrastrutturali
32.11. Componenti misti
32.12. Componenti speciali
33. Controlli JSF di Seam
33.1. Tag
33.1.1. Controlli di navigazione
33.1.2. Convertitori e Validatori
33.1.3. Formattazione
33.1.4. Seam Text
33.1.5. Supporto per le form
33.1.6. Altro
33.2. Annotazioni
34. JBoss EL
34.1. Espressioni parametrizzate
34.1.1. Utilizzo
34.1.2. Limitazioni e suggerimenti
34.2. Proiezione
35. Tuning delle performance
35.1. Bypassare gli interceptor
36. Test delle applicazioni Seam
36.1. Test d'unità dei componenti Seam
36.2. Test d'integrazione dei componenti Seam
36.2.1. Uso dei mock nei test d'intergrazione
36.3. Test d'integrazione delle interazioni utente in applicazioni Seam
36.3.1. Configurazione
36.3.2. Uso di SeamTest con un altro framework di test
36.3.3. Test d'integrazione con Dati Mock
36.3.4. Test d'integrazione di Seam Mail
37. Strumenti di Seam
37.1. Visualizzatore e designer jBPM
37.1.1. Designer del processo di business
37.1.2. Visualizzatore Pageflow
38. Seam on BEA's Weblogic
38.1. Installation and operation of Weblogic
38.1.1. Installing 10.3
38.1.2. Creating your Weblogic domain
38.1.3. How to Start/Stop/Access your domain
38.1.4. Setting up Weblogic's JSF Support
38.2. The jee5/booking Example
38.2.1. EJB3 Issues with Weblogic
38.2.2. Getting the jee5/booking Working
38.3. The jpa booking example
38.3.1. Building and deploying jpa booking example
38.3.2. What's different with Weblogic 10.x
38.4. Deploying an application created using seam-gen on Weblogic 10.x
38.4.1. Running seam-gen setup
38.4.2. What to change for Weblogic 10.X
38.4.3. Building and Deploying your application
39. Seam on IBM's Websphere AS
39.1. Websphere AS environment and deployment information
39.1.1. Installation versions
39.2. The jee5/booking example
39.2.1. Configuration file changes
39.2.2. Building the jee5/booking example
39.2.3. Deploying the application to Websphere
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 Websphere AS V7
39.4. Deploying an application created using seam-gen on Websphere V7
39.4.1. Running seam-gen Setup
39.4.2. Changes needed for deployment to Websphere
40. Seam on GlassFish application server
40.1. GlassFish environment and deployment information
40.1.1. Installazione
40.2. The jee5/booking example
40.2.1. Building the jee5/booking example
40.2.2. Deploying the application to GlassFish
40.3. The jpa booking example
40.3.1. Building the jpa example
40.3.2. Deploying the jpa example
40.3.3. What's different for GlassFish v2 UR2
40.4. Deploying an application generated by seam-gen on GlassFish v2 UR2
40.4.1. Eseguire il setup in seam-gen
40.4.2. Changes needed for deployment to GlassFish
41. Dipendenze
41.1. Dipendenze JDK
41.1.1. Considerazioni su JDK 6 di Sun
41.2. Dipendenze del progetto
41.2.1. Core
41.2.2. RichFaces
41.2.3. Seam Mail
41.2.4. Seam PDF
41.2.5. Seam Microsoft® Excel®
41.2.6. Supporto Seam RSS
41.2.7. JBoss Rules
41.2.8. JBPM
41.2.9. GWT
41.2.10. Spring
41.2.11. Groovy
41.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 30.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 in caso di bisogno.

Assicurarsi di avere JDK 5 o JDK 6 (vedere Sezione 41.1, «Dipendenze JDK» per maggiori dettagli), JBoss AS 4.2 o 5.0 e Ant 1.7.0, 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, 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 project workspace (the directory that contains your Seam projects) [C:/Projects] [C:/Projects]
/Users/pmuir/workspace
    [input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA] [C:/Program Files/jboss-4.2.3.GA]
/Applications/jboss-4.2.3.GA
    [input] Enter the project name [myproject] [myproject]
helloworld
     [echo] Accepted project name as: helloworld
    [input] Select a RichFaces skin (not applicable if using ICEFaces) [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)

    [input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear]  ([ear], war, )

    [input] Enter the Java package name for your session beans [com.mydomain.helloworld] [com.mydomain.helloworld]
org.jboss.helloworld
    [input] Enter the Java package name for your entity beans [org.jboss.helloworld] [org.jboss.helloworld]

    [input] Enter the Java package name for your test cases [org.jboss.helloworld.test] [org.jboss.helloworld.test]

    [input] What kind of database are you using? [hsql]  ([hsql], mysql, oracle, postgres, mssql, db2, sybase, enterprisedb, h2)
mysql
    [input] Enter the Hibernate dialect for your database [org.hibernate.dialect.MySQLDialect] [org.hibernate.dialect.MySQLDialect]

    [input] Enter the filesystem path to the JDBC driver jar [lib/hsqldb.jar] [lib/hsqldb.jar]
/Users/pmuir/java/mysql.jar
    [input] Enter JDBC driver class for your database [com.mysql.jdbc.Driver] [com.mysql.jdbc.Driver]

    [input] Enter the JDBC URL for your database [jdbc:mysql:///test] [jdbc:mysql:///test]
jdbc:mysql:///helloworld
    [input] Enter database username [sa] [sa]
pmuir
    [input] Enter database password [] []

    [input] skipping input as property hibernate.default_schema.new has already been set.
    [input] Enter the database catalog name (it is OK to leave this blank) [] []

    [input] Are you working with tables that already exist in the database? [n]  (y, [n], )
y
    [input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n]  (y, [n], )
n
    [input] Enter your ICEfaces home directory (leave blank to omit ICEfaces) [] []

[propertyfile] Creating new property file: /Users/pmuir/workspace/jboss-seam/seam-gen/build.properties
     [echo] Installing JDBC driver jar to JBoss server
     [echo] Type 'seam create-project' to create the new project

BUILD SUCCESSFUL
Total time: 1 minute 32 seconds
~/workspace/jboss-seam $ 

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. In questo esempio si usa JBoss AS 4.2, anche se è certamente possibile usare anche JBoss AS 5.0. 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.

Una conversazione mantiene lo stato associato a "cosa l'utente sta facendo adesso, in questa finestra". Un singolo utente potrebbe avere più conversazioni in corso in ogni momento, solitamente in più finestre. Il contesto conversazione assicura che lo stato delle diverse conversazioni non collida e non causi problemi.

Potrebbe volerci un pò di tempo prima di abituarsi a pensare applicazioni in termini di conversazione, ma una volta abituati, pensiamo che ci si appassionerà e non si riuscirà più a non pensare in altri termini!

Alcune conversazioni durano solo una singola richiesta. Le conversazioni che si prolungano attraverso più richieste devono essere marcate usando le annotazioni previste da Seam.

Alcune conversazioni sono anche task. Un task è una conversazione che è significativa in termini di processo di business long-running, ed ha il potenziale per lanciare una transizione di stato per il processo di business quando completa con successo. Seam fornisce uno speciale set di annotazioni per la demarcazione dei task.

Le conversazioni possono essere inestate, con una conversazione che ha posto "dentro" una conversazione più ampia. Questa è una caretteristica avanzata.

Solitamente lo stato della conversazione è mantenuto da Seam in una sessione servlet tra le richieste. Seam implementa dei timeout di conversazione configurabili, che automaticamente distruggono le conversazioni inattive, e quindi assicurano che lo stato mantenuto da una singola sessione utente non cresca senza limiti se l'utente abbandona le conversazioni.

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

In alternativa Seam può essere configurato per mantenere lo stato conversazionale nel browser.

Né il servlet né le specifiche EJB definiscono dei modi per gestire le richieste correnti originate dallo stesso client. Il servlet container semplicemente lascia girare tutti i thread in modo concorrente e lascia la gestione della sicurezza dei thread al codice dell'applicazione. Il container EJB consente di accedere ai componenti stateless e lancia un'eccezione se dei thread multipli accedono ad un bean di sessione stateful.

Questo comportamento potrebbe risultare corretto nel vecchio stile delle applicazioni web, che erano basate su richieste sincrone con granularità fine. Ma per le moderne applicazioni che fanno ampio uso di molte richieste asincrone (AJAX) a granularità fine, la concorrenza è un fattore vitale e deve essere supportato dal modello di programmazione. Seam possiede un layer per la gestione della concorrenza nel suo modello di contesto.

I contesti Seam di sessione e applicazione sono multithread. Seam lascia le richieste concorrenti in un contesto che verrà processato in modo concorrente. I contesti evento e pagina sono per natura a singolo thread. Il contesto business è strettamente multithread, ma in pratica la concorenza è abbastanza rara e questo fatto può essere ignorato per la maggior parte delle volte. Infine Seam forza un modello a singolo thread per conversazione per processo per il contesto conversazione, serializzando le richieste concorrenti nello stesso contesto di conversazione long-running.

Poiché il contesto sessione è multithread, e spesso contiene uno stato volatile, i componenti con scope sessione sono sempre protetti da Seam verso accessi concorrenti fintantoché gli interceptor Seam non vengano disabilitati per quel componente. Se gli interceptor sono disabilitati, allora ogni sicurezza di thread che viene richiesta deve essere implementata dal componente stesso. Seam serializza di default le richieste a bean con scope sessione e JavaBean (e rileva e rompe ogni deadlock che sopravviene). Questo non è il comportamento di default per i componenti con scope applicazione, poiché tali componenti solitamente non mantengono uno stato volatile e poiché la sincronizzazione a livello globale è estremamente dispensiosa. Comunque si può forzare il modello di thread serializzato su un qualsiasi session bean o componente JavaBean aggiungendo l'annotazione @Synchronized.

Questo modello di concorrenza signifca che i client AJAX possono usare in modo sicuro le sessioni volatili e lo stato conversazionale, senza il bisogno di alcun lavoro speciale da parte dello sviluppatore.

I componenti Seam sono POJO (Plain Old Java Objects). In particolare sono JavaBean o bean enterprise EJB 3.0. Mentre Seam non richiede che i componenti siano EJB e possono anche essere usati senza un container EJB 3.0, Seam è stato progettato con EJB 3.0 in mente ed realizza una profonda integrazione con EJB 3.0. Seam supporta i seguenti tipi di componenti.

Gli entity bean possono essere associati ad una variabile di contesto e funzionare come componenti Seam. Poiché gli entity hanno un'identità persistente in aggiunta alla loro identità contestuale, le istanze entity sono solitamente associate esplicitamente nel codice Java, piuttosto che essere istanziate implicitamente da Seam.

I componenti entity bean non supportano la bijection o la demarcazione di contesto. E neppure l'invocazione della validazione dell'entity bean (trigger).

Gli entity bean non sono solitamente usati come action listener JSF, ma spesso funzionano come backing bean che forniscono proprietà ai componenti JSF per la visualizzazione o la sottomissione di una form. In particolare è comune usare un entity come backing bean, assieme ad un action listener bean di sessione stateless per implementare funzionalità di tipo crea/aggiorna/cancella.

Di default gli entity bean sono associati al contesto conversazione. Non possono mai essere associati al contesto stateless.

Si noti che in un ambiente cluster è meno efficiente associare un entity bean direttamente ad una variabile di contesto con scope conversazione o sessione rispetto a come sarebbe mantenere un riferimento all'entity bean in un bean di sessione stateful. PEr questa ragione non tutte le applicazioni Seam definiscono entity bean come componenti Seam.

I componenti entity bean di Seam possono essere istanziati usando Component.getInstance(), @In(create=true) o direttamente usando l'operatore new.

Tutti i componenti Seam devono avere un nome. Si può assegnare un nome al componente usando l'annotazione @Name:

@Name("loginAction")

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

Questo nome è il nome del componente Seam e non è relazionato a nessun altro nome definito dalla specifica EJB. Comunque i nomi dei componenti Seam funzionano solo come nomi per i bean gestiti da JSF e si possono ritenere questi due concetti come identici.

@Name non è il solo modo per definire un nome di componente, ma occorre sempre specificare il nome da qualche parte. Altrimenti nessun'altra annotazione di Seam funzionerà.

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.

Per applicazioni estese e per i componenti predefiniti di seam, vengono spesso impiegati i nomi qualificati dei componenti per evitare conflitti di nome.

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

Dependency injection or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable. In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable. On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:

In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.

The @In annotation specifies that a value should be injected, either into an instance variable:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In User user;
    ... 
}

o nel metodo setter:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    ... 
}

By default, Seam will do a priority search of all contexts, using the name of the property or instance variable that is being injected. You may wish to specify the context variable name explicitly, using, for example, @In("currentUser").

If you want Seam to create an instance of the component when there is no existing component instance bound to the named context variable, you should specify @In(create=true). If the value is optional (it can be null), specify @In(required=false).

For some components, it can be repetitive to have to specify @In(create=true) everywhere they are used. In such cases, you can annotate the component @AutoCreate, and then it will always be created, whenever needed, even without the explicit use of create=true.

Si può anche iniettare il valore di un'espressione:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In("#{user.username}") String username;
    ... 
}

I valori iniettati sono disiniettati (cioè impostati a null) immediatamente dopo il completamento del metodo e dell'outjection.

(There is much more information about component lifecycle and injection in the next chapter.)

L'annotazione @Out specifica che occorre eseguire l'outjection di un attributo, oppure da una variabile d'istanza:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @Out User user;
    ... 
}

o dal metodo getter:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

Di un attributo si può fare sia l'injection sia l'outjection:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In @Out User user;
    ... 
}

o:

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

The @Install annotation lets you control conditional installation of components that are required in some deployment scenarios and not in others. This is useful if:

@Install works by letting you specify precedence and dependencies.

The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precendence. There are some predefined precedence values (in ascending order):

Suppose we have a component named messageSender that talks to a JMS queue.

@Name("messageSender") 

public class MessageSender {
    public void sendMessage() {
        //do something with JMS
    }
}

In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:

@Name("messageSender") 

@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
    public void sendMessage() {
        //do nothing!
    }
}

The precedence helps Seam decide which version to use when it finds both components in the classpath.

This is nice if we are able to control exactly which classes are in the classpath. But if I'm writing a reusable framework with many dependecies, I don't want to have to break that framework across many jars. I want to be able to decide which components to install depending upon what other components are installed, and upon what classes are available in the classpath. The @Install annotation also controls this functionality. Seam uses this mechanism internally to enable conditional installation of many of the built-in components. However, you probably won't need to use it in your application.

Chi non è nauseato dal vedere codice incasinato come questo?

private static final Log log = LogFactory.getLog(CreateOrderAction.class);

        
public Order createOrder(User user, Product product, int quantity) {
    if ( log.isDebugEnabled() ) {
        log.debug("Creating new order for user: " + user.username() + 
            " product: " + product.name() 
            + " quantity: " + quantity);
    }
    return new Order(user, product, quantity);
}

It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.

Seam fornisce un'API per il logging che semplifica in modo significativo questo codice:

@Logger private Log log;

        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
    return new Order(user, product, quantity);
}

Non importa se si dichiara la variabile log statica o no — funzionerà in entrambi i modi, tranne per i componenti entity bean che richiedono la variabile log statica.

Note that we don't need the noisy if ( log.isDebugEnabled() ) guard, since string concatenation happens inside the debug() method. Note also that we don't usually need to specify the log category explicitly, since Seam knows what component it is injecting the Log into.

If User and Product are Seam components available in the current contexts, it gets even better:

@Logger private Log log;

        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
    return new Order(user, product, quantity);
}

Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.

Many application servers feature an amazingly broken implementation of HttpSession clustering, where changes to the state of mutable objects bound to the session are only replicated when the application calls setAttribute() explicitly. This is a source of bugs that can not effectively be tested for at development time, since they will only manifest when failover occurs. Furthermore, the actual replication message contains the entire serialized object graph bound to the session attribute, which is inefficient.

Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.

For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by calling setAttribute() once in every request that the component was invoked by the application. Of course, this strategy is inefficient for read-mostly components. You can control this behavior by implementing the org.jboss.seam.core.Mutable interface, or by extending org.jboss.seam.core.AbstractMutable, and writing your own dirty-checking logic inside the component. For example,

@Name("account")

public class Account extends AbstractMutable
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        setDirty(this.balance, balance);
        this.balance = balance;
    }
    
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

O si può usare l'annotazione @ReadOnly per ottenere un effetto simile:

@Name("account")

public class Account
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        this.balance = balance;
    }
    
    @ReadOnly
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute() once in every request, unless the (conversation-scoped) entity is currently associated with a Seam-managed persistence context, in which case no replication is needed. This strategy is not necessarily efficient, so session or conversation scope entity beans should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the entity bean instance. For example,

@Stateful

@Name("account")
public class AccountManager extends AbstractMutable
{
    private Account account; // an entity bean
    
    @Unwrap
    public Account getAccount()
    {
        return account;
    }
    
    ...
    
}

Note that the EntityHome class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component.

We often need to work with objects that are not Seam components. But we still want to be able to inject them into our components using @In and use them in value and method binding expressions, etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy, for example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple of nice features that make it easier to work with non-component objects bound to contexts.

The factory component pattern lets a Seam component act as the instantiator for a non-component object. A factory method will be called when a context variable is referenced but has no value bound to it. We define factory methods using the @Factory annotation. The factory method binds a value to the context variable, and determines the scope of the bound value. There are two styles of factory method. The first style returns a value, which is bound to the context by Seam:

@Factory(scope=CONVERSATION)

public List<Customer
> getCustomerList() { 
    return ... ;
} 

Il secondo stile è un metodo di tipo void che associa il valore alla variabile di contesto stessa:

@DataModel List<Customer

> customerList;
@Factory("customerList")
public void initCustomerList() { 
    customerList = ...  ;
} 

In both cases, the factory method is called when we reference the customerList context variable and its value is null, and then has no further part to play in the lifecycle of the value. An even more powerful pattern is the manager component pattern. In this case, we have a Seam component that is bound to a context variable, that manages the value of the context variable, while remaining invisible to clients.

A manager component is any component with an @Unwrap method. This method returns the value that will be visable to clients, and is called every time a context variable is referenced.

@Name("customerList")

@Scope(CONVERSATION)
public class CustomerListManager
{
    ...
    
    @Unwrap
    public List<Customer
> getCustomerList() { 
        return ... ;
    }
}

The manager component pattern is especially useful if we have an object where you need more control over the lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when the context ends you could @Unwrap the object, and perform cleanup in the @Destroy method of the manager component.

@Name("hens")

@Scope(APPLICATION) 
public class HenHouse
{
    Set<Hen
> hens;
    
    @In(required=false) Hen hen;
    
    @Unwrap
    public List<Hen
> getHens()
    {
        if (hens == null)
        {
            // Setup our hens
        }
        return hens;
    }
    
    @Observer({"chickBorn", "chickenBoughtAtMarket"})
    public addHen()
    {
        hens.add(hen);
    }
    
    @Observer("chickenSoldAtMarket")
    public removeHen()
    {
        hens.remove(hen);
    }
    
    @Observer("foxGetsIn")
    public removeAllHens()
    {
        hens.clear();
    }
    ...
} 

Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.

La filosofia di minimizzare la configurazione basata su XML è estremamente forte in Seam. Tuttavia ci sono varie ragioni per voler configurare i componente Seam tramite XML: per isolare le informazioni specifiche del deploy dal codice Java, per abilitare la creazione di framework riutilizzabili, per configurare le funzionalità predefinite di Seam, ecc. Seam fornisce due approcci base per configurare i componenti: configurazione tramire impostazioni di proprietà in un file di proprietà o in web.xml, e configurazione tramite components.xml.

Il file components.xml è un poco più potente delle impostazioni di proprietà. Esso consente di:

Un file components.xml può apparire in una delle tre seguenti posizioni:

Solitamente i componenti Seam vengono installati quando lo scanner di deploy scopre una classe con una annotazione @Name collocata in un archivio con un file seam.properties o un file META-INF/components.xml. (Amenoché il componente abbia una annotazione @Install che indichi che non debba essere installato di default). Il file components.xml consente di gestire i casi speciali in cui occorra fare override delle annotazioni.

Per esempio, il seguente file components.xml installa jBPM:


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:bpm="http://jboss.com/products/seam/bpm">
    <bpm:jbpm/>
</components
>

Questo esempio fa la stessa cosa:


<components>
    <component class="org.jboss.seam.bpm.Jbpm"/>
</components
>

Questo installa e configura due differenti contesti di persistenza gestiti da Seam:


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

    <persistence:managed-persistence-context name="customerDatabase"
                       persistence-unit-jndi-name="java:/customerEntityManagerFactory"/>
        
    <persistence:managed-persistence-context name="accountingDatabase"
                       persistence-unit-jndi-name="java:/accountingEntityManagerFactory"/>            

</components
>

Ed anche questo fa lo stesso:


<components>
    <component name="customerDatabase" 
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/customerEntityManagerFactory</property>
    </component>
    
    <component name="accountingDatabase"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/accountingEntityManagerFactory</property>
    </component>
</components
>

Questo esempio crea un contesto di persistenza gestito da Seam con scope di sessione (questa non è una pratica raccomandata):


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                          scope="session"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
              scope="session"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

E' comune utilizzare l'opzione auto-create per gli oggetti infrastrutturali quali i contesti di persistenza, che risparmia dal dovere specificare esplicitamente create=true quando si usa l'annotazione @In.


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                    auto-create="true"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
        auto-create="true"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

La dichiarazione <factory> consente di specificare un valore o un'espressione di method binding che verrà valutata per inizializzare il valore di una variabile di contesto quando viene referenziata la prima volta.


<components>

    <factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>

</components
>

Si può creare un "alias" (un secondo nome) per un componente Seam in questo modo:


<components>

    <factory name="user" value="#{actor}" scope="STATELESS"/>

</components
>

Si può anche creare un "alias" per un'espressione comunemente usata:


<components>

    <factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>

</components
>

E' comune vedere usato auto-create="true" con la dichiarazione <factory>:


<components>

    <factory name="session" value="#{entityManager.delegate}" scope="STATELESS" auto-create="true"/>

</components
>

A volte si vuole riutilizzare lo stesso file components.xml con piccoli cambiamenti durante il deploy ed il testing. Seam consente di mettere dei wildcard della forma @wildcard@ nel file components.xml che può essere rimpiazzato o dallo script Ant (a deployment time) o fornendo un file chiamato components.properties nel classpath (a development time). Si vedrà usato quest'ultimo approccio negli esempi di Seam.

Le proprietà dei tipi stringa, primitivi o wrapper primitivi possono essere configurati solo come atteso:

org.jboss.seam.core.manager.conversationTimeout 60000

<core:manager conversation-timeout="60000"/>

<component name="org.jboss.seam.core.manager">
    <property name="conversationTimeout"
>60000</property>
</component
>

Anche array, set e liste di stringhe o primitivi sono supportati:

org.jboss.seam.bpm.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml

<bpm:jbpm>
    <bpm:process-definitions>
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </bpm:process-definitions>
</bpm:jbpm
>

<component name="org.jboss.seam.bpm.jbpm">
    <property name="processDefinitions">
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </property>
</component
>

Anche le mappe con chiavi associate a stringhe oppure valori stringa o primitivi sono supportati:


<component name="issueEditor">
    <property name="issueStatuses">
        <key
>open</key
> <value
>open issue</value>
        <key
>resolved</key
> <value
>issue resolved by developer</value>
        <key
>closed</key
> <value
>resolution accepted by user</value>
    </property>
</component
>

Quando si configurano le proprietà multivalore, Seam preserverà di default l'ordine in cui vengono messi gli attributi in components.xml (amenoché venga usato SortedSet/SortedMap allora Seam userà TreeMap/TreeSet). Se la proprietà ha un tipo concreto (per esempio LinkedList) Seam userà quel tipo.

Si può anche fare l'override del tipo specificando un nome di classe pienamente qualificato:


<component name="issueEditor">
   <property name="issueStatusOptions" type="java.util.LinkedHashMap">
      <key
>open</key
> <value
>open issue</value>
      <key
>resolved</key
> <value
>issue resolved by developer</value>
      <key
>closed</key
> <value
>resolution accepted by user</value>
   </property>
</component
>

Infine si può unire assieme i componenti usando un'espressione value-binding. Si noti che è diverso dall'usare l'iniezione con @In, poiché avviene al momento dell'istanziamento del componente invece che al momento dell'invocazione. E' quindi molto più simile alle strutture con dependency injection offerte dai tradizionali IoC container come JSF o Spring.


<drools:managed-working-memory name="policyPricingWorkingMemory"
    rule-base="#{policyPricingRules}"/>

<component name="policyPricingWorkingMemory"
    class="org.jboss.seam.drools.ManagedWorkingMemory">
    <property name="ruleBase"
>#{policyPricingRules}</property>
</component
>

Seam risolve anche un'espressione stringa EL prima di assegnare il valore iniziale alla proprietà del bean del componente. Quindi si possono iniettare alcuni dati di contesto nei componenti.


<component name="greeter" class="com.example.action.Greeter">
    <property name="message"
>Nice to see you, #{identity.username}!</property>
</component
>

C'è un'importante eccezione. Se un tipo di proprietà a cui il valore iniziale assegnato è o una ValueExpression di Seam o una MethodExpression, allora la valutazione di EL è rimandata. Invece il wrapper dell'espressione appropriata viene creato e assegnato alla proprietà. I modelli di messaggi nel componente Home dell'Applicazione Framework di Seam servono da esempio.


<framework:entity-home name="myEntityHome"
    class="com.example.action.MyEntityHome" entity-class="com.example.model.MyEntity"
    created-message="'#{myEntityHome.instance.name}' has been successfully added."/>

Dentro il componente sipuò accedere all'espressione di stringa chiamando getExpressionString() sulla ValueExpression o MethodExpression. Se la proprietà è una ValueExpression, si può risolvere il valore usando getValue() e se la proprietà è un MethodExpression, si può invocare il metodo usando invoke(Object args...). Ovviamente per assegnare un valore alla proprietà MethodExpression, l'intero valore iniziale deve essere una singola espressione EL.

Attraverso gli esempi ci sono stati due modi per dichiarare i componenti: con e senza l'uso di namespace XML. Il seguente mostra un tipico file components.xml senza namespace:


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

    <component class="org.jboss.seam.core.init">
        <property name="debug"
>true</property>
        <property name="jndiPattern"
>@jndiPattern@</property>
    </component>
    
</components
>

Come si può vedere, è abbastanza prolisso. Ancor peggio, i nomi del componente e dell'attributo non possono essere validati a development time.

La versione con namespace appare come:


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

    <core:init debug="true" jndi-pattern="@jndiPattern@"/>

</components
>

Anche se le dichiarazioni di schema sono lunghe, il contenuto vero di XML è piatto e facile da capire. Gli schemi forniscono informazioni dettagliate su ogni componente e sugli attributi disponibili, consentendo agli editor XML di offrire un autocompletamento intelligente. L'uso di elementi con namespace semplifica molto la generazione ed il mantenimento in uno stato corretto dei file components.xml.

Questo funziona bene per i componenti predefiniti di Seam, ma per i componenti creati dall'utente? Ci sono due opzioni. La prima, Seam supporta un misto dei due modelli, consentendo l'uso delle dichiarazioni generiche <component> for i componenti utente, assieme alle dichiarazioni con namespace dei componenti predefiniti. Ma ancor meglio, Seam consente di dichiarare in modo veloce i namespace per i propri componenti.

Qualsiasi pacchetto Java può essere associato ad un namespace XML annotando il pacchetto con l'annotazione @Namespace. (Le annotazioni a livello pacchetto vengono dichiarate in un file chiamato package-info.java nella directory del pacchetto.) Ecco un esempio tratto dalla demo seampay:

@Namespace(value="http://jboss.com/products/seam/examples/seampay")

package org.jboss.seam.example.seampay;
import org.jboss.seam.annotations.Namespace;

Questo è tutto ciò che bisogna fare per utilizzare lo stile namespace in components.xml! Adesso si può scrivere:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home new-instance="#{newPayment}"
                      created-message="Created a new payment to #{newPayment.payee}" />

    <pay:payment name="newPayment"
                 payee="Somebody"
                 account="#{selectedAccount}"
                 payment-date="#{currentDatetime}"
                 created-date="#{currentDatetime}" />
     ...
</components
>

Oppure:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home>
        <pay:new-instance
>"#{newPayment}"</pay:new-instance>
        <pay:created-message
>Created a new payment to #{newPayment.payee}</pay:created-message>
    </pay:payment-home>
    
    <pay:payment name="newPayment">
        <pay:payee
>Somebody"</pay:payee>
        <pay:account
>#{selectedAccount}</pay:account>
        <pay:payment-date
>#{currentDatetime}</pay:payment-date>
        <pay:created-date
>#{currentDatetime}</pay:created-date>
     </pay:payment>
     ...
</components
>

Questi esempi illustrano i due usi modi d'uso di un elemento con namespace. Nella prima dichiarazione, <pay:payment-home> referenzia il componente paymentHome:

package org.jboss.seam.example.seampay;

...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

Il nome dell'elemento è una forma con trattino d'unione del nome del componente. Gli attributi dell'elemento sono la forma con trattino dei nomi delle proprietà.

Nella seconda dichiarazione, l'elemento <pay:payment> fa riferimento alla classe Payment nel pacchetto org.jboss.seam.example.seampay. In questo caso Payment è un entity dichiarato come componente Seam:

package org.jboss.seam.example.seampay;

...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

Se si vuole far funzionare la validazione e l'autocompletamento per i componenti definiti dall'utente, occorre uno schema. Seam non fornisce ancora un meccanismo per generare automaticamente uno schema per un set di componenti, così è necessario generarne uno manualmente. Come guida d'esempio si possono usare le definizioni di schema dei pacchetti standard di Seam.

Seam utilizza i seguenti namespace:

Complementing the contextual component model, there are two further basic concepts that facilitate the extreme loose-coupling that is the distinctive feature of Seam applications. The first is a strong event model where events may be mapped to event listeners via JSF-like method binding expressions. The second is the pervasive use of annotations and interceptors to apply cross-cutting concerns to components which implement business logic.

A Seam page action is an event that occurs just before we render a page. We declare page actions in WEB-INF/pages.xml. We can define a page action for either a particular JSF view id:


<pages>
    <page view-id="/hello.jsp" action="#{helloWorld.sayHello}"/>
</pages
>

Or we can use a * wildcard as a suffix to the view-id to specify an action that applies to all view ids that match the pattern:


<pages>
    <page view-id="/hello/*" action="#{helloWorld.sayHello}"/>
</pages
>

Keep in mind that if the <page> element is defined in a fine-grained page descriptor, the view-id attribute can be left off since it is implied.

If multiple wildcarded page actions match the current view-id, Seam will call all the actions, in order of least-specific to most-specific.

The page action method can return a JSF outcome. If the outcome is non-null, Seam will use the defined navigation rules to navigate to a view.

Furthermore, the view id mentioned in the <page> element need not correspond to a real JSP or Facelets page! So, we can reproduce the functionality of a traditional action-oriented framework like Struts or WebWork using page actions. This is quite useful if you want to do complex things in response to non-faces requests (for example, HTTP GET requests).

Multiple or conditional page actions my be specified using the <action> tag:


<pages>
    <page view-id="/hello.jsp">
        <action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
        <action execute="#{hitCount.increment}"/>
    </page>
</pages
>

Page actions are executed on both an initial (non-faces) request and a postback (faces) request. If you are using the page action to load data, this operation may conflict with the standard JSF action(s) being executed on a postback. One way to disable the page action is to setup a condition that resolves to true only on an initial request.


<pages>
    <page view-id="/dashboard.xhtml">
        <action execute="#{dashboard.loadData}"
            if="#{not facesContext.renderKit.responseStateManager.isPostback(facesContext)}"/>
    </page>
</pages
>

This condition consults the ResponseStateManager#isPostback(FacesContext) to determine if the request is a postback. The ResponseStateManager is accessed using FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager().

To save you from the verbosity of JSF's API, Seam offers a built-in condition that allows you to accomplish the same result with a heck of a lot less typing. You can disable a page action on postback by simply setting the on-postback to false:


<pages>
    <page view-id="/dashboard.xhtml">
        <action execute="#{dashboard.loadData}" on-postback="false"/>
    </page>
</pages
>

For backwards compatibility reasons, the default value of the on-postback attribute is true, though likely you will end up using the opposite setting more often.

A JSF faces request (a form submission) encapsulates both an "action" (a method binding) and "parameters" (input value bindings). A page action might also needs parameters!

Since GET requests are bookmarkable, page parameters are passed as human-readable request parameters. (Unlike JSF form inputs, which are anything but!)

You can use page parameters with or without an action method.

Seam lets us provide a value binding that maps a named request parameter to an attribute of a model object.


<pages>
      <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
          <param name="firstName" value="#{person.firstName}"/>
          <param name="lastName" value="#{person.lastName}"/>
      </page>
  </pages
>

The <param> declaration is bidirectional, just like a value binding for a JSF input:

The essential idea behind all this is that however we get from any other page to /hello.jsp (or from /hello.jsp back to /hello.jsp), the value of the model attribute referred to in the value binding is "remembered", without the need for a conversation (or other server-side state).

If just the name attribute is specified then the request parameter is propagated using the PAGE context (it isn't mapped to model property).


<pages>
      <page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
          <param name="firstName" />
          <param name="lastName" />
      </page>
  </pages
>

Propagation of page parameters is especially useful if you want to build multi-layer master-detail CRUD pages. You can use it to "remember" which view you were previously on (e.g. when pressing the Save button), and which entity you were editing.

This all sounds pretty complex, and you're probably wondering if such an exotic construct is really worth the effort. Actually, the idea is very natural once you "get it". It is definitely worth taking the time to understand this stuff. Page parameters are the most elegant way to propagate state across a non-faces request. They are especially cool for problems like search screens with bookmarkable results pages, where we would like to be able to write our application code to handle both POST and GET requests with the same code. Page parameters eliminate repetitive listing of request parameters in the view definition and make redirects much easier to code.

Rewriting occurs based on rewrite patterns found for views in pages.xml. Seam URL rewriting does both incoming and outgoing URL rewriting based on the same pattern. Here's a simple pattern:



<page view-id="/home.xhtml">
    <rewrite pattern="/home" />
</page>

In this case, any incoming request for /home will be sent to /home.xhtml. More interestingly, any link generated that would normally point to /home.seam will instead be rewritten as /home. Rewrite patterns only match the portion of the URL before the query parameters. So, /home.seam?conversationId=13 and /home.seam?color=red will both be matched by this rewrite rule.

Rewrite rules can take these query paramters into consideration, as shown with the following rules.



<page view-id="/home.xhtml">
    <rewrite pattern="/home/{color}" />
    <rewrite pattern="/home" />
</page>

In this case, an incoming request for /home/red will be served as if it were a request for /home.seam?color=red. Similarly, if color is a page parameter an outgoing URL that would normally show as /home.seam?color=blue would instead be output as /home/blue. Rules are processed in order, so it is important to list more specific rules before more general rules.

Default Seam query parameters can also be mapped using URL rewriting, allowing for another option for hiding Seam's fingerprints. In the following example, /search.seam?conversationId=13 would be written as /search-13.



<page view-id="/search.xhtml">
    <rewrite pattern="/search-{conversationId}" />
    <rewrite pattern="/search" />
</page>

Seam URL rewriting provides simple, bidirectional rewriting on a per-view basis. For more complex rewriting rules that cover non-seam components, Seam applications can continue to use the org.tuckey URLRewriteFilter or apply rewriting rules at the web server.

URL rewriting requires the Seam rewrite filter to be enable. Rewrite filter configuration is discussed in Sezione 30.1.4.3, «Riscrittura dell'URL».

Si può specificare un convertitore JSF per proprietà di modelli complessi:


<pages>
   <page view-id="/calculator.jsp" action="#{calculator.calculate}">
      <param name="x" value="#{calculator.lhs}"/>
      <param name="y" value="#{calculator.rhs}"/>
      <param name="op" converterId="com.my.calculator.OperatorConverter" value="#{calculator.op}"/>
   </page>
</pages
>

In alternativa:


<pages>
   <page view-id="/calculator.jsp" action="#{calculator.calculate}">
      <param name="x" value="#{calculator.lhs}"/>
      <param name="y" value="#{calculator.rhs}"/>
      <param name="op" converter="#{operatorConverter}" value="#{calculator.op}"/>
   </page>
</pages
>

possono essere usati anche i validatori JSF e required="true":


<pages>
    <page view-id="/blog.xhtml">
        <param name="date" 
               value="#{blog.date}" 
               validatorId="com.my.blog.PastDate" 
               required="true"/>
    </page>
</pages
>

In alternativa:


<pages>
    <page view-id="/blog.xhtml">
        <param name="date" 
               value="#{blog.date}" 
               validator="#{pastDateValidator}" 
               required="true"/>
    </page>
</pages
>

Even better, model-based Hibernate validator annotations are automatically recognized and validated. Seam also provides a default date converter to convert a string parameter value to a date and back.

When type conversion or validation fails, a global FacesMessage is added to the FacesContext.

You can use standard JSF navigation rules defined in faces-config.xml in a Seam application. However, JSF navigation rules have a number of annoying limitations:

A further problem is that "orchestration" logic gets scattered between pages.xml and faces-config.xml. It's better to unify this logic into pages.xml.

Questa regola di navigazione JSF:


<navigation-rule>
   <from-view-id
>/editDocument.xhtml</from-view-id>
    
   <navigation-case>
      <from-action
>#{documentEditor.update}</from-action>
      <from-outcome
>success</from-outcome>
      <to-view-id
>/viewDocument.xhtml</to-view-id>
      <redirect/>
   </navigation-case>
    
</navigation-rule
>

Può essere riscritto come segue:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule if-outcome="success">
            <redirect view-id="/viewDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

But it would be even nicer if we didn't have to pollute our DocumentEditor component with string-valued return values (the JSF outcomes). So Seam lets us write:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}" 
                   evaluate="#{documentEditor.errors.size}">
        <rule if-outcome="0">
            <redirect view-id="/viewDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

Od anche:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule if="#{documentEditor.errors.empty}">
            <redirect view-id="/viewDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

The first form evaluates a value binding to determine the outcome value to be used by the subsequent rules. The second approach ignores the outcome and evaluates a value binding for each possible rule.

Of course, when an update succeeds, we probably want to end the current conversation. We can do that like this:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule if="#{documentEditor.errors.empty}">
            <end-conversation/>
            <redirect view-id="/viewDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

As we've ended conversation any subsequent requests won't know which document we are interested in. We can pass the document id as a request parameter which also makes the view bookmarkable:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule if="#{documentEditor.errors.empty}">
            <end-conversation/>
            <redirect view-id="/viewDocument.xhtml">
                <param name="documentId" value="#{documentEditor.documentId}"/>
            </redirect>
        </rule>
    </navigation>
    
</page
>

Null outcomes are a special case in JSF. The null outcome is interpreted to mean "redisplay the page". The following navigation rule matches any non-null outcome, but not the null outcome:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule>
            <render view-id="/viewDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

If you want to perform navigation when a null outcome occurs, use the following form instead:


<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <render view-id="/viewDocument.xhtml"/>
    </navigation>
    
</page
>

The view-id may be given as a JSF EL expression:


<page view-id="/editDocument.xhtml">

    <navigation>
        <rule if-outcome="success">
            <redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
        </rule>
    </navigation>
    
</page
>

Seam components can interact by simply calling each others methods. Stateful components may even implement the observer/observable pattern. But to enable components to interact in a more loosely-coupled fashion than is possible when the components call each others methods directly, Seam provides component-driven events.

Si specificano gli event listener (observer) in components.xml.


<components>
    <event type="hello">
        <action execute="#{helloListener.sayHelloBack}"/>
        <action execute="#{logger.logHello}"/>
    </event>
</components
>

Dove il tipo di evento è solo una stringa arbitraria.

When an event occurs, the actions registered for that event will be called in the order they appear in components.xml. How does a component raise an event? Seam provides a built-in component for this.

@Name("helloWorld")

public class HelloWorld {
    public void sayHello() {
        FacesMessages.instance().add("Hello World!");
        Events.instance().raiseEvent("hello");
    }
}

Oppure si può usare un'annotazione.

@Name("helloWorld")

public class HelloWorld {
    @RaiseEvent("hello")
    public void sayHello() {
        FacesMessages.instance().add("Hello World!");
    }
}

Notice that this event producer has no dependency upon event consumers. The event listener may now be implemented with absolutely no dependency upon the producer:

@Name("helloListener")

public class HelloListener {
    public void sayHelloBack() {
        FacesMessages.instance().add("Hello to you too!");
    }
}

The method binding defined in components.xml above takes care of mapping the event to the consumer. If you don't like futzing about in the components.xml file, you can use an annotation instead:

@Name("helloListener")

public class HelloListener {
    @Observer("hello")
    public void sayHelloBack() {
        FacesMessages.instance().add("Hello to you too!");
    }
}

You might wonder why I've not mentioned anything about event objects in this discussion. In Seam, there is no need for an event object to propagate state between event producer and listener. State is held in the Seam contexts, and is shared between components. However, if you really want to pass an event object, you can:

@Name("helloWorld")

public class HelloWorld {
    private String name;
    public void sayHello() {
        FacesMessages.instance().add("Hello World, my name is #0.", name);
        Events.instance().raiseEvent("hello", name);
    }
}
@Name("helloListener")

public class HelloListener {
    @Observer("hello")
    public void sayHelloBack(String name) {
        FacesMessages.instance().add("Hello #0!", name);
    }
}

Seam defines a number of built-in events that the application can use to perform special kinds of framework integration. The events are:

I componenti Seam possono osservare uno di questi eventi così come osservano qualsiasi altro evento guidato da componente.

EJB 3.0 introduced a standard interceptor model for session bean components. To add an interceptor to a bean, you need to write a class with a method annotated @AroundInvoke and annotate the bean with an @Interceptors annotation that specifies the name of the interceptor class. For example, the following interceptor checks that the user is logged in before allowing invoking an action listener method:

public class LoggedInInterceptor {


   @AroundInvoke
   public Object checkLoggedIn(InvocationContext invocation) throws Exception {
   
      boolean isLoggedIn = Contexts.getSessionContext().get("loggedIn")!=null;
      if (isLoggedIn) {
         //the user is already logged in
         return invocation.proceed();
      }
      else {
         //the user is not logged in, fwd to login page
         return "login";
      }
   }
}

To apply this interceptor to a session bean which acts as an action listener, we must annotate the session bean @Interceptors(LoggedInInterceptor.class). This is a somewhat ugly annotation. Seam builds upon the interceptor framework in EJB3 by allowing you to use @Interceptors as a meta-annotation for class level interceptors (those annotated @Target(TYPE)). In our example, we would create an @LoggedIn annotation, as follows:

@Target(TYPE)

@Retention(RUNTIME)
@Interceptors(LoggedInInterceptor.class)
public @interface LoggedIn {}

We can now simply annotate our action listener bean with @LoggedIn to apply the interceptor.

@Stateless

@Name("changePasswordAction")
@LoggedIn
@Interceptors(SeamInterceptor.class)
public class ChangePasswordAction implements ChangePassword { 
    
    ...
    
    public String changePassword() { ... }
    
}

If interceptor ordering is important (it usually is), you can add @Interceptor annotations to your interceptor classes to specify a partial order of interceptors.

@Interceptor(around={BijectionInterceptor.class,

                     ValidationInterceptor.class,
                     ConversationInterceptor.class},
             within=RemoveInterceptor.class)
public class LoggedInInterceptor
{
    ...
}

You can even have a "client-side" interceptor, that runs around any of the built-in functionality of EJB3:

@Interceptor(type=CLIENT)

public class LoggedInInterceptor
{
    ...
}

EJB interceptors are stateful, with a lifecycle that is the same as the component they intercept. For interceptors which do not need to maintain state, Seam lets you get a performance optimization by specifying @Interceptor(stateless=true).

Much of the functionality of Seam is implemented as a set of built-in Seam interceptors, including the interceptors named in the previous example. You don't have to explicitly specify these interceptors by annotating your components; they exist for all interceptable Seam components.

You can even use Seam interceptors with JavaBean components, not just EJB3 beans!

EJB defines interception not only for business methods (using @AroundInvoke), but also for the lifecycle methods @PostConstruct, @PreDestroy, @PrePassivate and @PostActive. Seam supports all these lifecycle methods on both component and interceptor not only for EJB3 beans, but also for JavaBean components (except @PreDestroy which is not meaningful for JavaBean components).

JSF is surprisingly limited when it comes to exception handling. As a partial workaround for this problem, Seam lets you define how a particular class of exception is to be treated by annotating the exception class, or declaring the exception class in an XML file. This facility is meant to be combined with the EJB 3.0-standard @ApplicationException annotation which specifies whether the exception should cause a transaction rollback.

Since we can't add annotations to all the exception classes we are interested in, Seam also lets us specify this functionality in pages.xml.


<pages>
   
   <exception class="javax.persistence.EntityNotFoundException">
      <http-error error-code="404"/>
   </exception>
   
   <exception class="javax.persistence.PersistenceException">
      <end-conversation/>
      <redirect view-id="/error.xhtml">
          <message
>Database access failed</message>
      </redirect>
   </exception>
   
   <exception>
      <end-conversation/>
      <redirect view-id="/error.xhtml">
          <message
>Unexpected failure</message>
      </redirect>
   </exception>
   
</pages
>

The last <exception> declaration does not specify a class, and is a catch-all for any exception for which handling is not otherwise specified via annotations or in pages.xml.

You can also use EL to specify the view-id to redirect to.

You can also access the handled exception instance through EL, Seam places it in the conversation context, e.g. to access the message of the exception:


...
throw new AuthorizationException("You are not allowed to do this!");

<pages>

    <exception class="org.jboss.seam.security.AuthorizationException">
        <end-conversation/>
        <redirect view-id="/error.xhtml">
            <message severity="WARN"
>#{org.jboss.seam.handledException.message}</message>
        </redirect>
    </exception>

</pages
>

org.jboss.seam.handledException holds the nested exception that was actually handled by an exception handler. The outermost (wrapper) exception is also available, as org.jboss.seam.caughtException.

Se si usa JPA:


<exception class="javax.persistence.EntityNotFoundException">
   <redirect view-id="/error.xhtml">
      <message
>Not found</message>
   </redirect>
</exception>

<exception class="javax.persistence.OptimisticLockException">
   <end-conversation/>
   <redirect view-id="/error.xhtml">
      <message
>Another user changed the same data, please try again</message>
   </redirect>
</exception
>

Se si usa il Seam Application Framework:


<exception class="org.jboss.seam.framework.EntityNotFoundException">
   <redirect view-id="/error.xhtml">
      <message
>Not found</message>
   </redirect>
</exception
>

Se si usa Seam Security:


<exception class="org.jboss.seam.security.AuthorizationException">
   <redirect>
      <message
>You don't have permission to do this</message>
   </redirect>
</exception>
    
<exception class="org.jboss.seam.security.NotLoggedInException">
   <redirect view-id="/login.xhtml">
      <message
>Please log in first</message>
   </redirect>
</exception
>

E per JSF:


<exception class="javax.faces.application.ViewExpiredException">
   <redirect view-id="/error.xhtml">
      <message
>Your session has timed out, please try again</message>
   </redirect>
</exception
>

A ViewExpiredException occurs if the user posts back to a page once their session has expired. no-conversation-view-id and conversation-required give you finer grained control over session expiration if you are inside a conversation.

E' ora di capire il modello di conversazione di Seam con maggior dettaglio.

Storicamente la nozione di "conversazione" Seam si presenta come unificatrice di tre differenti idee:

  • L'idea di uno spazio di lavoro (workspace), che ho incontrato in un progetto per il governo Vittoriano nel 2002. In questo progetto fui obbligato ad implementare la gestione del workspace sopra Struts, un'esperienza che prego di non ripetere mai più.

  • L'idea di una transazione per l'applicazione con semantica ottimista, e il convincimento che i framework esistenti basati su un'architettura stateless non potessero fornire una gestione efficiente dei contesti di persistenza estesa. (La squadra di Hibernate è veramente stanca di sentire lamentele per le LazyInitializationException, che non è in verità colpa di Hibernate, ma piuttosto colpa di un modello di contesto di persistenza estremamente limitato delle architetture stateless come il framework Spring o il tradizionale (anti)pattern stateless session facade in J2EE.)

  • L'idea di un task a workflow.

Unificando queste idee e fornendo un supporto profondo nel framework, si ha un costrutto potente che consente di creare applicazioni più ricche e più efficienti con meno codice di prima.

Gli esempi visti finora usano un modello di conversazione molto semplice che segue queste regole:

Seam propaga in modo trasparente il contesto della conversazione (includendo il contesto della conversazione temporanea) lungo i postback JSF e i redirect. Se non si fa niente di speciale, una richiesta non-faces (per esempio una richiesta GET) non verrà propagata nel contesto di conversazione e non verrà processata in una conversazione temporanea. Questo è solitamente - ma non sempre - il comportamento desiderato.

Se si vuole propagare una conversazione Seam lungo una richiesta non-faces, non occorre esplicitamente codificare l'id della conversazione come parametro di richiesta:


<a href="main.jsf?#{manager.conversationIdParameter}=#{conversation.id}"
>Continue</a
>

O in stile più JSF:


<h:outputLink value="main.jsf">
    <f:param name="#{manager.conversationIdParameter}" value="#{conversation.id}"/>
    <h:outputText value="Continue"/>
</h:outputLink
>

Se si utilizza la libreria di tag Seam, questo è l'equivalente:


<h:outputLink value="main.jsf">
    <s:conversationId/>
    <h:outputText value="Continue"/>
</h:outputLink
>

Se si desidera disabilitare la propagazione del contesto di conversazione per il postback, viene usato un simile trucchetto:


<h:commandLink action="main" value="Exit">
    <f:param name="conversationPropagation" value="none"/>
</h:commandLink
>

Se si utilizza la libreria di tag Seam, questo è l'equivalente:


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="none"/>
</h:commandLink
>

Si noti che disabilitando la propagazione del contesto della conversazione non è assolutamente la stessa cosa che terminare la conversazione:

Il parametro di richiesta conversationPropagation, o il tag <s:conversationPropagation> possono anche essere usati per iniziare e terminare la conversazione, o iniziare una conversazione innestata.


<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="end"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Child">
    <s:conversationPropagation type="nested"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="begin"/>
</h:commandLink
>

<h:commandLink action="main" value="Select Hotel">
    <s:conversationPropagation type="join"/>
</h:commandLink
>

Il modello di conversazione rende semplice costruire applicazioni che si comportano in modo corretto in presenza di operazioni con finestre multiple. Per molte applicazioni, questo è quello che serve. Alcune applicazioni complesse hanno uno ed entrambi dei seguenti requisiti aggiuntivi:

Una conversazione innestata viene creata invocando un metodo marcato con @Begin(nested=true) dentro lo scope di una conversazione esitente. Una conversazione innestata ha un proprio contesto di conversazione, ma può leggere i valori dal contesto della conversazione più esterna. Il contesto della conversazione più esterna è in sola lettura in una conversazione innestata, ma poiché gli oggetti sono ottenuti per riferimento, i cambiamenti agli oggetti stessi si rifletteranno nel contesto più esterno.

Quando si incontra una @End, la conversazione innestata verrà distrutta, togliendola dallo stack (pop), e la conversazione più esterna verrà ripristinata. Le conversazioni possono essere annidate con gradi di profondità arbitrari.

Certe attività utente (gestione del workspace, o il pulsante indietro) possono causare il ripristino della conversazione più esterna prima che venga terminata la conversazione innestata. In questo caso è possibile avere più conversazioni innestate concorrenti che appartengono alla stessa conversazione più esterna. Se la conversazione più esterna finisce prima che termini la conversazione innestata, Seam distrugge tutti i contesti delle conversazioni innestate assieme a quella più esterna.

La conversazione alla fine dello stack delle conversazioni è la conversazione radice. Distruggendo questa conversazione si distruggono sempre tutti i suoi discendenti. Si può ottenere questo in modo dichiarativo specificando @End(root=true).

Una conversazione può essere pensata come uno stato continuo. Le conversazioni innestate consentono all'applicazione di catturare lo stato continuo consistente in vari punti durante l'interazione utente, quindi assicurando un comportamento corretto rispetto al pulsante indietro ed alla gestione del workspace.

Come menzionato in precedenza, se un componente si trova in una conversazione padre dell'attuale conversazione innestata, la conversazione innestata userà la stessa istanza. Occasionalmente, è utile avere diverse istanze in ciascuna conversazione innestata, cosicché l'istanza del componente che si trova nella conversazione padre sia invisibile alle sue conversazioni figlie. Si può ottenere questo comportamento annotando il componente @PerNestedConversation.

JSF non definisce alcun tipo di action listener da lanciare quando una pagina viene acceduta tramite una richiesta non-faces (per esempio, una richiesta HTTP GET). Questo può succedere se l'utente memorizza la pagina come segnalibro, o se si naviga nella pagina tramite un <h:outputLink>.

A volte si vuole immediatamente iniziare una conversazione all'accesso della pagina. Poiché non c'è alcun metodo d'azione JSF, non si può risolvere il problema nel consueto modo, annotando l'azione con @Begin.

Sorge un altro problema se la pagina ha bisogno di recuperare uno stato da una variabile di contesto. Si sono già visti due modi per risolvere questo problema. Se lo stato è mantenuto in un componente Seam, si può recuperare lo stato in un metodo @Create. Se non lo è, si può definire un metodo @Factory per la variabile di contesto.

Se nessuna di queste opzioni funziona, Seam permette di definire una pagina d'azione nel file pages.xml.


<pages>
    <page view-id="/messageList.jsp" action="#{messageManager.list}"/>
    ...
</pages
>

Il metodo d'azione viene chiamato all'inizio della fase di render response, ogni volta che la pagina sta per essere generata. Se l'azione della pagina ritorna un esito non-null, Seam processerà ogni opportuna regola di JSF e Seam, e genererà un'altra pagina.

Se tutto ciò che si vuole fare prima di generare una pagina è iniziare una conversazione, si può utilizzare un metodo d'azione predefinito che fa questo:


<pages>
    <page view-id="/messageList.jsp" action="#{conversation.begin}"/>
    ...
</pages
>

Si noti che si può chiamare quest'azione ridefinita da un controllo JSF, ed in modo simile si può usare #{conversation.end} per terminare le conversazioni.

Se si vuole più controllo, per unirsi a conversazioni esistenti od iniziare una conversazione innestata, per iniziare un pageflow od una conversazione atomica, occorre usare l'elemento <begin-conversation>.


<pages>
    <page view-id="/messageList.jsp">
       <begin-conversation nested="true" pageflow="AddItem"/>
    <page>
    ...
</pages
>

C'è anche un elemento <end-conversation>.


<pages>
    <page view-id="/home.jsp">
       <end-conversation/>
    <page>
    ...
</pages
>

Per risolvere il primo problema, si hanno cinque opzioni:

I comandi link JSF eseguono sempre un invio di form tramite JavaScript, che rompe le caratteristiche dei browser "Apri in nuova finestra" o "Apri in nuova scheda". Nel semplice JSF occorre usare un <h:outputLink> se si vuole questa finzionalità. Ma ci sono due grandi limitazioni in <h:outputLink>

Seam fornisce la nozione di pagina d'azione per aiutare a risolvere il primo problema, ma questo non aiuta per niente il secondo problema. Si può aggirare questo usando l'approccio RESTful di passare un parametro di richiesta e di riottenere l'oggetto selezionato lato server. In alcuni casi — come nell'esempio Seam del Blog — questo è il migliore approccio. Lo stile RESTful supporta i segnalibri, poiché non richiede uno stato lato server. In altri casi, dove non interessanno i segnalibri, l'uso di un @DataModel e di @DataModelSelection è conveniente e trasparente!

Per riempiere questa mancanza di funzionalità e rendere semplicela propagazione delle conversazioni da gestire, Seam fornisce il tag JSF <s:link>.

Il link può specificare solo l'id della vista JSF:


<s:link view="/login.xhtml" value="Login"/>

Oppure può spcificareil metodo d'azione (nel qual caso l'esito dell'azione determina la pagina di destinazione):


<s:link action="#{login.logout}" value="Logout"/>

Se si specificano entrambi l'id della vista JSF ed il metodo d'azione, verrà usata la 'vista' amenoché il metodo d'azione ritorni un esito non-null:


<s:link view="/loggedOut.xhtml"  action="#{login.logout}" value="Logout"/>

Il link propaga automaticamente la riga selezionata del DataModel usando all'interno <h:dataTable>:


<s:link view="/hotel.xhtml" action="#{hotelSearch.selectHotel}" value="#{hotel.name}"/>

Si può lasciare lo scope di una conversazione esistente:


<s:link view="/main.xhtml" propagation="none"/>

Si può iniziare, terminare, o innestare le conversazioni:


<s:link action="#{issueEditor.viewComment}" propagation="nest"/>

Se il link inizia una conversazione, si può anche specificare il pageflow da usare:


<s:link action="#{documentEditor.getDocument}" propagation="begin"
        pageflow="EditDocument"/>

L'attributo taskInstance è per l'uso nelle liste di task jBPM:


<s:link action="#{documentApproval.approveOrReject}" taskInstance="#{task}"/>

(Si veda l'applicazione demo di Negozio DVD come esempio.)

Infine se il "link" deve essere visualizzato come pulsante, si usi <s:button>:


<s:button action="#{login.logout}" value="Logout"/>

Lavorando con le conversazioni che trattano oggetti persistenti, può essere desiderabile utilizzare la chiave naturale di business dell'oggetto invece dello standard, id di conversazione "surrogato":

Redirect facile verso conversazioni esistenti

Può essere utile redirigersi verso una conversazione esistente se l'utente richiede la stessa operazione due volte. Si prenda quest'esempio: «Sei su eBay, stai per pagare un oggetto che hai scelto come regalo per i tuoi genitori. Diciamo che lo stai per inviare a loro - stai per inserire i dettagli di pagamento ma non ti ricordi il loro indirizzo. Accidentalmente riutilizzi la stessa finestra del browser per cercare il loro indirizzo. Ora devi ritornare al pagamento di quell'oggetto.»

Con una conversazione naturale è molto facile riunire l'utente alla conversazione esistente, e riprendere dove aveva lasciato - riunirsi alla conversazione pagaOggetto con l'idOggetto come id di conversazione.

URL user friendly

Questo si concretizza in una gerarchia navigabile (si può navigare editando l'url) e in URL significativi (come mostrato in Wiki - quindi non si identifichino gli oggetti con id casuali). Per alcune applicazioni gli URL user friendly sono sicuramente meno importanti.

Con una conversazione naturale, quando si costruisce un sistema di prenotazione hotel (od una qualsiasi applicazione) si può generare un URL del tipo http://seam-hotels/book.seam?hotel=BestWesternAntwerpen (sicuramente un qualsiasi parametro hotel, che mappi sul modello di dominio, deve essere univoco) e con URLRewrite si può facilmente trasformare questo in http://seam-hotels/book/BestWesternAntwerpen.

Molto meglio!

Le conversazioni naturali sono definite in pages.xml:


  <conversation name="PlaceBid"
                  parameter-name="auctionId"
                  parameter-value="#{auction.auctionId}"/>

La prima cosa da notare dalla definizione di cui sopra è che la conversazione ha un nome, in questo caso PlaceBid. Questo nome identifica univocamente questa particolare conversazione e viene usato dalla definizione di pagina per identificare una conversazione con nome in cui partecipare.

Il prossimo attributo parameter-name definisce il parametro di richiesta che conterrà l'id della conversazione naturale, al posto del parametro dell'id della conversazione di default. In quest'esempio, il parameter-name è auctionId. Questo significa che invece di un parametro di conversazione come cid=123 che appare nell'URL della pagina, conterrà invece auctionId=765432.

L'ultimo attributo della configurazione di cui sopra, parameter-value, definisce un'espressione EL usata per valutare il valore della chiave naturale di business da usare come id di conversazione. In quest'esempio, l'id della conversazione sarà il valore della chiave primaria dell'istanza auction attualmente nello scope.

Poi si definirà quali pagine parteciperanno nella conversazione con nome. Questo è fatto specificando l'attributo conversation per una definizione di pagina:


  <page view-id="/bid.xhtml" conversation="PlaceBid" login-required="true">
      <navigation from-action="#{bidAction.confirmBid}"
>        
          <rule if-outcome="success">
              <redirect view-id="/auction.xhtml">
                  <param name="id" value="#{bidAction.bid.auction.auctionId}"/>
              </redirect>
          </rule
>        
      </navigation>
  </page
>

Avviando o facendo redirect verso una conversazione naturale ci sono un numero di opzioni possibili per specificare il nome della conversazione naturale. Si guardi alla seguente definizione di pagina:


  <page view-id="/auction.xhtml">
    <param name="id" value="#{auctionDetail.selectedAuctionId}"/>
       
    <navigation from-action="#{bidAction.placeBid}">
      <redirect view-id="/bid.xhtml"/>
    </navigation>
  </page
>

Da qua si può vedere che invocando l'azione #{bidAction.placeBid} dalla vista auction (comunque, tutti questi esempio sono presi dall'esempio seamBay), che verrà rediretta a /bid.xhtml, che come si è visto, è configurata con la conversazione naturale PlaceBid. La dichiarazione del metodo d'azione appare come:

   @Begin(join = true)

   public void placeBid()

Quando le conversazioni con nome vengono specificate nell'elemento <page/>, la redirezione alla conversazione con nome avviene come parte delle regole, dopo che il metodo d'azione è già stato invocato. Questo è un problema quando ci si redirige ad una conversazione esistente, poiché la redirezione deve avvenire prima che sia invocato il metodo d'azione. Quindi è necessario specificare il nome della conversazione quando si invoca l'azione. Un metodo per fare questo è usare il tag s:conversationName:


  <h:commandButton id="placeBidWithAmount" styleClass="placeBid" action="#{bidAction.placeBid}">
    <s:conversationName value="PlaceBid"/>
  </h:commandButton
>

Un'altra alternativa è specificare l'attributo conversationName quando si usano o s:link o s:button:


  <s:link value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/>

La gestione del workspace è la capacità di "cambiare" le conversazioni all'interno di una singola finestra. Seam rende la gestione del workspace completamente trasparente a livello di codice Java. Per abilitare la gestione del workspace, occorre fare questo:

La lista delle conversazioni è molto simile allo switcher delle conversazioni, tranne che viene mostrata come tabella:


<h:dataTable value="#{conversationList}" var="entry"
        rendered="#{not empty conversationList}">
    <h:column>
        <f:facet name="header"
>Workspace</f:facet>
        <h:commandLink action="#{entry.select}" value="#{entry.description}"/>
        <h:outputText value="[current]" rendered="#{entry.current}"/>
    </h:column>
    <h:column>
        <f:facet name="header"
>Activity</f:facet>
        <h:outputText value="#{entry.startDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
        <h:outputText value=" - "/>
        <h:outputText value="#{entry.lastDatetime}">
            <f:convertDateTime type="time" pattern="hh:mm a"/>
        </h:outputText>
    </h:column>
    <h:column>
        <f:facet name="header"
>Action</f:facet>
        <h:commandButton action="#{entry.select}" value="#{msg.Switch}"/>
        <h:commandButton action="#{entry.destroy}" value="#{msg.Destroy}"/>
    </h:column>
</h:dataTable
>

Si immagini di voler personalizzare questo per la propria applicazione.

Solo le conversazioni con una descrizione verranno incluse nella lista.

Si noti che la lista delle conversazioni consente all'utente di distruggere i workspace.

I componenti conversazionali hanno una piccola limitazione: non possono essere usati per mantenere un riferimento ai componenti JSF. (In generali si preferisce non usare questa funzionalità di JSF amenoché sia assolutamente necessario, poiché questo crea una dipendenza stretta della logica dell'applicazione con la vista.) Sulle richieste postback, i binding dei componenti vengono aggiornati durante la fase restore view, prima che il contesto della conversazione di Seam venga ripristinato.

Per aggirare questo si usa un componente con scope evento per memorizzare i binding dei componenti ed per iniettarlo nel componente a scope conversazione che lo richiede.

@Name("grid")

@Scope(ScopeType.EVENT)
public class Grid
{
    private HtmlPanelGrid htmlPanelGrid;
    // getters and setters
    ...
}
@Name("gridEditor")

@Scope(ScopeType.CONVERSATION)
public class GridEditor
{
    @In(required=false)
    private Grid grid;
    
    ...
}

Inoltre non si può iniettare un componente con scope conversazione in un componente con scope evento a cui associare il controllo JSF. Questo include i componenti predefiniti Seam come facesMessages.

In alternativa si può accedere all'albero del componente JSF attraverso l'handle implicito uiComponent. Il seguente esempio accede a getRowIndex() del componente UIData che è retrostante alla tabella dei dati durante l'interazione, e stampa il numero della riga corrente:



<h:dataTable id="lineItemTable" var="lineItem" value="#{orderHome.lineItems}">
   <h:column>
      Row: #{uiComponent['lineItemTable'].rowIndex}
   </h:column>
   ...
</h:dataTable
>

I componenti JSF UI sono disponibili in questa mappa con il loro identificatore di client.

Una discussione generale sulle chiamate concorrenti ai componenti Seam può essere trovata in Sezione 4.1.10, «Modello di concorrenza». Qua si discuterà la situazione più comune in cui si troverà la concorrenza — accedendo a componenti conversazionali da richieste AJAX. Si dicuteranno le opzioni che la libreria client Ajax dovrebbe fornire per controllare gli eventi originati nel client — e si vedranno le opzioni che fornisce RichFaces.

I componenti conversazionali non consentono una vero accesso concorrente, e quindi Seam accoda ogna richiesta per poi processarla in modo seriale. Questo consente ad ogni richiesta di venir eseguita in un modo deterministico. Comunque, una semplice cosa non è cosa ottima — in primo luogo, se un metodo per qualche ragione impiega molto tempo a terminare, eseguirlo più volte quando il client genera una richiesta, è una cattiva idea (potenziale per attacchi di tipo Denial of Service), ed in secondo luogo, AJAX spesso viene usato per fornire un veloce aggiornamento dello stato all'utente, e così continuare ad eseguire l'azione per lungo tempo non è utile.

Quindi quando si lavora all'interno di una conversazione long-running, Seam accoda l'evento azione per un periodo di tempo (il timeout della richiesta concorrente); se non si può processare l'evento in tempo, si crea una conversazione temporanea e si stampa un messaggio all'utente per rendergli noto cosa sta succedendo. E' quindi molto importante non inondare il server con eventi AJAX!

Si può impostare un valore di default sensibile per il timeout delle richieste concorrenti (in ms) dentro il file components.xml:


<core:manager concurrent-request-timeout="500" />

Si può anche perfezionare questo timeout a livello di ogni singola pagina:


<page view-id="/book.xhtml" 
         conversation-required="true" 
         login-required="true"
         concurrent-request-timeout="2000" />

Finora si è discusso delle richieste AJAX che appaiono in serie all'utente - il client dice al server quale evento avviene, e quindi rigenera parte della pagina a seconda del risultato. Questo approccio è ottimo quando la richiesta AJAX è leggera (i metodi chiamati sono semplici, per esempio il calcolo della somma di una colonna di numeri): Ma cosa succede se occorre un calcolo più complesso che dura diversi minuti?

Per una computazione pesante occorre usare un approccio basato sull'interrogazione — il client spedisce una richiesta AJAX al server, che causa un'azione asincrona sul server (la risposta al client è immediata) ed il client quindi interroga il server per gli aggiornamenti. Questo è un buon approccio quando si ha un'azione long-running per la quale è importante che ciascuna azione venga eseguita (non si vuole che qualcuna vada in timeout).

In primo luogo occorre decidere se si vuole usare una richiesta semplice "seriale" e se si vuole l'approccio con interrogazione.

Nel caso di richiesta "seriale" occorre stimare quando tempo occorrerà alle richieste per completarsi - è più breve del timeout della richiesta concorrente? Altrimenti si potrebbe volere probabilmente modificare il timeout per questa pagina (come discusso sopra). Si vuole che la coda lato client prevenga l'inondazione di richiesta al server. Se l'evento si verifica spesso (es. keypress, onblur di un campo d'input) e l'aggiornamento immediato del client non è una priorità si deve ritardare la richiesta lato client. Quando si lavora sul ritardo delle richieste, si tenga presente che l'evento può venire accodato anche lato server.

Infine la libreria del client può fornire un'opzione su come abbandonare le richieste duplicate non terminate a favore di quelle più recenti.

Usando un design di tipo interrogazione richiede un minor fine-tuning. Basta marcare il metodo d'azione con @Asynchronous e decidere l'intervallo di interrogazione:

int total;


// This method is called when an event occurs on the client
// It takes a really long time to execute
@Asynchronous      
public void calculateTotal() {
   total = someReallyComplicatedCalculation();
}
// This method is called as the result of the poll
// It's very quick to execute
public int getTotal() {
   return total;
}

Comunque per quanto in modo attento si progetti la propria applicazione in modo che accodi le richieste concorrenti nel componente conversazionale, c'è il rischio che il server venga sovraccaricato e sia incapace di processare tutte le richieste prima che la richiesta debba aspettare più a lungo del concurrent-request-timeout. In questo caso Seam lancerà una ConcurrentRequestTimeoutException che potrà venir gestita in pages.xml. Si raccomanda di inviare un errore HTTP 503:


   <exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
      <http-error error-code="503" />
   </exception
>

In alternativa si può fare il redirect ad una pagina d'errore:


<exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
   <end-conversation/>
   <redirect view-id="/error.xhtml">
      <message
>The server is too busy to process your request, please try again later</message>
   </redirect>
</exception
>

ICEfaces, RichFaces e Seam Remoting possono tutti gestire i codici d'errore HTTP. Seam Remoting mostrerà una finestra di dialogo con l'errore HTTP e ICEfaces indicherà l'errore nel suo componente di stato. RichFaces fornisce il supporto più completo per la gestione degli errori HTTP e chiamate callback definibili dall'utente. Per esempio, per mostrare il messaggio d'errore all'utente:

<script type="text/javascript">
   A4J.AJAX.onError = function(req,status,message) { 
      alert("An error occurred");
   };
</script
>

Invece di un codice d'errore, il server riporta che la vista è scaduta, forse per un timeout di sessione, si usi una funzione callback separata in RichFaces per gestire questo scenario.

<script type="text/javascript">
   A4J.AJAX.onExpired = function(loc,message) { 
      alert("View expired");
   };
</script
>

In alternativa si può consentire a RichFaces di gestire quest'errore, nel qual caso all'utente verrà presentato un prompt che chiede "Lo stato della vista non può essere ripristinato - ricaricare la pagina?" Si può personalizzare questo messaggio impostando la seguente chiave in un resource bundle dell'applicazione.

AJAX_VIEW_EXPIRED=View expired. Please reload the page.

RichFaces (Ajax4jsf) è la libreria AJAX più usata in Seam e fornisce tutti i controlli discussi sopra:

JBoss jBPM è un motore di gestione dei processi di business per ambiente Java SE o EE. jBPM ti consente di rappresentare un processo di business o un'interazione utente come un grafo di nodi, raffiguranti stati d'attesa, decisioni, compiti (task), pagine web, ecc. Il grafo viene definito usando un dialetto XML semplice, molto leggibile, chiamato jPDL, che può essere editato e visualizzato graficamente usando un plugin di eclipse. jPDL è un linguaggio estendibile ed è adatto per un range di problemi, dalla definizione di un flusso di pagine dell'applicazione web alla gestione tradizionale del workflow, fino all'orchestrazione di servizi in un ambiente SOA.

Le applicazioni Seam utilizzano jBPM per due tipi di problemi:

  • Complesse interazioni da parte dell'utente comportano la definizione un pageflow (flusso di pagina). Una definizione di un processo con jPDL stabilisce il flusso delle pagine per una singola conversazione. Una conversazione in Seam è considerata un'interazione di breve durata con un singolo utente.

  • Definizione del processo di business sottostante. Il processo di business può comportare una serie di conversazioni con più utenti. Il suo stato viene persistito nel database jBPM, divenendo così di lunga durata. Il coordinamento delle attività di più utenti è un problema molto più complesso che descrivere l'interazione di un singolo utente, cosicché jBPM offre dei modi sofisticati per la gestione dei compiti (task) e per la gestione di più percorsi concorrenti di esecuzione.

Non confondere le due cose! Queste operano a livelli molto diversi e con diverso grado di granularità. Pageflow, conversazione e task si riferiscono tutti alla singola interazione con il singolo utente. Un processo di business comporta più task. Quindi le due applicazione di jBPM sono totalmente ortogonali. Possono essere usate assieme, in modo indipendente, o si può non usarle affatto.

Non serve conoscere jPDL per usare Seam. Se ci si trova bene nel definire un pageflow con JSF o con le regole di navigazione di Seam, e se l'applicazione è guida più dai dati che dal processo, probabilmente non serve usare jBPM. Ma noi pensiamo che strutturare l'interazione dell'utente in termini di rappresentazione grafical ben definita aiuti a costruire applicazioni più robuste.

Ci sono due modi per definire il pageflow in Seam:

Applicazioni molto semplici richiedono soltanto un modello di navigazione stateless. Invece applicazioni molto complesse impiegano entrambi i modelli in differenti punti. Ciascun modello ha i suoi punti di forza e le sue debolezze!

Il modello stateless definisce una mappatura tra un set di esiti di un evento e la pagina della vista. Le regole di navigazione sono interamente senza memoria rispetto allo stato mantenuto dall'applicazione oltre che alla pagina origine dell'evento. Questo significa che i metodi dell'action listener devono di tanto in tanto prendere decisioni sul pageflow, poiché solo loro hanno accesso allo stato corrente dell'applicazione.

Ecco ora un esempio di definizione di pageflow usando le regole di navigazione JSF:


<navigation-rule>
    <from-view-id
>/numberGuess.jsp</from-view-id>
        
    <navigation-case>
        <from-outcome
>guess</from-outcome>
        <to-view-id
>/numberGuess.jsp</to-view-id>
        <redirect/>
    </navigation-case>

    <navigation-case>
        <from-outcome
>win</from-outcome>
        <to-view-id
>/win.jsp</to-view-id>
        <redirect/>
    </navigation-case>
        
    <navigation-case>
        <from-outcome
>lose</from-outcome>
        <to-view-id
>/lose.jsp</to-view-id>
        <redirect/>
    </navigation-case>

</navigation-rule
>

Ecco lo stesso esempio di definizione di pageflow usando le regole di navigazione di Seam:


<page view-id="/numberGuess.jsp">
        
    <navigation>
        <rule if-outcome="guess">
            <redirect view-id="/numberGuess.jsp"/>
        </rule>
        <rule if-outcome="win">
            <redirect view-id="/win.jsp"/>
        </rule>
        <rule if-outcome="lose">
            <redirect view-id="/lose.jsp"/>
        </rule>
    </navigation>

</page
>

Se ritieni che le regole di navigazione siano troppo lunghe, si può restituire l'id della vista direttamente dai metodi dell'action listener:

public String guess() {

    if (guess==randomNumber) return "/win.jsp";
    if (++guessCount==maxGuesses) return "/lose.jsp";
    return null;
}

Si noti che questo comporta un redirect. Si possono persino specificare i parametri da usare nel redirect:

public String search() {

    return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}

Il modello stateful definisce un set di transizioni tra gli stati dell'applicazione. In questo modello è possibile esprimere il flusso di qualsiasi interazione utente interamente nella definizione jPDL di pageflow, e scrivere i metodi action listener completamente slegati dal flusso dell'interazione.

Ecco ora un esempio di definizione di pageflow usando jPDL:


<pageflow-definition name="numberGuess">
    
   <start-page name="displayGuess" view-id="/numberGuess.jsp">
      <redirect/>
      <transition name="guess" to="evaluateGuess">
              <action expression="#{numberGuess.guess}" />
      </transition>
   </start-page>
   
   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
      <transition name="true" to="win"/>
      <transition name="false" to="evaluateRemainingGuesses"/>
   </decision>
   
   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
      <transition name="true" to="lose"/>
      <transition name="false" to="displayGuess"/>
   </decision>
   
   <page name="win" view-id="/win.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
   <page name="lose" view-id="/lose.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
</pageflow-definition
>

Ci sono due cose da notare immediatamente:

In aggiunta il modello stateful è più vincolato. Per ogni stato logico (ogni passo del pageflow) c'è un set vincolato di possibili transizioni verso altri stati. Il modello stateless è un modello ad hoc adatto ad una navigazione libera e senza vincoli in cui l'utente decide dove andare, non l'applicazione.

La distinzione di navigazione stateful/stateless è abbastanza simile alla tradizionale vista di interazione modale/senza modello. Ora le applicazioni Seam non sono solitamente modali nel semplice senso della parola - infatti, evitare il comportamento modale dell'applicazione è uno delle principali ragioni per usare le conversazioni! Comunque le applicazioni Seam possono essere, e spesso lo sono, modali a livello di una particolare conversazione. E' noto che il comportamento modale è qualcosa da evitare il più possibile; è molto difficile predire l'ordine in cui gli utenti vogliono fare le cose! Comunque non c'è dubbio che il modello stateful ha un suo utilizzo.

Il maggior contrasto fra i due modelli è nel comportamento col pulsante indietro.

Quando le regole di navigazione di JSF o Seam vendono impiegate, Seam consente all'utente di navigare liberamente avanti ed indietro e di usare il pulsante aggiorna. E' responsabilità dell'applicazione assicurare che lo stato conversazionale rimanga internamente consistente quando questo avviene. L'esperienza con la combinazione di framework web come Struts o WebWork - che non supportano un modello conversazionale - e modelli a componenti stateless come session bean EJB stateless o il framework Spring ha insegnato a molti sviluppatori che questo è quasi praticamente impossibile da realizzare! Comunqur la nostra esperienza è che il contesto di Seam, dove c'è un modello conversazionale ben definito, agganciato a session bean stateful, è in verità abbastanza semplice. E' tanto semplice quanto combinare l'uso di no-conversation-view-id con controlli nulli all'inizio di metodi action listener. Riteniamo che il supporto alla navigazione libera sia quasi sempre desiderabile.

In questo casola dichiarazione no-conversation-view-id va in pages.xml. Questa dice a Seam di reindirizzare ad una pagina differente se la richiesta proviene da una pagina generata durante una conversazione, e questa conversazione non esiste più:


<page view-id="/checkout.xhtml" 
        no-conversation-view-id="/main.xhtml"/>

Dall'altro lato, nel modello stateful, il pulsante indietro viene interpretato come una transizione indietro ad un precedente stato. Poiché il modello stateful costringe ad un set di transizioni dallo stato corrente, il pulsante indietro viene di default disabilitato nel modello stateful! Seam rileva in modo trasparente l'uso del pulsante indietro e blocca qualsiasi tentativo di eseguire un'azione da una pagina precedente "in stallo", e reindirizza l'utente alla pagina "corrente" (mostrando un messagio faces). Se si consideri questa una funzionalità oppure una limitazione del modello stateful dipende dal proprio punto di vista: come sviluppatore è una funzionalità; come utente può essere frustrante! Si può abilitare la navigazione da un particolare nodo di pagina con il pulsante indietro impostando back="enabled".


<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page
>

Questo permette l'uso del pulsante indietro dallo stato checkout a qualsiasi altro stato!

Occorre ancora definire cosa succede se una richiesta ha origine da una pagina generata durante un pageflow mentre la conversazione con il pageflow non esiste più. In questo caso la dichiarazione no-conversation-view-id va dentro la definizione del pageflow:


<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled" 
        no-conversation-view-id="/main.xhtml">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page
>

In pratica entrambi i modelli di navigazione hanno la loro utilità ed imparerai presto a riconoscere quando impiegare uno o l'altro.

Si "inizia" un pageflow basato su jPDLspecificando il nome della definizione del processo usando un'annotazione @Begin, @BeginTask oppure @StartTask:

@Begin(pageflow="numberguess")

public void begin() { ... }

In alternativa si può iniziare un pageflow usando pages.xml:


<page>
        <begin-conversation pageflow="numberguess"/>
    </page
>

Se il pageflow viene iniziato durante la fase RENDER_RESPONSE — durante un metodo @Factory o @Create, per esempio — si presume di essere già nella pagina da generare, e si usa un nodo <start-page> come primo nodo nel pageflow, come nell'esempio sopra.

Ma se il pageflow viene iniziato come risultato di un'invocazione di un action listener, l'esito dell'action listener determina quale è la prima pagina da generare. In questo caso si usa un <start-state> come primo nodo del pageflow, e si dichiara una transizione per ogni possibile esito:


<pageflow-definition name="viewEditDocument">

    <start-state name="start">
        <transition name="documentFound" to="displayDocument"/>
        <transition name="documentNotFound" to="notFound"/>
    </start-state>
    
    <page name="displayDocument" view-id="/document.jsp">
        <transition name="edit" to="editDocument"/>
        <transition name="done" to="main"/>
    </page>
    
    ...
    
    <page name="notFound" view-id="/404.jsp">
        <end-conversation/>
    </page>
    
</pageflow-definition
>

Ogni nodo <page> rappresenta uno stato in cui il sistema aspetta input da parte dell'utente:


<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition name="guess" to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page
>

view-id è l'id della vista JSF. L'elemento <redirect/> ha lo stesso effetto di <redirect/> in una regola di navigazione JSF: cioè un comportamento post-then-redirect per superare i problemi del pulsante aggiorna del browser. (Si noti che Seam propaga i contesti di conversazioni assieme a questi redirect. Quindi in Seam non serve alcun construtto "flash" dello stile di Ruby on Rails!)

Il nome della transizione è il nome dell'esito JSF lanciato cliccando un command button od un command link in numberGuess.jsp.


<h:commandButton type="submit" value="Guess" action="guess"/>

Quando cliccando il pulsante verrà invocata la transizione, jBPM attiverà l'azione della transizione chiamando il metodo guess() del componente numberGuess. Si noti che la sintassi usata per specificare le azioni in jPDL non è che un'espressione JSF EL già familiare, e che l'action handler della transizione è solo un metodo di un componente Seam negli attuali contesti. Così per gli eventi jBPM si ha esattamente lo stesso modello ad eventi visto per gli eventi JSF! (Il principio Unico tipo di "cosa".)

Nel caso di esito nullo (per esempio un pulsante di comando senza la definizione di action), Seam segnalerà la transizione senza nome se ne esiste una, oppure rivisualizzerà la pagina se tutte le transizioni hanno un nome. Così è possibile semplificare leggermente l'esempio del pageflow e questo pulsante:


<h:commandButton type="submit" value="Guess"/>

Esegue la seguente transizione senza nome:


<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page
>

E' anche possibile che il pulsante chiami un action method, nel qual caso l'esito dell'azione determinerà la transizione da prendere:


<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>

<page name="displayGuess" view-id="/numberGuess.jsp">
    <transition name="correctGuess" to="win"/>
    <transition name="incorrectGuess" to="evaluateGuess"/>
</page
>

Comunque questo è considerato uno stile inferiore, poiché sposta la responsabilità del controllo del flusso fuori dalla definizione del pageflow e la mette negli altri componenti. E' molto meglio centralizzare questo concern nel pageflow stesso.

Un processo di business è un set di task ben-definiti che deve essere eseguito dagli utenti o dai sistemi software secondo regole ben-definite riguardo chi può eseguire un task, e quandodeve essere eseguito. L'integrazione jBPM di Seam facilita la visione della lista di task agli utenti e consente loro di gestire questi task. Seam consente anche all'applicazione di memorizzare lo stato associato al processo di business nel contesto BUSINESS_PROCESS, ed rendere questo stato persistente tramite variabili jBPM.

Una semplice definizione di processo di business appare più o meno come una definizione di pageflow (Unico tipo di cosa), tranne che invece dei nodi <page>, si hanno i nodi <task-node>. In un processo di business long-running, gli stati di attesa si verificano quando il sistema aspetta che un qualche utente entri ed esegua un task.


<process-definition name="todo">
   
   <start-state name="start">
      <transition to="todo"/>
   </start-state>
   
   <task-node name="todo">
      <task name="todo" description="#{todoList.description}">
         <assignment actor-id="#{actor.id}"/>
      </task>
      <transition to="done"/>
   </task-node>
   
   <end-state name="done"/>
   
</process-definition
>

E' perfettamente possibile avere entrambi le definizioni di processo di business jPDL e le definizioni di pageflow jPDL nello stesso progetto. Se questo è il caso, la relazione tra i due è che un singolo <task> in un processo di business corrisponde ad un intero pageflow <pageflow-definition>

Parecchi componenti incorporati in Seam consentono facilmente di visualizzare liste di compiti. pooledTaskInstanceList è una lista di compiti che gli utenti possono assegnare a se stessi:


<h:dataTable value="#{pooledTaskInstanceList}" var="task">
    <h:column>
        <f:facet name="header"
>Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
    </h:column
>                    
</h:dataTable
>

Notare che invece di <s:link> avremmo potuto usare un semplice JSF<h:commandLink>:


<h:commandLink action="#{pooledTask.assignToCurrentActor}"
> 
    <f:param name="taskId" value="#{task.id}"/>
</h:commandLink
>

Il componente pooledTask è un componente incorporato che semplicemente assegna il task all'utente corrente.

Il componente taskInstanceListForType include i task di un particolare tipo che sono stati assegnati all'utente corrente:


<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
    <h:column>
        <f:facet name="header"
>Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
    </h:column
>                    
</h:dataTable
>

Seam fornisce un supporto esteso alle due maggiori e più popolari architetture per la persistenza in Java: Hibernate3 e Java Persistence API introdotta con EJB 3.0. L'architettura unica di Seam per la gestione dello stato consente l'integrazione dei più sofisticati ORM di ogni framework per applicazioni web.

Seam è nato dalla frustrazione del team di Hibernate per l'assenza di contesto stateless tipica delle precedenti generazioni di architetture nelle applicazioni Java. L'architettura della gestione dello stato di Seam è stata originariamente progettata per risolvere problemi relativi alla persistenza — in particolare i problemi associati all'elaborazione ottimistica delle transazioni. Le applicazioni online scalabili usano sempre transazioni ottimistiche. Una transazione atomica di livello (database/JTA) non dovrebbe propagare l'interazione dell'utente amenoché l'applicazione sia progettata per supportare solo un piccolo numero di client concorrenti. Ma quasi tutto il lavoro interessante coinvolge in primo luogo la visualizzazione dei dati all'utente, e poi, immediatamente dopo, l'aggiornamento dei dati stessi. Quindi Hibernate è stato progettato per supportare l'idea del contesto di persistenza che propaga una transazione ottimistica.

Sfortunatamente le cosiddette architetture "stateless" che precedettero Seam e EJB 3.0 non avevano alcun costrutto per rappresentare una transazione ottimistica. Quindi, invece, queste architetture fornivano contesti di persistenza con scope a livello di transazione atomica. Sicuramente questo portava diversi problemi agli utenti ed è la causa numero uno per le lamentele riguardanti Hibernate: la temuta LazyInitializationException. Ciò di cui si ha bisogno è un costrutto per rappresentare una transazione ottimistica a livello applicazione.

EJB 3.0 riconosce il problema e introduce l'idea di componente stateful (un bean di sessione stateful) con un contesto di persistenza esteso con scope legato al ciclo di vita del componente. Questa è una soluzione parziale al problema (ed è un utile costrutto), comunque ci sono due problemi:

Seam risolve il primo problema fornendo conversazioni, componenti bean di sessione stateful con scope di conversazione. (La maggior parte delle conversazioni in verità rappresentano transazioni ottimistiche a livello dei dati). Questo è sufficiente per molte semplici applicazioni (quali la demo prenotazione di Seam) dove non serve la propagazione del contesto di persistenza. Per applicazioni più complesse, con molti componenti interagenti in modo stretto in ciascuna conversazione, la propagazione del contesto di persistenza tra componenti diventa un problema importante. Quindi Seam estende il modello di gestione del contesto di persistenza di EJB 3.0 per fornire contesti di persistenza estesi e con scope di conversazione.

I bean di sessione EJB includono la gestione dichiarativa delle transazioni. Il container EJB è capace di avviare una transazione in modo trasparente quando viene invocato il bean, e terminarla quando termina l'invocazione. Se si scrive un metodo di un bean di sessione che agisce come action listener JSF, si può fare tutto il lavoro associato all'azione in una transazione, ed essere sicuri che venga eseguito il commit od il rollback quando l'azione viene terminata. Questa è grande funzionalità ed è tutto ciò che serve ad alcune applicazioni Seam.

Comunque c'è un problema con tale approccio. Un'applicazione Seam potrebbe non eseguire l'accesso a tutti i dati per una richiesta da una chiamata di un singolo metodo a un bean di sessione.

Più transazioni per richiesta ci sono, più è probabile che si incontrino problemi di atomicità e isolamento quando l'applicazione processa molte richieste concorrenti. Certamente tutte le operazioni di scrittura devono avvenire nella stessa transazione!

Hibernate users developed the "open session in view" pattern to work around this problem. In the Hibernate community, "open session in view" was historically even more important because frameworks like Spring use transaction-scoped persistence contexts. So rendering the view would cause LazyInitializationExceptions when unfetched associations were accessed.

This pattern is usually implemented as a single transaction which spans the entire request. There are several problems with this implementation, the most serious being that we can never be sure that a transaction is successful until we commit it — but by the time the "open session in view" transaction is committed, the view is fully rendered, and the rendered response may already have been flushed to the client. How can we notify the user that their transaction was unsuccessful?

Seam solves both the transaction isolation problem and the association fetching problem, while working around the problems with "open session in view". The solution comes in two parts:

In the next section, we'll tell you how to set up a conversation-scope persistence context. But first we need to tell you how to enable Seam transaction management. Note that you can use conversation-scoped persistence contexts without Seam transaction management, and there are good reasons to use Seam transaction management even when you're not using Seam-managed persistence contexts. However, the two facilities were designed to work together, and work best when used together.

La gestione delle transazioni di Seam è utile anche se vengono usati contesti di persistenza gestiti da un container EJB 3.0. Ma in particolare essa è utile quando Seam è usato fuori dall'ambiente Java EE 5, o in ogni altro caso dove si usi un contesto di persistenza gestito da Seam.

Seam provides a transaction management abstraction for beginning, committing, rolling back, and synchronizing with a transaction. By default Seam uses a JTA transaction component that integrates with Container Managed and programmatic EJB transactions. If you are working in a Java EE 5 environment, you should install the EJB synchronization component in components.xml:


<transaction:ejb-transaction />

However, if you are working in a non EE 5 container, Seam will try auto detect the transaction synchronization mechanism to use. However, if Seam is unable to detect the correct transaction synchronization to use, you may find you need configure one of the following:

Configure JPA RESOURCE_LOCAL transaction management by adding the following to your components.xml where #{em} is the name of the persistence:managed-persistence-context component. If your managed persistence context is named entityManager, you can opt to leave out the entity-manager attribute. (see Seam-managed persistence contexts )


<transaction:entity-transaction entity-manager="#{em}"/>

To configure Hibernate managed transactions declare the following in your components.xml where #{hibernateSession} is the name of the project's persistence:managed-hibernate-session component. If your managed hibernate session is named session, you can opt to leave out the session attribute. (see Seam-managed persistence contexts )


<transaction:hibernate-transaction session="#{hibernateSession}"/>

To explicitly disable Seam managed transactions declare the following in your components.xml:


<transaction:no-transaction />

For configuring Spring managed transactions see using Spring PlatformTransactionManagement .

If you're using Seam outside of a Java EE 5 environment, you can't rely upon the container to manage the persistence context lifecycle for you. Even if you are in an EE 5 environment, you might have a complex application with many loosly coupled components that collaborate together in the scope of a single conversation, and in this case you might find that propagation of the persistence context between component is tricky and error-prone.

In either case, you'll need to use a managed persistence context (for JPA) or a managed session (for Hibernate) in your components. A Seam-managed persistence context is just a built-in Seam component that manages an instance of EntityManager or Session in the conversation context. You can inject it with @In.

Seam-managed persistence contexts are extremely efficient in a clustered environment. Seam is able to perform an optimization that EJB 3.0 specification does not allow containers to use for container-managed extended persistence contexts. Seam supports transparent failover of extended persisence contexts, without the need to replicate any persistence context state between nodes. (We hope to fix this oversight in the next revision of the EJB spec.)

Configuring a managed persistence context is easy. In components.xml, we can write:


<persistence:managed-persistence-context name="bookingDatabase" 
                                  auto-create="true"
                   persistence-unit-jndi-name="java:/EntityManagerFactories/bookingData"/>

This configuration creates a conversation-scoped Seam component named bookingDatabase that manages the lifecycle of EntityManager instances for the persistence unit (EntityManagerFactory instance) with JNDI name java:/EntityManagerFactories/bookingData.

Of course, you need to make sure that you have bound the EntityManagerFactory into JNDI. In JBoss, you can do this by adding the following property setting to persistence.xml.


<property name="jboss.entity.manager.factory.jndi.name" 
          value="java:/EntityManagerFactories/bookingData"/>

Now we can have our EntityManager injected using:

@In EntityManager bookingDatabase;

If you are using EJB3 and mark your class or method @TransactionAttribute(REQUIRES_NEW) then the transaction and persistence context shouldn't be propagated to method calls on this object. However as the Seam-managed persistence context is propagated to any component within the conversation, it will be propagated to methods marked REQUIRES_NEW. Therefore, if you mark a method REQUIRES_NEW then you should access the entity manager using @PersistenceContext.

Persistence contexts scoped to the conversation allows you to program optimistic transactions that span multiple requests to the server without the need to use the merge() operation , without the need to re-load data at the beginning of each request, and without the need to wrestle with the LazyInitializationException or NonUniqueObjectException.

As with any optimistic transaction management, transaction isolation and consistency can be achieved via use of optimistic locking. Fortunately, both Hibernate and EJB 3.0 make it very easy to use optimistic locking, by providing the @Version annotation.

By default, the persistence context is flushed (synchronized with the database) at the end of each transaction. This is sometimes the desired behavior. But very often, we would prefer that all changes are held in memory and only written to the database when the conversation ends successfully. This allows for truly atomic conversations. As the result of a truly stupid and shortsighted decision by certain non-JBoss, non-Sun and non-Sybase members of the EJB 3.0 expert group, there is currently no simple, usable and portable way to implement atomic conversations using EJB 3.0 persistence. However, Hibernate provides this feature as a vendor extension to the FlushModeTypes defined by the specification, and it is our expectation that other vendors will soon provide a similar extension.

Seam lets you specify FlushModeType.MANUAL when beginning a conversation. Currently, this works only when Hibernate is the underlying persistence provider, but we plan to support other equivalent vendor extensions.

@In EntityManager em; //Seam-managed persistence context


@Begin(flushMode=MANUAL)
public void beginClaimWizard() {
    claim = em.find(Claim.class, claimId);
}

Now, the claim object remains managed by the persistence context for the rest ot the conversation. We can make changes to the claim:

public void addPartyToClaim() {

    Party party = ....;
    claim.addParty(party);
}

But these changes will not be flushed to the database until we explicitly force the flush to occur:

@End

public void commitClaim() {
    em.flush();
}

Of course, you could set the flushMode to MANUAL from pages.xml, for example in a navigation rule:


<begin-conversation flush-mode="MANUAL" />

Si può impostare qualsiasi Contesto di Persistenza Gestito da Seam alla modalità flush manuale:

<components xmlns="http://jboss.com/products/seam/components"
   xmlns:core="http://jboss.com/products/seam/core">
   <core:manager conversation-timeout="120000" default-flush-mode="manual" />
</components
>

The EntityManager interface lets you access a vendor-specific API via the getDelegate() method. Naturally, the most interesting vendor is Hibernate, and the most powerful delegate interface is org.hibernate.Session. You'd be nuts to use anything else. Trust me, I'm not biased at all. If you must use a different JPA provider see Using Alternate JPA Providers.

But regardless of whether you're using Hibernate (genius!) or something else (masochist, or just not very bright), you'll almost certainly want to use the delegate in your Seam components from time to time. One approach would be the following:

@In EntityManager entityManager;


@Create
public void init() {
    ( (Session) entityManager.getDelegate() ).enableFilter("currentVersions");
}

But typecasts are unquestionably the ugliest syntax in the Java language, so most people avoid them whenever possible. Here's a different way to get at the delegate. First, add the following line to components.xml:


<factory name="session" 
         scope="STATELESS" 
         auto-create="true" 
         value="#{entityManager.delegate}"/>

Ora si può iniettare la sessione direttamente:

@In Session session;


@Create
public void init() {
    session.enableFilter("currentVersions");
}

The coolest, and most unique, feature of Hibernate is filters. Filters let you provide a restricted view of the data in the database. You can find out more about filters in the Hibernate documentation. But we thought we'd mention an easy way to incorporate filters into a Seam application, one that works especially well with the Seam Application Framework.

I contesti di persistenza gestiti da Seam possono avere una lista di filtri definiti, che verrà abilitata quando viene creato un EntityManager od una Session di Hibernate. (Certamente possono essere utilizzati solo quando Hibernare è il provider di persistenza sottostante.)


<persistence:filter name="regionFilter">
    <persistence:name
>region</persistence:name>
    <persistence:parameters>
        <key
>regionCode</key>
        <value
>#{region.code}</value>
    </persistence:parameters>
</persistence:filter>

<persistence:filter name="currentFilter">
    <persistence:name
>current</persistence:name>
    <persistence:parameters>
        <key
>date</key>
        <value
>#{currentDate}</value>
    </persistence:parameters>
</persistence:filter>

<persistence:managed-persistence-context name="personDatabase"
    persistence-unit-jndi-name="java:/EntityManagerFactories/personDatabase">
    <persistence:filters>
        <value
>#{regionFilter}</value>
        <value
>#{currentFilter}</value>
    </persistence:filters>
</persistence:managed-persistence-context
>

Nel puro JSF la validazione è definita nella vista:


<h:form>
    <h:messages/>

    <div>
        Country:
        <h:inputText value="#{location.country}" required="true">
            <my:validateCountry/>
        </h:inputText>
    </div>
    
    <div>
        Zip code:
        <h:inputText value="#{location.zip}" required="true">
            <my:validateZip/>
        </h:inputText>
    </div>

    <h:commandButton/>
</h:form
>

In pratica quest'approccio di norma viola il principio DRY (Don't Repeat Yourself = Non ripeterti), poiché la maggior parte della "validazione" forza i vincoli che sono parte del modello di dati, e che esistono lungo tutta la definizione dello schema di database. Seam fornisce supporto ai vincoli del modello definiti, utilizzando Hibernate Validator.

Si cominci col definire i vincoli sulla classe Location:

public class Location {

    private String country;
    private String zip;
    
    @NotNull
    @Length(max=30)
    public String getCountry() { return country; }
    public void setCountry(String c) { country = c; }
    @NotNull
    @Length(max=6)
    @Pattern("^\d*$")
    public String getZip() { return zip; }
    public void setZip(String z) { zip = z; }
}

Bene, questa è una prima buona riduzione, ma in pratica è possibile essere più eleganti utilizzando dei vincoli personalizzati invece di quelli interni a Hibernate Validator:

public class Location {

    private String country;
    private String zip;
    
    @NotNull
    @Country
    public String getCountry() { return country; }
    public void setCountry(String c) { country = c; }
    @NotNull
    @ZipCode
    public String getZip() { return zip; }
    public void setZip(String z) { zip = z; }
}

Qualsiasi strada si prenda non occorre più specificare il tipo di validazione da usare nelle pagine JSF. Invece è possibile usare <s:validate> per validare il vincolo definito nell'oggetto modello.


<h:form>
    <h:messages/>

    <div>
        Country:
        <h:inputText value="#{location.country}" required="true">
            <s:validate/>
        </h:inputText>
    </div>
    
    <div>
        Zip code:
        <h:inputText value="#{location.zip}" required="true">
            <s:validate/>
        </h:inputText>
    </div>
    
    <h:commandButton/>

</h:form
>

Nota: specificare @NotNull nel modello non elimina la necessità di required="true" per farlo apparire nel controllo! Questo è dovuto ad una limitazione nell'architettura di validazione in JSF.

Quest'approccio definisce i vincoli sul modello e presenta le violazioni al vincolo nella vista — di gran lunga un miglior design.

Comunque non è molto meno corto di quanto lo era all'inizio, quindi proviamo <s:validateAll>:


<h:form>
    
    <h:messages/>

    <s:validateAll>

        <div>
            Country:
            <h:inputText value="#{location.country}" required="true"/>
        </div>

        <div>
            Zip code:
            <h:inputText value="#{location.zip}" required="true"/>
        </div>

        <h:commandButton/>

    </s:validateAll>

</h:form
>

Questo tag semplicemente aggiunge un <s:validate> ad ogni input nella form. Per form grandi questo fa risparmiare molto!

Adesso occorre fare qualcosa per mostrare all'utente un messaggio quando la validazione fallisce. Ora vengono mostrati tutti i messaggi in cima alla form. Per consentire all'utente di associare il messaggio al singolo input, occorre definire un'etichetta e usare l'attributo standard label sul componente d'input.


<h:inputText value="#{location.zip}" required="true" label="Zip:">
    <s:validate/>
</h:inputText
>

Si può iniettare questo valore nella stringa del messaggio usando il placeholder {0} (il primo ed unico parametro passato al messaggio JSF a causa di una restrizione in Hibernate Validator). Vedere la sezione Internazionalizzazione per ulteriori informazioni sulla definizione dei messaggi.

validator.length={0} la lunghezza deve essere tra {min} e {max}

Ciò che si vuole fare, tuttavia, è mostrare il messaggio vicino al campo con l'errore (questo è possibile nel semplice JSF), ed evidenziare il campo e l'etichetta (questo non è possibile) ed, eventualmente, mostrare un'immagine vicino al campo (anche questo non è possibile). Si vuole anche mostrare un piccolo asterisco colorato vicino all'etichetta per ciascun campo richiesto. Utilizzando quest'approccio non è più necessario identificare l'etichetta.

Si ha quindi bisogno di parecchia funzionalità per ogni singolo campo della form. Ma non si vuole essere costretti a specificare per ogni campo della form come evidenziare, come disporre l'immagine, quale messaggio scrivere e quale è il campo d'input da associare.


<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:s="http://jboss.com/products/seam/taglib">
                 
    <div>
    
        <s:label styleClass="#{invalid?'error':''}">
            <ui:insert name="label"/>
            <s:span styleClass="required" rendered="#{required}"
>*</s:span>
        </s:label>
        
        <span class="#{invalid?'error':''}">
            <h:graphicImage value="/img/error.gif" rendered="#{invalid}"/>
            <s:validateAll>
                <ui:insert/>
            </s:validateAll>
        </span>
        
        <s:message styleClass="error"/>
        
    </div>
    
</ui:composition
>

Si può includere questo template per ciascun campo della form utilizzando <s:decorate>.


<h:form>

    <h:messages globalOnly="true"/>

    <s:decorate template="edit.xhtml">
        <ui:define name="label"
>Country:</ui:define>
        <h:inputText value="#{location.country}" required="true"/>
    </s:decorate>
    
    <s:decorate template="edit.xhtml">
        <ui:define name="label"
>Zip code:</ui:define>
        <h:inputText value="#{location.zip}" required="true"/>
    </s:decorate>

    <h:commandButton/>

</h:form
>

Infine è possibile utilizzare RichFaces Ajax per mostrare i messaggi di validazione mentre l'utente naviga nella form:


<h:form>

    <h:messages globalOnly="true"/>

    <s:decorate id="countryDecoration" template="edit.xhtml">
        <ui:define name="label"
>Country:</ui:define>
        <h:inputText value="#{location.country}" required="true">
            <a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
        </h:inputText>
    </s:decorate>
    
    <s:decorate id="zipDecoration" template="edit.xhtml">
        <ui:define name="label"
>Zip code:</ui:define>
        <h:inputText value="#{location.zip}" required="true">
            <a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
        </h:inputText>
    </s:decorate>

    <h:commandButton/>

</h:form
>

E' meglio definire esplicitamente gli id per i controlli importanti all'interno di una pagina, specialmente in caso di un test automatico della UI, utilizzando dei toolkit quale Selenium. Se non vengono forniti gli id in modo esplicito, JSF li genererà, ma i valori generati cambieranno se si cambia qualcosa nella pagina.


<h:form id="form">

    <h:messages globalOnly="true"/>

    <s:decorate id="countryDecoration" template="edit.xhtml">
        <ui:define name="label"
>Country:</ui:define>
        <h:inputText id="country" value="#{location.country}" required="true">
            <a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
        </h:inputText>
    </s:decorate>
    
    <s:decorate id="zipDecoration" template="edit.xhtml">
        <ui:define name="label"
>Zip code:</ui:define>
        <h:inputText id="zip" value="#{location.zip}" required="true">
            <a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
        </h:inputText>
    </s:decorate>

    <h:commandButton/>

</h:form
>

E se si vuole specificare un messaggio diverso quando la validazione fallisce? Si può utilizzare il message bundle di Seam con Hibernate Validator (e tutte le altre finezze come le espressioni EL dentro il messaggio ed i message bundle per ogni singola vista).

public class Location {

    private String name;
    private String zip;
    
    // Getters and setters for name
    @NotNull
    @Length(max=6)
    @ZipCode(message="#{messages['location.zipCode.invalid']}")
    public String getZip() { return zip; }
    public void setZip(String z) { zip = z; }
}
location.zipCode.invalid = Codice ZIP non valido per #{location.name}

Uno degli aspetti di JBoss Seam è la caratteristica RAD (Rapid Application Development). Benché i linguaggi dinamici non siano un sinonimo di RAD, in questo ambito essi sono uno degli aspetti più interessanti. Fino a poco tempo fa scegliere un linguaggio dinamico richiedeva anche di scegliere una piattaforma di sviluppo completamente differente (una piattaforma di sviluppo con un insieme di API ed un ambiente runtime così conveniente da non voler più tornare ad usare le vecchie API Java, con la fortuna di essere costretti ad usare in ogni caso quelle API proprietarie). I linguaggi dinamici costruiti sulla Java Virtual Machine, e Groovy in particolare, hanno rotto questo approccio alla grande.

Oggi JBoss Seam unisce il mondo dei linguaggi dinamici con il mondo Java EE integrando perfettamente sia i linguaggi statici che quelli dinamici. JBoss Seam lascia che lo sviluppatore scelga il migliore strumento per ciò che deve fare, senza cambiare contesto. Scrivere componenti Seam dinamici è esattamente come scrivere componenti Seam normali. Si usano le stesse annotazioni, le stesse API, lo stesso di tutto.

Non c'è molto da dire su questo. Poiché un oggetto Groovy è un oggetto Java, è virtualmente possibile scrivere qualsiasi componente Seam, così come qualsiasi altra classe del resto, in Groovy e metterla in funzione. E' anche possibile fare un misto di classi Groovy e classi Java nella stessa applicazione.

Come è stato possibile notare finora, Seam usa pesantemente le annotazioni. Assicurarsi di usare Groovy 1.1 o una versione successiva per avere il supporto delle annotazioni. Di seguito ci sono alcuni esempi di codice Groovy utilizzato in una applicazione Seam.

Eseguire classi Groovy è molto simile ad eseguire classi Java (soprendentemente non c'è bisogno di scrivere o di essere compatibili con qualche complessa specifica a 3 lettere per supportare più linguaggi nei componenti di framework).

Al di là della modalità standard di esecuzione, JBoss Seam ha l'abilità, durante lo sviluppo, di sostituire componenti Seam JavaBeans senza bisogno di riavviare l'applicazione, risparmiando molto tempo nel ciclo di sviluppo e test. Lo stesso supporto è fornito per i componenti Seam GroovyBeans quando i file .groovy vengono eseguiti.

JBoss Seam supporta l'esecuzione diretta di file .groovy (cioè senza compilazione) nella modalità di hot deployment incrementale (solo per lo sviluppo). Ciò consente un ciclo di modifica/test molto rapido. Per impostare l'esecuzione dei file .groovy, seguire la configurazione indicata in Sezione 2.8, «Seam e hot deploy incrementale» ed eseguire il codice Groovy (i file .groovy) nella cartella WEB-INF/dev. I componenti GroovyBean verranno presi in modo incrementale senza bisogno di riavviare l'applicazione (e ovviamente neanche l'application server).

Fare attenzione al fatto che l'esecuzione diretta dei file .groovy soffre delle stesse limitazioni del normale hot deployment di Seam:

  • I componenti devono essere JavaBeans o GroovyBeans. Non possono essere componenti EJB3.

  • Le entità non possono essere eseguite in modalità hot deploy.

  • I componenti da eseguire in modalità hotdeploy non saranno visibili ad alcuna classe posizionata al di fuori di WEB-INF/dev

  • La modalità debug di Seam deve essere attivata

Seam supporta Wicket come uno strato di presentazione alternativo a JSF. Si guardi l'esempio wicket in Seam che illustra l'esempio Booking riscritto per Wicket.

Nota

Il supporto Wicket è nuovo in Seam, perciò alcune caratteristiche che sono disponibili in JSF non sono ancora disponibili quando viene usato Wicket (ad esempio il pageflow). Si potrà notare inoltre che la documentazione è molto centrata su JSF e necessita di una riorganizzazione per riflettere il supporto di prima categoria per Wicket.

Le caratteristiche aggiunte ad un'applicazione Wicket possono essere divise in due categorie: la bijection e l'orchestrazione. Esse vengono discusse in dettaglio di seguito.

E' comune un uso intensivo di classi interne (inner class) quando si costruisce un'applicazione Wicket, in cui l'albero dei componenti viene generato nel costruttore. Seam gestisce pienamente l'uso di annotazioni di controllo nelle inner class e nei costruttori (a differenza che nei componenti Seam normali).

Le annotazioni sono elaborate dopo ogni chiamata alla superclasse. Ciò significa che qualsiasi attributo iniettato non può essere passato come argomento in una chiamata a this() o super().

Quando un metodo è chiamato in una inner class, la bijection avviene per ogni classe che la contiene. Ciò consente di posizionare le variabili bi-iniettabili nella classe esterna e riferirsi ad esse in qualsiasi inner class.

E' possibile abilitare la sicurezza di un componente Wicket usando l'annotazione @Restrict. Questa può essere messa nel componente più esterno o in qualsiasi componente interno. Se viene indicato @Restrict, l'accesso al componente verrà automaticamente limitato agli utenti registrati. Facoltativamente è possibile usare un'espressione EL nell'attributo value per specificare la restrizione da applicare. Per maggiori dettagli vedi Capitolo 15, Sicurezza.

Ad esempio:

@Restrict

public class Main extends WebPage {
   ...

Suggerimento

Seam applicherà automaticamente la restrizione ad ogni classe interna.

E' possibile demarcare le conversazioni da un componente Wicket attraverso l'uso di @Begin e @End. La semantica di queste annotazioni è la stessa di quando sono usate nei componenti Seam. E' possibile mettere @Begin e @End in qualsiasi metodo.

Nota

L'attributo deprecato ifOutcome non è gestito.

Ad esempio:

item.add(new Link("viewHotel") {


   @Override
   @Begin
   public void onClick() {
      hotelBooking.selectHotel(hotel);
      setResponsePage(org.jboss.seam.example.wicket.Hotel.class);
   }
};

E' possibile che l'applicazione abbia delle pagine che possono essere visitate solo quando l'utente ha una conversazione lunga (long-running conversation) attiva. Per imporre questo criterio è possibile usare l'annotazione @NoConversationPage:

@Restrict
@NoConversationPage(Main.class)
public class Hotel extends WebPage {

Se si vogliono ulteriormente disaccoppiare le classi dell'applicazione è possibile usare gli eventi Seam. Naturalmente è possibile lanciare un evento usando Events.instance().raiseEvent("pippo"). In alternativa è possibile annotare un metodo con @RaiseEvent("pippo"); se il metodo restituisce un valore non nullo senza eccezioni, l'evento verrà lanciato.

E' anche possibile controllare task e processi nelle classi Wicket attraverso l'uso di @CreateProcess, @ResumeTask, @BeginTask, @EndTask, @StartTask e @Transition.

TODO - Realizzare il controllo BPM - JBSEAM-3194

Seam ha bisogno di intervenire sul bytecode delle classi Wicket in modo da poter intercettare le annotazioni usate. Seam fornisce due modi per fare questo. Il primo è di mettere le classi in WEB-INF/wicket. Seam cercherà le classi messe in questa cartella durante l'avvio e interverrà sul loro bytecode. Un approccio alternativo, che può essere utilizzato insieme al primo, è di usare un task ant per modificare il bytecode. Seam fornisce questo task: è contenuto in jboss-seam-wicket-ant.jar e può essere usato nel seguento modo:


<taskdef name="instrumentWicket" 
   classname="org.jboss.seam.wicket.ioc.WicketInstrumentationTask">
  <classpath>
    <pathelement location="lib/jboss-seam-wicket-ant.jar"/>
    <pathelement location="web/WEB-INF/lib/jboss-seam-wicket.jar"/>
    <pathelement location="lib/javassist.jar"/>
    <pathelement location="lib/jboss-seam.jar"/>
  </classpath>
</taskdef>

<instrumentWicket outputDirectory="${build.instrumented}">
  <classpath refid="build.classpath"/>
  <fileset dir="${build.classes}" includes="**/*.class"/>
</instrumentWicket
>

Dopo bisogna fare in modo che Ant copi le classi alterate da ${build.instrumented} a WEB-INF/classes. Se si vuole attivare l'esecuzione a caldo dei componenti Wicket è possibile copiare le classi alterate in WEB-INF/hot. Se si usa l'esecuzione a caldo, accertarsi che anche la classe WicketApplication sia eseguita nello stesso modo. Dopo che le classi eseguite a caldo vengono ricaricate, l'intera istanza di WicketApplication deve essere reinizializzata allo scopo di recuperare tutti i nuovi riferimenti alle classi delle pagine montate.

Un'applicazione web Wicket che usa Seam deve usare SeamWebApplication come classe base. Questa crea gli agganci nel ciclo Wicket che consentono a Seam di propagare auto-magicamente la conversazione quando necessario. Aggiunge pure i messaggi di stato alla pagina.

Ad esempio:

La SeamAuthorizationStrategy delega le autorizzazioni a Seam Security, consentendo l'uso di @Restrict nei componenti Wicket. SeamWebApplication provvede ad installare la strategia di autorizzazioni. E' possibile specificare una pagina di login implementando il metodo getLoginPage().

C'è poi bisogno di impostare la home page dell'applicazione implementando il metodo getHomePage().

public class WicketBookingApplication extends SeamWebApplication {


   @Override
   public Class getHomePage() {
      return Home.class;
   }
   @Override
   protected Class getLoginPage() {
      return Home.class;
   }
   
}

Seam installa automaticamente il filtro Wicket (assicurando che sia inserito nella posizione corretta), ma è ancora necessario indicare a Wicket quale classe WebApplication usare:


<components xmlns="http://jboss.com/products/seam/components"
 xmlns:wicket="http://jboss.com/products/seam/wicket"
 xsi:schemaLocation=
  "http://jboss.com/products/seam/wicket
   http://jboss.com/products/seam/wicket-2.1.xsd">
   
  <wicket:web-application 
    application-class="org.jboss.seam.example.wicket.WicketBookingApplication" />
</components

In aggiunta se si pensa di usare le pagine basate su JSF in un'appliczione con pagine wicket, bisogna assicurarsi che il filtro delle eccezioni jsf sia abilitato per gli url jsf:


<components xmlns="http://jboss.com/products/seam/components"
 xmlns:web="http://jboss.com/products/seam/web"
 xmlns:wicket="http://jboss.com/products/seam/wicket"
 xsi:schemaLocation=
  "http://jboss.com/products/seam/web
   http://jboss.com/products/seam/web-2.1.xsd">
     
    <!-- Si mappa solo il filtro per le eccezioni jsf di seam nel path jsf, che viene identificato con il path *.seam -->
        <web:exception-filter url-pattern="*.seam"/>
</components

Seam semplifica la creazione di applicazioni tramite la scrittura di classi Java semplici con annotazioni, che non hanno bisogno di estendere speciali interfacce o superclassi. Ma è possibile semplificare ulteriormente alcuni comuni compiti di programmazione, fornendo un set di componenti predefiniti che possono essere riutilizzati o tramite configurazione in components.xml (per casi molto semplici) o tramite estensione.

Seam Application Framework può ridurre la quantità di codice da scrivere nel fornire l'accesso ai database nelle applicazioni web, usando Hibernate o JPA.

Sottolineiamo che il framework è estremamente semplice, solamente una manciata di classi molto semplici, facili da capire e da estendere. La "magia" è in Seam stesso — la stessa magia che si usa nel creare un'applicazione Seam anche senza usare questo framework.

I componenti forniti dal framework Seam possono essere usati secondo due differenti approcci. Il primo modo è installare e configurare un'istanza del componente in components.xml, come si è fatto con altri tipi di componenti Seam predefiniti. Per esempio, il seguente frammento da components.xml installa un componente che esegue semplici operazioni CRUD per un'entità Person:


<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       entity-manager="#{personDatabase}">
    <framework:id
>#{param.personId}</framework:id>
</framework:entity-home
>

Se per i propri gusti tutto questo sembra troppo "programmare in XML", è possibile altrimenti usare l'estensione:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
   @In EntityManager personDatabase;
    
   public EntityManager getEntityManager() {
      return personDatabase; 
   }
    
}

Il secondo approccio ha un vantaggio enorme: si possono facilmente aggiungere funzionalità extra ed eseguire l'override di funzionalità predefinite (le classi del framework sono state attentamente progettate per l'estensione e la personalizzazione).

Un secondo vantaggio è che le classi possono essere bean di sessione stateful EJB, se si vuole. (Non devono per forza esserlo, se si vuole possono essere componenti JavaBean semplici.) Se si sta usando JBoss AS, serve la versione 4.2.2.GA o successive:

@Stateful

@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
    
}

Si possono rendere le proprie classi bean di sessione stateless. In questo caso occorre usare l'injection per fornire il contesto di persistenza, anche se viene chiamato l'entityManager:

@Stateless

@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
    
   @In EntityManager entityManager;
    
   public EntityManager getPersistenceContext() { 
      entityManager; 
   }
    
}

Attualmente Seam fornisce quattro componenti predefiniti: EntityHome e HibernateEntityHome per le operazioni CRUD, assieme a EntityQuery e HibernateEntityQuery le query.

I componenti Home e Query sono scritti per funzionare con scope di sessione, evento o conversazione. Quale scope usare dipende dal modello di stato che si desidera usare nella propria applicazione.

Seam Application Framework funziona solo con contesti di persistenza gestiti da Seam. Di default i componenti cercano un contesto di persistenza chiamato entityManager.

Un oggetto Home fornisce operazioni per la persistenza per una particolare classe entity. Si supponga di avere una classe Person:

@Entity

public class Person {
    @Id private Long id;
    private String firstName;
    private String lastName;
    private Country nationality;
    
    //getters and setters...
}

E' possibile definire un componente personHome o via configurazione:


<framework:entity-home name="personHome" entity-class="eg.Person" />

O tramite estensione:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {}

Un oggetto Home fornisce le seguenti operazioni: persist(), remove(), update() e getInstance(). Prima di chiamare le operazioni remove(), o update(), occorre prima impostare l'identificatore dell'oggetto interessato, usando il metodo setId().

Si può usare un Home direttamente da una pagina JSF, per esempio:


<h1
>Create Person</h1>
<h:form>
    <div
>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form
>

Di solito è più comodo poter fare riferimento a Person semplicemente come person, e quindi si aggiunga una linea a components.xml:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" />

(Se si usa la configurazione.) O si aggiunga un metodo @Factory a PersonHome:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
}

(Se si usa l'estensione.) Questo cambiamento semplifica le pagine JSF come segue:


<h1
>Create Person</h1>
<h:form>
    <div
>First name: <h:inputText value="#{person.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form
>

Bene, queste crea le nuove entry per Person. Esatto, è tutto quanto il codice che serve! Ora se si vuole mostrare, aggiornare e cancellare entry di Person già esistenti nel database, occorre passare l'identificatore delle entry a PersonHome. I parametri di pagina sono un eccezionale modo per farlo:


<pages>
    <page view-id="/editPerson.jsp">
        <param name="personId" value="#{personHome.id}"/>
    </page>
</pages
>

Ora possiamo aggiungere operazioni extra alle pagine JSF:


<h1>
    <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
    <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
</h1>
<h:form>
    <div
>First name: <h:inputText value="#{person.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
        <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
        <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
    </div>
</h:form
>

Quando ci si collega alla pagina senza parametri di richiesta, la pagina verrà mostrata come una pagina "Create Person". Quando si fornisce un valore per il parametro di richiesta personId, sarà una pagina "Edit Person".

Si supponga di dover creare entry di Person con la nazionalità inizializzata. E' possibile farlo semplicemente via configurazione:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       new-instance="#{newPerson}"/>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality"
>#{country}</property>
</component
>

O tramite estensione:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
}

Certamente Country può essere un oggetto gestito da un altro oggetto Home, per esempio, CountryHome.

Per aggiungere altre operazioni sofisticate (gestione dell'associazione, ecc.) si possono aggiungere dei metodi a PersonHome.

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    public void migrate()
    {
        getInstance().setCountry(country);
        update();
    }
    
}

L'oggetto Home solleva un'evento org.jboss.seam.afterTransactionSuccess quando una transazione ha successo (una chiamata a persist(), update() o remove() ha successo). Osservando questo evento si può fare il refresh delle query quando le entità sottostanti cambiano. Se si vuole solo eseguire il refresh quando una particolare entità viene persistita, aggiornata o rimossa, si può osservare l'evento org.jboss.seam.afterTransactionSuccess.<name> (dove <name> è il nome dell'entity).

L'oggetto Home mostra automaticamente i messaggi faces quando un'operazione ha successo. Per personalizzare questi messaggi si può ancora usare la configurazione:


<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome"
                       entity-class="eg.Person"
                       new-instance="#{newPerson}">
    <framework:created-message
>New person #{person.firstName} #{person.lastName} created</framework:created-message>
    <framework:deleted-message
>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>
    <framework:updated-message
>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>
</framework:entity-home>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality"
>#{country}</property>
</component
>

O estensione:

@Name("personHome")

public class PersonHome extends EntityHome<Person
> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    protected String getCreatedMessage() { return createValueExpression("New person #{person.firstName} #{person.lastName} created"); }
    protected String getUpdatedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} updated"); }
    protected String getDeletedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} deleted"); }
    
}

Ma il modo migliore per specificare i messaggi è metterli in un resource bundle noto a Seam (di default, il nome del bundle è messages).

Person_created=New person #{person.firstName} #{person.lastName} created
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated

Questo abilita l'internazionalizzazione e mantiene il codice e la configurazione puliti dagli elementi di presentazione.

Il passo finale è aggiungere alla pagina la funzionalità di validazione, usando <s:validateAll> e <s:decorate>, ma verrà lasciato al lettore come esercizio.

Se occorre una lista di tutte le istanze Person nel database, si può usare un oggetto Query. Per esempio:


<framework:entity-query name="people" 
                        ejbql="select p from Person p"/>

E' possibile usarlo da una pagina JSF:


<h1
>List of people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable
>

Probabilmente occorre un supporto per la paginazione:


<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20"/>

Si userà un parametro di pagina per determinare la pagina da mostrare:


<pages>
    <page view-id="/searchPerson.jsp">
        <param name="firstResult" value="#{people.firstResult}"/>
    </page>
</pages
>

Il codice JSF per il controllo della paginazione è un pò verboso, ma gestibile:


<h1
>Search for people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
    <f:param name="firstResult" value="0"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
    <f:param name="firstResult" value="#{people.previousFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
    <f:param name="firstResult" value="#{people.nextFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
    <f:param name="firstResult" value="#{people.lastFirstResult}"/>
</s:link
>

Le schermate di ricerca consentono all'utente di inserire una serie di criteri di ricerca per restringere la lista dei risultati restituiti. L'oggetto Query consente di specificare delle "restrizioni" opzionali per supportare quest'importante caso d'uso:


<component name="examplePerson" class="Person"/>
        
<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20">
    <framework:restrictions>
        <value
>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
        <value
>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
    </framework:restrictions>
</framework:entity-query
>

Si noti l'uso di un oggetto "esempio".


<h1
>Search for people</h1>
<h:form>
    <div
>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
    <div
>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
    <div
><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>

<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable
>

Per fare il refresh della query qualora cambino le entità sottostanti, si può osservare l'evento org.jboss.seam.afterTransactionSuccess:


<event type="org.jboss.seam.afterTransactionSuccess">
    <action execute="#{people.refresh}" />
</event
>

O semplicemente per fare il refresh della query quando l'entity person viene persistita, aggiornata o rimossa attraverso PersonHome:


<event type="org.jboss.seam.afterTransactionSuccess.Person">
    <action execute="#{people.refresh}" />
    </event
>

Sfortunatamente gli oggetti Query non funzionano bene con query join fetch - non è consigliato l'uso della paginazione con queste query, ed occorrerà implementare un proprio metodo di calcolo del numero totale di risultati (con l'override di getCountEjbql()).

Gli esempi in questa sezione hanno mostrato tutti il riuso tramite configurazione. Comunque il riuso tramite estensione è ugualmente possibile per gli oggetti Query.

Seam facilita le chiamate alle regole di JBoss Rules (Drools) dai componenti Seam o dalle definizioni di processo jBPM.

Il primo passo è creare un'istanza di org.drools.RuleBase disponibile in una variabile del contesto di Seam. Per i test Seam fornisce un componente interno che compila un set statico di regole dal classpath. Si può installare questo componente tramite components.xml:


<drools:rule-base name="policyPricingRules">
    <drools:rule-files>
        <value
>policyPricingRules.drl</value>
    </drools:rule-files>
</drools:rule-base
>

Questo componente compila le regole da un set di file .drl e mette in cache un'istanza di org.drools.RuleBase nel contesto APPLICATION di Seam. Notare che è abbastanza probabile che in un'applicazione guidata dalle regole occorra installare altre basi di regole.

Se si vuole utilizzare una Drool DSL, devi specificare la definizione DSL:


<drools:rule-base name="policyPricingRules" dsl-file="policyPricing.dsl">
    <drools:rule-files>
        <value
>policyPricingRules.drl</value>
    </drools:rule-files>
</drools:rule-base
>

Nella maggior parte delle applicazioni guidate dalle regole, le regole devono essere dinamicamente deployabili, e quindi un'applicazione in produzione dovrà usare un Drools RuleAgent per gestire la RuleBase. Il RuleAgent può connettersi al server di regole Drool (BRMS) od eseguire l'hot deploy dei pacchetti di regole dal repository locale. La RuleBase gestita dal RulesAgen è configurabile in components.xml:


<drools:rule-agent name="insuranceRules" 
                    configurationFile="/WEB-INF/deployedrules.properties" />

Il file delle proprietà contiene proprietà specifiche per RulesAgent. Ecco un file di configurazione d'esempio proveniente dalla distribuzione Drools.

newInstance=true
url=http://localhost:8080/drools-jbrms/org.drools.brms.JBRMS/package/org.acme.insurance/fmeyer
localCacheDir=/Users/fernandomeyer/projects/jbossrules/drools-examples/drools-examples-brms/cache
poll=30
name=insuranceconfig

E' anche possibile configurare le opzioni derettamente sul componente, bypassando il file di configurazione.


<drools:rule-agent name="insuranceRules"
   url="http://localhost:8080/drools-jbrms/org.drools.brms.JBRMS/package/org.acme.insurance/fmeyer"
   local-cache-dir="/Users/fernandomeyer/projects/jbossrules/drools-examples/drools-examples-brms/cache"
   poll="30"
   configuration-name="insuranceconfig" />

Successivamente occorre rendere disponibile ad ogni conversazione un'istanza di org.drools.WorkingMemory. (Ogni WorkingMemory accumula fatti relativi alla conversazione corrente.)


<drools:managed-working-memory name="policyPricingWorkingMemory" auto-create="true" rule-base="#{policyPricingRules}"/>

Notare che è stato dato a policyPricingWorkingMemory un riferimento alla base di regole tramite la proprietà di configurazione ruleBase.

Si può anche consentire alla base di regole di agire come action handler di jBPM, decision handler, o assignment handler — sia in una definizione di pageflow sia in un processo di business.


<decision name="approval">
         
    <handler class="org.jboss.seam.drools.DroolsDecisionHandler">
        <workingMemoryName
>orderApprovalRulesWorkingMemory</workingMemoryName>
        <assertObjects>
            <element
>#{customer}</element>
            <element
>#{order}</element>
            <element
>#{order.lineItems}</element>
        </assertObjects>
    </handler>
    
    <transition name="approved" to="ship">
        <action class="org.jboss.seam.drools.DroolsActionHandler">
            <workingMemoryName
>shippingRulesWorkingMemory</workingMemoryName>
            <assertObjects>
                <element
>#{customer}</element>
                <element
>#{order}</element>
                <element
>#{order.lineItems}</element>
            </assertObjects>
        </action>
    </transition>
    
    <transition name="rejected" to="cancelled"/>
    
</decision
>

L'elemento <assertObjects> specifica le espressioni EL che restituiscono un oggetto od una collezione di oggetti da asserire come fatti nella WorkingMemory.

Esiste anche il supporto per l'uso di Drools per le assegnazioni dei task in jBPM:


<task-node name="review">
    <task name="review" description="Review Order">
        <assignment handler="org.jboss.seam.drools.DroolsAssignmentHandler">
            <workingMemoryName
>orderApprovalRulesWorkingMemory</workingMemoryName>
            <assertObjects>
                <element
>#{actor}</element>
                <element
>#{customer}</element>
                <element
>#{order}</element>
                <element
>#{order.lineItems}</element>
            </assertObjects>
        </assignment>
    </task>
    <transition name="rejected" to="cancelled"/>
    <transition name="approved" to="approved"/>
</task-node
>

Alcuni oggetti sono consultabili dalle regole come Drools globals, chiamate Assignable in jBPM, come assignable ed oggetto Decision in Seam, come decision. Le regole che gestiscono le decisioni dovrebbero chiamare decision.setOutcome("result") per determinare il risultato della decisione. Le regole che eseguono assegnazioni dovrebbero impostare l'actor id usando Assignable.

package org.jboss.seam.examples.shop

import org.jboss.seam.drools.Decision

global Decision decision

rule "Approve Order For Loyal Customer"
  when
    Customer( loyaltyStatus == "GOLD" )
    Order( totalAmount <= 10000 )
  then
    decision.setOutcome("approved");
end
package org.jboss.seam.examples.shop

import org.jbpm.taskmgmt.exe.Assignable

global Assignable assignable

rule "Assign Review For Small Order"
  when
    Order( totalAmount <= 100 )
  then
    assignable.setPooledActors( new String[] {"reviewers"} );
end

Attenzione

Seam viene fornito con dipendenze Drools sufficienti per implementare alcune regole semplici. Per aggiungere ulteriori funzionalità a Drools occorre scaricare la distribuzione completa ed aggiungere le dipendenze necessarie.

Suggerimento

Drools viene rilasciato con MVEL compilato per Java 1.4, che è compatibile con Java 1.4, Java 5 e Java 6. E' possibile cambiare il jar MVEL con quello compilato per la propria versione di Java.

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

Le caratteristiche relative all'autenticazione nella gestione della sicurezza di Seam sono costruite su JAAS (Java Authentication and Authorization Service, servizio di autenticazione e autorizzazione Java) e, come tali, forniscono una API robusta e altamente configurabile per gestire l'autentifica degli utenti. Comunque, per requisiti di autentifica meno complessi, Seam offre un metodo di autentifica molto semplificato che nasconde la complessità di JAAS.

Il metodo di autenticazione semplificato fornito da Seam usa un modulo di login JAAS già fatto, SeamLoginModule, il quale delega l'autentifica ad uno dei componenti dell'applicazione. Questo modulo di login è già configurato all'interno di Seam come parte dei criteri di gestione di default e in quanto tale non richiede alcun file di configurazione aggiuntivo. Esso consente di scrivere un metodo di autentifica usando le classi entità che sono fornite dall'applicazione o, in alternativa, di esegure l'autentifica con qualche altro fornitore di terze parti. Per configurare questa forma semplificata di autentifica è richiesto di di configurare il componente Identity in components.xml:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:security="http://jboss.com/products/seam/security"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd">

    <security:identity authenticate-method="#{authenticator.authenticate}"/>

</components
>

L'espressione EL #{authenticator.authenticate} è la definizione di un metodo tramite la quale si indica che il metodo authenticate del componente authenticator verrà usato per autenticare l'utente.

La proprietà authenticate-method specificata per identity in components.xml specifica quale metodo sarà usato dal SeamLoginModule per autenticare l'utente. Questo metodo non ha parametri ed è previsto che restituisca un boolean, il quale indica se l'autenticazione ha avuto successo o no. Il nome utente e la password possono essere ottenuti da Credentials.getUsername() e Credentials.getPassword() rispettivamente (è possibile avere un riferimento al componente credentials tramite Identiy.instance().getCredentials()). Tutti i ruoli di cui l'utente è membro devono essere assegnati usando Identity.addRole(). Ecco un esempio completo di un metodo di autentifica all'interno di un componente POJO:

@Name("authenticator")

public class Authenticator {
   @In EntityManager entityManager;
   @In Credentials credentials;
   @In Identity identity;
   public boolean authenticate() {
      try {
         User user = (User) entityManager.createQuery(
            "from User where username = :username and password = :password")
            .setParameter("username", credentials.getUsername())
            .setParameter("password", credentials.getPassword())
            .getSingleResult();
         if (user.getRoles() != null) {
            for (UserRole mr : user.getRoles())
               identity.addRole(mr.getName());
         }
         return true;
      }
      catch (NoResultException ex) {
         return false;
      }
   }
}

Nell'esempio precedente sia User che UserRole sono entity bean specifici dell'applicazione. Il parametro roles è popolato con i ruoli di cui l'utente è membro, che devono essere aggiunti alla Set come valori stringa, ad esempio "amministratore", "utente". In questo caso, se il record dell'utente non viene trovato e una NoResultException viene lanciata, il metodo di autenticazione restituisce false per indicare che l'autentifica è fallita.

La sicurezza di Seam gestisce lo stesso tipo di funzionalità "Ricordami su questo computer" che si incontra comunemente in molte applicazioni basate sull'interfaccia web. In effetti essa è gestita in due diverse "varietà" o modalità. La prima modalità consente al nome utente di essere memorizzato nel browser dell'utente come un cookie e lascia che sia il browser ad inserire la password (molti browser moderni sono in grado di ricordare le password).

La seconda modalità gestisce la memorizzazione di un identificativo unico in un cookie e consente all'utente di autenticarsi automaticamente non appena ritorna sul sito, senza dover fornire una password.

Avvertimento

L'autenticazione automatica tramite un cookie persistente memorizzato sulla macchina client è pericolosa. Benché sia conveniente per gli utenti, qualsiasi debolezza nella sicurezza che consenta un cross-site scripting nel sito avrebbe effetti drammaticamente più gravi del solito. Senza il cookie di autentifica, il solo cookie che un malintenzionato può prelevare tramite un attacco XSS è il cookie della sessione corrente dell'utente. Ciò significa che l'attacco funziona solo quando l'utente ha una sessione aperta, ovvero per un intervallo di tempo limitato. Al contrario è molto più allettante e pericoloso se un malintenzionato ha la possibilità di prelevare il cookie relativo alla funzione "Ricordami su questo computer", il quale gli consentirebbe di accedere senza autentifica ogni volta che vuole. Notare che questo dipende anche da quanto è efficace la protezione del sito dagli attacchi XSS. Sta a chi scrive l'applicazione fare in modo che il sito sia sicuro al 100% dagli attacchi XSS, un obiettivo non banale per qualsiasi sito che consente di rappresentare sulle pagine un contenuto scritto dagli utenti.

I produttori di browser hanno riconosciuto questo problema e hanno introdotto la funzione "Ricorda la password", oggi disponibile su quasi tutti i browser. In questo caso il browser ricorda il nome utente e la password per un certo sito e dominio, e riempie la form di accesso automaticamente quando non è attiva una sessione con il sito. Se poi il progettista del sito offre una scorciatoia da tastiera conveniente, questo approccio è quasi altrettanto immediato come il cookie "Ricordami su questo computer", ma molto più sicuro. Alcuni browser (ad esempio Safari su OS X) memorizzano addirittura i dati delle form di accesso nel portachiavi cifrato di sistema. Oppure, in un ambiente di rete, il portachiavi può essere trasportato dall'utente (tra il portatile e il desktop, ad esempio), mentre i cookie del browser di solito non sono sincronizzati.

In definitiva: benché tutti lo stiano facendo, il cookie "Ricordami su questo computer" con l'autenticazione automatica è un cattiva pratica e non dovrebbe essere usata. I cookie che "ricordano" solo il nome dell'utente e riempiono la form di accesso con quel nome utente per praticità, non comportano rischi.

Per abilitare la funzione "Ricordami su questo computer" nella modalità di default (quella sicura, con il solo nome utente) non è richiesta alcuna speciale configurazione. Basta collegare un checkbox "Ricordami su questo computer" a rememberMe.enabled nella form di accesso, come nel seguente esempio:


  <div>
    <h:outputLabel for="name" value="Nome utente"/>
    <h:inputText id="name" value="#{credentials.username}"/>
  </div>
  
  <div>
    <h:outputLabel for="password" value="Password"/>
    <h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
  </div
>      
  
  <div class="loginRow">
    <h:outputLabel for="rememberMe" value="Ricordami su questo computer"/>
    <h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>
  </div
>

Per usare la modalità automatica, attraverso il token, della funzione "Ricordami su questo computer", occorre prima configurare la memorizzazione del token. Nello scenario più comune (gestito da Seam) questi token di autenticazione vengono memorizzati nel database, comunque è possibile implementare la propria memorizzazione dei token implementando l'interfaccia org.jboss.seam.security.TokenStore. In questo paragrafo si suppone che per la memorizzazione dei token in una tabella del database si stia usando l'implementazione fornita con Seam JpaTokenStore.

Il primo passo consiste nel creare una nuova entità che conterrà i token. Il seguente esempio mostra una possibile struttura che può essere usata:

@Entity

public class AuthenticationToken implements Serializable {  
   private Integer tokenId;
   private String username;
   private String value;
   
   @Id @GeneratedValue
   public Integer getTokenId() {
      return tokenId;
   }
   
   public void setTokenId(Integer tokenId) {
      this.tokenId = tokenId;
   }
   
   @TokenUsername
   public String getUsername() {
      return username;
   }
   
   public void setUsername(String username) {
      this.username = username;
   }
   
   @TokenValue
   public String getValue() {
      return value;
   }
   
   public void setValue(String value) {
      this.value = value;
   }
}

Come si può vedere dal listato, vengono usate un paio di annotazioni speciali, @TokenUsername e @TokenValue, per configurare le proprietà token e nome utente dell'entità. Queste annotazioni sono richieste per l'entità che conterrà i token di autenticazione.

Il passo successivo consiste nel configurare il JpaTokenStore per usare questo entity bean per memorizzare e recuperare i token di autenticazione. Ciò viene fatto in components.xml specificando l'attributo token-class.



  <security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>        
        

Una volta fatto questo, l'ultima cosa da fare è configurare anche il componente RememberMe in components.xml. La sua proprietà mode dovrà essere impostata a autoLogin:


  <security:remember-me mode="autoLogin"/>        
        

Questo è tutto ciò che è necessario. L'autenticazione automatica degli utenti avverrà quando torneranno a visitare il sito (purché abbiano impostato il checkbox "Ricordami su questo computer").

Per essere sicuri che gli utenti siano autenticati automaticamente quando tornano sul sito, il seguente codice deve essere posizionato in components.xml:


  <event type="org.jboss.seam.security.notLoggedIn">
    <action execute="#{redirect.captureCurrentView}"/>
    <action execute="#{identity.tryLogin()}"/>
  </event>
  <event type="org.jboss.seam.security.loginSuccessful">
    <action execute="#{redirect.returnToCapturedView}"/>
  </event
>

Per prevenire il fatto che gli utenti ricevano la pagina di errore di default in risposta ad un errore di sicurezza, si raccomanda che in pages.xml sia configurata una redirezione degli errori di sicurezza ad una pagina più "carina". I due principali tipi di eccezione lanciati dalle API della sicurezza sono:

Nel caso della NotLoggedInException, si raccomanda che l'utente venga rediretto o sulla pagina di accesso o su quella di registrazione, così che possa accedere. Per una AuthorizationException, può essere utile redirigere l'utente su una pagina di errore. Ecco un esempio di un pages.xml che redirige entrambe queste eccezioni:


<pages>

    ...

    <exception class="org.jboss.seam.security.NotLoggedInException">
        <redirect view-id="/login.xhtml">
            <message
>Per eseguire questa operazione devi prima eseguire l'accesso</message>
        </redirect>
    </exception>

    <exception class="org.jboss.seam.security.AuthorizationException">
        <end-conversation/>
        <redirect view-id="/security_error.xhtml">
            <message
>Non disponi dei privilegi di sicurezza necessari per eseguire questa operazione.</message>
        </redirect>
    </exception>

</pages
>

La maggior parte delle applicazioni web richiede una gestione più sofisticata della redirezione sulla pagina di accesso, perciò Seam include alcune funzionalità speciali per gestire questo problema:

E' possibile chiedere a Seam di redirigere l'utente su una pagina di accesso quando un utente non autenticato tenta di accedere ad una particolare view (o ad una view il cui id corrisponda ad una wildcard), nel modo seguente:


<pages login-view-id="/login.xhtml">

    <page view-id="/members/*" login-required="true"/>

    ...

</pages
>

Dopo che l'utente ha eseguito l'accesso, lo si vorrà rimandare automaticamente indietro da dove è venuto, così che potrà riprovare ad eseguire l'azione che richiedeva l'accesso. Se si aggiungono i seguenti listener in components.xml, i tentativi di accesso ad una view protetta eseguiti quando non si è fatto l'accesso verranno ricordati così, dopo che l'utente ha eseguito l'accesso, può essere rediretto alla view che aveva originariamente richiesto, compresi tutti i parametri di pagina che esistevano nella richiesta originale.


<event type="org.jboss.seam.security.notLoggedIn">
    <action execute="#{redirect.captureCurrentView}"/>
</event>

<event type="org.jboss.seam.security.postAuthenticate">
    <action execute="#{redirect.returnToCapturedView}"/>
</event
>

Notare che la redirezione dopo l'accesso è implementata con un meccanismo con visibilità sulla conversazione, perciò occorre evitare di terminare la conversazione nel metodo authenticate().

Benché l'uso non sia raccomandato a meno che non sia assolutamente necessario, Seam fornisce gli strumenti per l'autenticazione in HTTP sia con metodo Basic che Digest (RFC 2617). Per usare entrambe le forme di autentifica, occorre abilitare il componente authentication-filter in components.xml:



  <web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
      

Per abilitare il filtro per l'autenticazione Basic impostare auth-type a basic, oppure per l'autentifica Digest, impostarlo a digest. Se si usa l'autentifica Digest, occorre impostare anche un valore per key e realm:



  <web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="La mia Applicazione"/>
      

key può essere un qualunque valore stringa. realm è il nome del dominio di autenticazione che viene presentato all'utente quando si autentica.

La gestione delle identità fornisce un'API standard per la gestione degli utenti e dei ruoli di una applicazione Seam, a prescindere da quale dispositivo di memorizzazione delle identità è usato internamente (database, LDAP, ecc). Al centro delle API per la gestione delle identità c'è il componente identityManager, il quale fornisce tutti i metodi per creare, modificare e cancellare utenti, concedere e revocare ruoli, cambiare le password, abilitare e disabilitare gli utenti, autenticare gli utenti ed elencare utenti e ruoli.

Prima di essere usato, identityManager deve essere configurato con uno o più IdentityStore. Questi componenti fanno il vero lavoro di interagire con il fornitore di sicurezza sottostante, sia che si tratti di un database, di un server LDAP o di qualcos'altro.

Il componente identityManager consente di separare i dispositivi di memorizzazione configurati per le operazioni di autenticazione e di autorizzazione. Ciò significa che è possibile autenticare gli utenti tramite un dispositivo di memorizzazione, ad esempio una directory LDAP, e poi avere i loro ruoli caricati da un altro dispositivo di memorizzazione, come un database relazionale.

Seam fornisce due implementazioni IdentityStore già pronte. JpaIdentityStore usa un database relazionale per memorizzare le informazioni su utenti e ruoli ed è il dispositivo di memorizzazione di identità di default che viene usato se non viene configurato niente in modo esplicito nel componente identityManager. L'altra implementazione fornita è LdapIdentityStore, che usa una directory LDAP per memorizzare utenti e ruoli.

Ci sono due proprietà configurabili per il componente identityManager, identityStore e roleIdentityStore. Il valore di queste proprietà deve essere un'espressione EL che fa riferimento ad un componente Seam che implementa l'interfaccia IdentityStore. Come già detto, se viene lasciato non configurato allora JpaIdentityStore viene assunto come default. Se è configurata solamente la proprietà identityStore allora lo stesso valore verrà usato anche per roleIdentityStore. Ad esempio la seguente voce in components.xml configura identityManager per usare un LdapIdentityStore sia per le operazioni relative agli utenti che per quelle relative ai ruoli:


      
  <security:identity-manager identity-store="#{ldapIdentityStore}"/>
      

Il seguente esempio configura identityManager per usare un LdapIdentityStore per le operazioni relative agli utenti e un JpaIdentityStore per le operazioni relative ai ruoli.


      
  <security:identity-manager 
    identity-store="#{ldapIdentityStore}" 
    role-identity-store="#{jpaIdentityStore}"/>
      

Il paragrafo seguente spiega con maggiore dettaglio entrambe queste implementazioni di IdentityStore.

Questa memorizzazione delle identità consente agli utenti e ai ruoli di essere memorizzati in un database relazionale. E' progettato per essere il meno restrittivo possibile riguardo allo schema del database, consentendo una grande flessibilità per la struttura delle tabelle sottostanti. Questo si ottiene tramite l'uso di uno speciale insieme di annotazioni, consentendo agli entity bean di essere configurati per memorizzare utenti e ruoli.

Come già menzionato, un apposito insieme di annotazioni viene usato per configurare gli entity bean per la memorizzazione di utenti e ruoli. La seguente tabella elenca ciascuna di queste annotazioni e la relativa descrizione.



Come detto precedentemente, JpaIdentityStore è progettato per essere il più possibile flessibile per ciò che riguarda lo schema del database delle tabelle degli utenti e dei ruoli. Questo paragrafo esamina una serie di possibili schemi di database che possono essere usati per memorizzare i record degli utenti e dei ruoli.

In questo esempio minimale una tabella di utenti e una di ruoli sono legate tramite una relazione molti-a-molti che utilizza una tabella di collegamento chiamata UserRoles.

@Entity

public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role
> roles;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
    joinColumns = @JoinColumn(name = "UserId"),
    inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role
> getRoles() { return roles; }
  public void setRoles(Set<Role
> roles) { this.roles = roles; }
}
@Entity
public class Role {
  private Integer roleId;
  private String rolename;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
}

Questo esempio è costruito a partire dall'esempio minimo includendo tutti i campi opzionali e consentendo ai ruoli di appartenere ai gruppi.

@Entity

public class User {
  private Integer userId;
  private String username;
  private String passwordHash;
  private Set<Role
> roles;
  private String firstname;
  private String lastname;
  private boolean enabled;
  
  @Id @GeneratedValue
  public Integer getUserId() { return userId; }
  public void setUserId(Integer userId) { this.userId = userId; }
  
  @UserPrincipal
  public String getUsername() { return username; }
  public void setUsername(String username) { this.username = username; }
  
  @UserPassword(hash = "md5")
  public String getPasswordHash() { return passwordHash; }
  public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
  
  @UserFirstName
  public String getFirstname() { return firstname; }
  public void setFirstname(String firstname) { this.firstname = firstname; }
  
  @UserLastName
  public String getLastname() { return lastname; }
  public void setLastname(String lastname) { this.lastname = lastname; }
  
  @UserEnabled
  public boolean isEnabled() { return enabled; }
  public void setEnabled(boolean enabled) { this.enabled = enabled; }
  
  @UserRoles
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "UserRoles", 
    joinColumns = @JoinColumn(name = "UserId"),
    inverseJoinColumns = @JoinColumn(name = "RoleId"))
  public Set<Role
> getRoles() { return roles; }
  public void setRoles(Set<Role
> roles) { this.roles = roles; }
}
@Entity
public class Role {
  private Integer roleId;
  private String rolename;
  private boolean conditional;
  
  @Id @Generated
  public Integer getRoleId() { return roleId; }
  public void setRoleId(Integer roleId) { this.roleId = roleId; }
  
  @RoleName
  public String getRolename() { return rolename; }
  public void setRolename(String rolename) { this.rolename = rolename; }
  
  @RoleConditional
  public boolean isConditional() { return conditional; }
  public void setConditional(boolean conditional) { this.conditional = conditional; }
  
  @RoleGroups
  @ManyToMany(targetEntity = Role.class)
  @JoinTable(name = "RoleGroups", 
    joinColumns = @JoinColumn(name = "RoleId"),
    inverseJoinColumns = @JoinColumn(name = "GroupId"))
  public Set<Role
> getGroups() { return groups; }
  public void setGroups(Set<Role
> groups) { this.groups = groups; }  
  
}

Quando si usa JpaIdentityStore come implementazione della memorizzazione delle identità con IdentityManager, alcuni eventi vengono lanciati in corrispondenza dell'invocazione di certi metodi di IdentityManager.

Questa implementazione della memorizzazione delle identità è progettata per funzionare quando le informazioni sugli utenti sono memorizzate in una directory LDAP. E' molto configurabile consentendo una grande flessibilità sul modo in cui utenti e ruoli sono memorizzati nella directory. ll seguente paragrafo descrive le opzioni di configurazione per questa implementazione e fornisce alcuni esempi di configurazione.

La seguente tabella descrive le proprietà disponibili che possono essere configurate in components.xml per LdapIdentityStore.

Tabella 15.3. Proprietà di configurazione di LdapIdentityStore

Proprietà

Valore di default

Descrizione

server-address

localhost

L'indirizzo del server LDAP

server-port

389

Il numero di porta su cui il server LDAP è in ascolto.

user-context-DN

ou=Person,dc=acme,dc=com

Il Distinguished Name (DN) del contesto contenente le informazioni sugli utenti.

user-DN-prefix

uid=

Questo valore è usato come prefisso anteponendolo al nome utente durante la ricerca delle informazioni sull'utente.

user-DN-suffix

,ou=Person,dc=acme,dc=com

Questo valore è aggiunto alla fine del nome utente per ricercare le informazioni sull'utente.

role-context-DN

ou=Role,dc=acme,dc=com

Il DN del contesto contenente le informazioni sui ruoli.

role-DN-prefix

cn=

Questo valore è usato come prefisso anteponendolo al nome del ruolo per formare il DN nella ricerca delle informazioni sul ruolo.

role-DN-suffix

,ou=Roles,dc=acme,dc=com

Questo valore è aggiunto al nome del ruolo per formare il DN nella ricerca delle informazioni sul ruolo.

bind-DN

cn=Manager,dc=acme,dc=com

Questo è il contesto usato per collegare il server LDAP.

bind-credentials

secret

Queste sono le credenziali (la password) usate per collegare il server LDAP.

user-role-attribute

roles

Questo è il nome dell'attributo sulle informazioni dell'utente che contiene la lista dei ruoli di cui l'utente è membro.

role-attribute-is-DN

true

Questa proprietà boolean indica se l'attributo del ruolo nelle informazioni dell'utente è esso stesso un Distinguished Name.

user-name-attribute

uid

Indica quale attributo delle informazioni sull'utente contiene il nome utente.

user-password-attribute

userPassword

Indica quale attributo nelle informazioni sull'utente contiene la password dell'utente.

first-name-attribute

null

Indica quale attributo nelle informazioni sull'utente contiene il nome proprio dell'utente.

last-name-attribute

sn

Indica quale attributo nelle informazioni sull'utente contiene il cognome dell'utente.

full-name-attribute

cn

Indica quale attributo nelle informazioni sull'utente contiene il nome per esteso dell'utente.

enabled-attribute

null

Indica quale attributo nelle informazioni sull'utente determina se l'utente è abilitato.

role-name-attribute

cn

Indica quale attributo nell'informazioni sul ruolo contiene il nome del ruolo.

object-class-attribute

objectClass

Indica quale attributo determina la classe di un oggetto nella directory.

role-object-classes

organizationalRole

Un elenco di classi di oggetto con cui devono essere create le informazioni su un nuovo ruolo.

user-object-classes

person.uidObject

Un elenco di classi di oggetto con cui devono essere create le informazioni su un nuovo utente.


IdentityManager può essere utilizzato sia iniettandolo in un componente Seam come di seguito:

  @In IdentityManager identityManager;

sia accedendo ad esso tramite il suo metodo statico instance():

  IdentityManager identityManager = IdentityManager.instance();

La seguente tabella descrive i metodi di API per IdentityManager:

Tabella 15.4. API per la gestione delle identità

Metodo

Valore restituito

Descrizione

createUser(String name, String password)

boolean

Crea un nuovo utente con il nome e la password specificate. Restituisce true se l'operazione si è conclusa con successo, altrimenti false.

deleteUser(String name)

boolean

Elimina le informazioni dell'utente con il nome specificato. Restituisce true se l'operazione si è conclusa con successo, oppure false.

createRole(String role)

boolean

Crea un nuovo ruolo con il nome specificato. Restituisce true se l'operazione si è conclusa con successo, oppure false.

deleteRole(String name)

boolean

Elimina il ruolo con il nome specificato. Restituisce true se l'operazione si è conclusa con successo, oppure false.

enableUser(String name)

boolean

Abilita l'utente con il nome specificato. Gli utenti che non sono abilitati non sono in grado di autenticarsi. Restituisce true se l'operazione si è conclusa con successo, oppure false.

disableUser(String name)

boolean

Disabilita l'utente con il nome specificato. Restituisce true se l'operazione si è conclusa con successo, oppure false.

changePassword(String name, String password)

boolean

Modifica la password dell'utente con il nome specificato. Restituisce true se l'operazione si è conclusa con successo, oppure false.

isUserEnabled(String name)

boolean

Restituisce true se l'utente specificato è abilitato, oppure false se non lo è.

grantRole(String name, String role)

boolean

Concede il ruolo specificato all'utente o al ruolo. Il ruolo deve già esistere per essere concesso. Restituisce true se il ruolo è stato concesso, oppure false se era già stato concesso all'utente.

revokeRole(String name, String role)

boolean

Revoca il ruolo specificato all'utente o al ruolo. Restituisce true se l'utente specificato era membro del ruolo e questo è stato revocato con successo, oppure false se l'utente non è un membro del ruolo.

userExists(String name)

boolean

Restituisce true se l'utente specificato esiste, oppure false se non esiste.

listUsers()

listUsers(String filter)

Restituisce una lista di tutti i nomi utente in ordine alfanumerico.

listUsers(String filter)

listUsers(String filter)

Restituisce una lista di tutti i nomi utente filtrata secondo il parametro di filtro specificato e in ordine alfanumerico.

listRoles()

listUsers(String filter)

Restituisce una lista di tutti i nomi dei ruoli.

getGrantedRoles(String name)

listUsers(String filter)

Restituisce una lista dei nomi di tutti i ruoli esplicitamente concessi all'utente con il nome specificato.

getImpliedRoles(String name)

listUsers(String filter)

Restituisce la lista dei nomi di tutti i ruoli implicitamente concessi all'utente specificato. I ruoli implicitamente concessi includono quelli che non sono concessi direttamente all'utente, ma sono concessi ai ruoli di cui l'utente è membro. Ad esempio, se il ruolo admin è un membro del ruolo user e un utente è membro del ruolo admin, allora i ruoli impliciti per l'utente sono sia admin che user.

authenticate(String name, String password)

boolean

Autenticazione il nome utente e la password specificati usando l'Identity Store configurato. Restituisce true se conclude con successo, oppure false se l'autentifica fallisce. Il successo dell'autenticazione non implica niente oltre al valore restituito dal metodo. Non cambia lo stato del componente Identity. Per eseguire un vero e proprio login deve essere invece usato il metodo Identity.login().

addRoleToGroup(String role, String group)

boolean

Aggiunge il ruolo specificato come membro del gruppo specificato. Restituisce true se l'operazione va a buon fine.

removeRoleFromGroup(String role, String group)

boolean

Rimuove il ruolo specificato dal gruppo specificato. Restituisce true se l'operazione va a buon fine.

listRoles()

listUsers(String filter)

Elenca i nomi di tutti i ruoli.


L'uso delle API per la gestione delle identità richiede che l'utente chiamante abbia le autorizzazioni appropriate per invocare i suoi metodi. La seguente tabella descrive i permessi richiesti per ciascuno dei metodi in IdentityManager. Gli oggetti dei permessi elencati qui sotto sono valori stringa.


Il seguente listato fornisce un esempio con un insieme di regole di sicurezza che concedono al ruolo admin l'accesso a tutti i metodi relativi alla gestione delle identità:

rule ManageUsers
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.user", granted == false)
  Role(name == "admin")
then
  check.grant();
end

rule ManageRoles
  no-loop
  activation-group "permissions"
when
  check: PermissionCheck(name == "seam.role", granted == false)
  Role(name == "admin")
then
  check.grant();
end

Ci sono diversi meccanismi di autorizzazione forniti dalle API di sicurezza di Seam per rendere sicuro l'accesso ai componenti, ai metodi dei componenti e alle pagine. Questo paragrafo descrive ognuno di essi. Un aspetto importante da notare è che qualora si voglia utilizzare una delle caratteristiche avanzate (come i permessi basati sulle regole) il components.xml potrebbe dover essere configurato per gestirle. Vedi il paragrafo Configurazione più sopra.

La sicurezza di Seam è costruita intorno alla premessa per cui agli utenti vengono concessi ruoli e/o permessi, consentendo loro di eseguire operazioni che non sarebbero altrimenti permesse agli utenti senza i necessari privilegi di sicurezza. Ognuno dei meccanismi di autorizzazione forniti dalle API di sicurezza di Seam è costruito intorno a questo concetto principale di ruoli e permessi, con un framework espandibile che fornisce più modi per rendere sicure le risorse di un'applicazione.

Iniziamo ad esaminare la forma più semplice di autorizzazione, la sicurezza dei componenti, inziando con l'annotazione @Restrict.

I componenti Seam possono essere resi sicuri sia a livello di metodo che a livello di classe usando l'annotazione @Restrict. Se sia un metodo che la classe in cui è dichiarato sono annotati con @Restrict, la restrizione sul metodo ha la precedenza (e la restrizione sulla classe non si applica). Se nell'invocazione di un metodo fallisce il controllo di sicurezza, viene lanciata un'eccezione come definito nel contratto di Identity.checkRestriction() (vedi Restrizioni in linea). Una @Restrict solo sulla classe del componente stesso è equivalente ad aggiungere @Restrict a ciascuno dei suoi metodi.

Una @Restrict vuota implica un controllo di permesso per nomeComponente:nomeMetodo. Prendiamo ad esempio il seguente metodo di un componente:

@Name("account")

public class AccountAction {
    @Restrict public void delete() {
      ...
    }
}

In questo esempio il permesso richiesto per chiamare il metodo delete() è account:delete. L'equivalente di ciò sarebbe stato scrivere @Restrict("#{s:hasPermission('account','delete')}"). Ora vediamo un altro esempio:

@Restrict @Name("account")

public class AccountAction {
    public void insert() {
      ...
    }
    @Restrict("#{s:hasRole('admin')}")
    public void delete() {
      ...
    }
}

Questa volta la classe stessa del componente è annotata con @Restrict. Ciò significa che tutti i metodi senza una annotazione @Restrict a sovrascrivere, richiedono un controllo implicito di permesso. Nel caso di questo esempio il metodo insert() richiede un permesso per account:insert, mentre il metodo delete() richiede che l'utente sia membro del ruolo admin.

Prima di andare avanti, esaminiamo l'espressione #{s:hasRole()} vista nell'esempio precedente. Sia s:hasRole() che s:hasPermission sono funzioni EL, le quali delegano ai metodi con i nomi corrispondenti nella classe Identity. Queste funzioni possono essere usate all'interno di una espressione EL in tutte le API di sicurezza.

Essendo un'espressione EL, il valore dell'annotazione @Restrict può fare riferimento a qualunque oggetto che sia presente in un contesto Seam. Ciò è estremamente utile quando si eseguono i controlli sui permessi per una specifica istanza di un oggetto. Ad esempio:

@Name("account")

public class AccountAction {
    @In Account selectedAccount;
    @Restrict("#{s:hasPermission(selectedAccount,'modifica')}")
    public void modify() {
        selectedAccount.modify();
    }
}

La cosa interessante da notare in questo esempio è il riferimento a selectedAccount che si vede all'interno della chiamata alla funzione hasPermission. Il valore di questa variabile verrà ricercato all'interno del contesto Seam e passato al metodo hasPermission() di Identity, il quale in questo caso può determinare se l'utente ha il permesso richiesto per modificare l'oggetto Account specificato.

Una degli indici di un'interfaccia utente ben progettata è quando agli utenti non vengono presentate opzioni per le quali non hanno i permessi necessari per usarle. La sicurezza di Seam consente la visualizzazione condizionale sia di sezioni di una pagina che di singoli controlli, basata sui privilegi dell'utente, usando esattamente le stesse espressioni EL che sono usate nella sicurezza dei componenti.

Diamo un'occhiata ad alcuni esempi della sicurezza nell'interfaccia. Prima di tutto prentendiamo di avere una form di accesso che debba essere visualizzata solo se l'utente non ha già fatto l'accesso. Usando la proprietà identity.isLoggedIn() possiamo scrivere questo:


<h:form class="loginForm" rendered="#{not identity.loggedIn}"
>

Se l'utente non ha eseguito l'accesso, allora la form di accesso verrà visualizzata. Fin qui tutto bene. Ora vogliamo che ci sia un menu sulla pagina che contenga alcune azioni speciali che devono essere accessibili solo agli utenti del ruolo dirigente. Ecco un modo in cui ciò potrebbe essere scritto:


<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('dirigente')}">
    Rapporti per i dirigenti
</h:outputLink
>

Anche fin qui tutto bene. Se l'utente non è un membro del ruolo dirigente, allora outputLink non verrà visualizzato. L'attributo rendered in generale può essere usato per il controllo stesso oppure in un controllo <s:div> o <s:span> che ne comprende altri.

Ora andiamo su qualcosa di più complesso. Supponiamo di avere in una pagina un controllo h:dataTable che elenca delle righe per le quali si può volere visualizzare o meno i link alle azioni in funzione dei permessi dell'utente. La funzione EL s:hasPermission ci consente di passare un parametro oggetto che può essere usato per determinare se l'utente ha o meno il permesso richiesto per quell'oggetto. Ecco come può apparire una dataTable con dei link controllati dalla sicurezza:


<h:dataTable value="#{clients}" var="cl">
    <h:column>
        <f:facet name="header"
>Nome</f:facet>
        #{cl.name}
    </h:column>
    <h:column>
        <f:facet name="header"
>Citt�</f:facet>
        #{cl.city}
    </h:column>
    <h:column>
        <f:facet name="header"
>Azione</f:facet>
        <s:link value="Modifica cliente" action="#{clientAction.modify}"
                rendered="#{s:hasPermission(cl,'modifica')"/>
        <s:link value="Cancella cliente" action="#{clientAction.delete}"
                rendered="#{s:hasPermission(cl,'cancella')"/>
    </h:column>
</h:dataTable
>

La sicurezza di Seam consente anche di applicare le restrizioni di sicurezza alle azioni per leggere, inserire, aggiornare e cancellare le entità.

Per rendere sicure tutte le azioni per una classe entità, aggiungere un'annotazione @Restrict alla classe stessa:

@Entity

@Name("customer")
@Restrict
public class Customer {
  ...
}

Se nell'annotazione @Restrict non è indicata alcuna espressione, il controllo di sicurezza di default che viene eseguito è una verifica del permesso entità:azione, dove l'obiettivo del permesso è l'istanza dell'entità e azione è read, insert, update o delete.

E' anche possibile applicare una restrizione solo a determinate azioni, posizionando l'annotazione @Restrict nel corrispondente metodo relativo al ciclo di vita dell'entità (annotato come segue):

Ecco un esempio di come un'entità potrebbe essere configurata per eseguire un controllo di sicurezza per tutte le operazioni insert. Notare che non è richiesto che il metodo faccia qualcosa, la sola cosa importante per quanto riguarda la sicurezza è come è annotato:



  @PrePersist @Restrict
  public void prePersist() {}
   

Ed ecco un esempio di una regola sui permessi di entità che controlla se all'utente autenticato è consentito di inserire un record MemberBlog (dall'applicazione di esempio seamspace). L'entità per la quale viene fatto il controllo di sicurezza è inserita automaticamente nella working memory (in questo caso MemberBlog):

rule InsertMemberBlog
  no-loop
  activation-group "permissions"
when
  principal: Principal()
  memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName())))
  check: PermissionCheck(target == memberBlog, action == "insert", granted == false)
then
  check.grant();
end;

Questa regola concederà il permesso memberBlog:insert se l'utente attualmente autenticato (indicato dal fatto Principal) ha lo stesso nome del membro per il quale è stata creata la voce del blog. La riga "principal: Principal()" può essere vista nel codice di esempio come un collegamento con una variabile. Essa collega l'istanza dell'oggetto Principal nella working memory (posizionato durante l'autenticazione) e lo assegna ad una variabile chiamata principal. I collegamenti con le variabili consentono di fare riferimento al valore in altri posti, come nella riga successiva che confronta il nome dell'utente con il nome del Principal. Per maggiori dettagli fare riferimento alla documentazione di JBoss Rules.

Infine abbiamo bisogno di installare una classe listener che integra la sicurezza Seam con la libreria JPA.

Seam fornisce una serie di annotazioni che possono essere usate come un'alternativa a @Restrict e che hanno l'ulteriore vantaggio di essere verificabili durante la compilazione, dato che non gestiscono espressioni EL arbitrarie nel modo in cui succede per la @Restrict.

Così com'è, Seam contiene delle annotazioni per i permessi standard per le operazioni CRUD, comunque è solo questione di aggiungerne altre. Le seguenti annotazioni sono fornire nel package org.jboss.seam.annotations.security:

Per usare queste annotazioni basta metterle sul metodo o sul parametro per il quale si vuole eseguire il controllo di sicurezza. Se messe su un metodo, allora dovranno specificare la classe obiettivo per la quale il permesso deve essere controllato. Si prenda il seguente esempio:

  @Insert(Customer.class)
  public void createCustomer() {
    ...
  }

In questo esempio un controllo di permessi viene fatto sull'utente per assicurarsi che abbia i diritti per creare un nuovo oggetto Customer. L'obiettivo del controllo di permessi sarà Customer.class (l'effettiva istanza di java.lang.Class) e l'azione è la rappresentazione a lettere minuscole del nome dell'annotazione, che in questo esempio è insert.

E' anche possibile annotare i parametri di un metodo di un componente allo stesso modo. Se viene fatto in questo modo non è richiesto di specificare l'obiettivo del permesso (dato che il valore stesso del parametro sarà l'obiettivo del controllo di permessi):

  public void updateCustomer(@Update Customer customer) {
    ...
  }

Per creare una propria annotazione di sicurezza basta annotarla con @PermissionCheck, ad esempio:

@Target({METHOD, PARAMETER})

@Documented
@Retention(RUNTIME)
@Inherited
@PermissionCheck
public @interface Promote {
   Class value() default void.class;
}

Se si vuole modificare il nome dell'azione di default del permesso (che è la versione a lettere minuscole del nome dell'annotazione) con un altro valore, è possibile specificarlo all'interno dell'annotazione @PermissionCheck:

@PermissionCheck("upgrade")

La sicurezza di Seam fornisce un framework espandibile per risolvere i permessi dell'applicazione. Il seguente diagramma di classi mostra una panoramica dei componenti principali del framework dei permessi:

Le classi rilevanti sono spiegate in maggiore dettaglio nel seguente paragrafo.

Questa è in realtà un'interfaccia che fornisce i metodi per risolvere i singoli permessi sugli oggetti. Seam fornisce le seguenti implementazioni già fatte di PermissionResolver, che sono descritte in maggiore dettaglio più avanti in questo capitolo:

E' molto semplice implementare il proprio risolutore di permessi. L'interfaccia PermissionResolver definisce solo due metodi che devono essere implementati, come mostra la seguente tabella. Includendo la propria implementazione di PermissionResolver nel proprio progetto Seam, essa sarà automaticamente rilevata durante l'esecuzione e registrata nel ResolverChain predefinito.


Nota

As they are cached in the user's session, any custom PermissionResolver implementations must adhere to a couple of restrictions. Firstly, they may not contain any state that is finer-grained than session scope (and the scope of the component itself should either be application or session). Secondly, they must not use dependency injection as they may be accessed from multiple threads simultaneously. In fact, for performance reasons it is recommended that they are annotated with @BypassInterceptors to bypass Seam's interceptor stack altogether.

Un ResolverChain contiene un elenco ordinato di PermissionResolver, con lo scopo di risolvere i permessi sugli oggetti di una determinata classe oppure i permessi obiettivo.

The default ResolverChain consists of all permission resolvers discovered during application deployment. The org.jboss.seam.security.defaultResolverChainCreated event is raised (and the ResolverChain instance passed as an event parameter) when the default ResolverChain is created. This allows additional resolvers that for some reason were not discovered during deployment to be added, or for resolvers that are in the chain to be re-ordered or removed.

Il seguente diagramma di sequenza mostra l'interazione tra i componenti del framework dei permessi durante la verifica di un permesso (segue la spiegazione). Una verifica di permesso può essere originata da una serie di possibili fonti, ad esempio gli intercettori di sicurezza, la funzione EL s:hasPermission, oppure tramite una chiamata alla API Identity.checkPermission:

Uno dei risolutori di permesso già fatti forniti da Seam, RuleBasedPermissionResolver, consente di valutare i permessi in base ad un insieme di regole di sicurezza Drools (JBoss Rules). Un paio di vantaggi nell'uso di un motore di regole sono: 1) una posizione centralizzata della logica di gestione che è usata per valutare i permessi degli utenti; 2) la velocità, Drools usa algoritmi molto efficienti per valutare grandi quantità di regole complesse comprendenti condizioni multiple.

La configurazione per RuleBasedPermissionResolver richiede che una base di regole venga prima configurata in components.xml. Per difetto si aspetta che questa base di regole sia chiamata securityRules, come nel seguente esempio:


<components xmlns="http://jboss.com/products/seam/components"
              xmlns:core="http://jboss.com/products/seam/core"
              xmlns:security="http://jboss.com/products/seam/security"
              xmlns:drools="http://jboss.com/products/seam/drools"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation=
                  "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
                   http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
                   http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.1.xsd"
                   http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd">
  
     <drools:rule-base name="securityRules">
         <drools:rule-files>
             <value
>/META-INF/security.drl</value>
         </drools:rule-files>
     </drools:rule-base>
  
  </components
>

Il nome predefinito della base di regole può essere modificato specificando la proprietà security-rules per RuleBasedPermissionResolver:

  <security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>

Una volta che il componente RuleBase è configurato, è il momento di scrivere le regole di sicurezza.

Il primo passo per scrivere delle regole di sicurezza è di creare un nuovo file di regole nella cartella /META-INF del file jar dell'applicazione. Di solito questo file dovrebbe essere chiamato qualcosa come security.drl, comunque lo si può chiamare nel modo che si preferisce purché sia configurato in maniera corrispondente in components.xml.

Dunque, che cosa deve contenere il file delle regole di sicurezza? A questo punto potrebbe essere una buona idea almeno sbirciare nella documentazione Drools, comunque per partire ecco un esempio estremamente semplice:

package MyApplicationPermissions;
  
  import org.jboss.seam.security.permission.PermissionCheck;
  import org.jboss.seam.security.Role;
  
  rule CanUserDeleteCustomers
  when
    c: PermissionCheck(target == "customer", action == "delete")
    Role(name == "admin")
  then
    c.grant();
  end

Dividiamolo passo per passo. La prima cosa che vediamo è la dichiarazione del package. Un package in Drools è essenzialmente una collezione di regole. Il nome del package può essere qualsiasi, non è in relazione con niente che sia al di fuori della visibilità della base di regole.

La cosa successiva che possiano notare è un paio di dichiarazioni import per le classi PermissionCheck e Role. Questi import informano il motore di regole che all'interno delle nostre regole faremo riferimento a queste classi.

Infine abbiamo il codice della regola. Ogni regola all'interno di un package deve avere un nome univoco (di solito descrive lo scopo della regola). In questo caso la nostra regola si chiama CanUserDeleteCustomers e verrà usata per verificare se ad un utente è consentito di cancellare un record relativo ad un cliente.

Guardando il corpo della definizione della regola si possono notare due distinte sezioni. Le regole hanno quello che è noto come lato sinistro (LHS, left hand side) e un lato destro (RHS, right hand side). Il lato sinistro consiste nella parte condizionale della regola, cioè l'elenco delle condizioni che devono essere soddisfatte per la regola si applichi. Il lato sinistro è rappresentato dalla sezione when. Il lato destro è la conseguenza, o la parte di azione della regola che si applica solo se tutte le condizioni del lato sinistro sono verificate. Il lato destro è rappresentato dalla sezione then. La fine della regola è stabilita dalla linea end.

Se guardiamo la parte sinistra della regola vediamo che ci sono due condizioni. Esaminiamo la prima condizione:

c: PermissionCheck(target == "customer", action == "delete")

Letta in inglese questa condizione dice che all'interno della working memory deve esistere un oggetto PermissionCheck con una proprietà target uguale a "customer" e una proprietà action uguale a "delete".

Dunque cos'è la working memory? Nota anche come "stateful session" nella terminologia Drools, la working memory è un oggetto collegato alla sessione che contiene le informazioni contestuali che sono richieste dal motore di regole per prendere una decisione sul controllo di permesso. Ogni volta che il metodo hasPermission() viene chiamato, viene creato un oggetto, o Fatto, temporaneo PermissionCheck, e viene inserito nella working memory. Questo PermissionCheck corrisponde esattamente al permesso che si sta controllando, così, ad esempio, se viene chiamato hasPermission("account", "create") allora verrà inserito nella working memory un oggetto PermissionCheck con target uguale a "account" e action uguale a "create", per la durata del controllo di permesso.

Accanto al fatto PermissionCheck c'è anche un fatto org.jboss.seam.security.Role per ogni ruolo di cui l'utente autenticato è membro. Questi fatti Role sono sincronizzati con i ruoli dell'utente autenticato all'inizio di ogni controllo di permesso. Di conseguenza qualsiasi oggetto Role che venisse inserito nella working memory nel corso del controllo di permesso sarebbe rimosso prima che il controllo di permesso successivo avvenga, a meno che l'utente autenticato non sia effettivamente membro di quel ruolo. Insieme ai fatti PermissionCheck e Role la working memory contiene anche l'oggetto java.security.Principal che era stato creato come risultato del processo di autentifica.

E' anche possibile inserire ulteriori fatti nella working memory chiamando RuleBasedPermissionResolver.instance().getSecurityContext().insert(), passando l'oggetto come parametro. Fanno eccezione a questo gli oggetti Role che, come già detto, sono sincronizzati all'inizio di ciascun controllo di permesso.

Tornando al nostro esempio, possiamo anche notare che la prima linea della nostra parte sinistra ha il prefisso c:. Questa è una dichiarazione di variabile ed è usata per fare riferimento all'oggetto rilevato dalla condizione (in questo caso il PermissionCheck). Passando alla seconda linea della nostra parte sinistra vediamo questo:

Role(name == "admin")

Questa condizione dichiara semplicemente che ci deve essere un oggetto Role con un name uguale ad "admin" nella working memory. Come già menzionato, i ruoli dell'utente sono inseriti nella working memory all'inizio di ogni controllo di permesso. Così, mettendo insieme entrambe le condizioni, questa regola in pratica dice "mi attiverò quando ci sarà un controllo per il permesso customer:delete e l'utente è un membro del ruolo admin".

Quindi qual è la conseguenza dell'attivazione della regola? Diamo un'occhiata alla parte destra della regola:

c.grant()

La parte destra è costituita da codice Java e, in questo caso, esso invoca il metodo grant() dell'oggetto c il quale, come già detto, è una variabile che rappresenta l'oggetto PermissionCheck. Insieme alle proprietà name e action, nell'oggetto PermissionCheck c'è anche una proprietà granted che inizialmente è impostata a false. Chiamando grant() su un PermissionCheck la proprietà granted viene impostata a true, il che significa che il controllo di permesso è andato a buon fine, consentendo all'utente di portare avanti qualsiasi azione per cui il controlo di permesso era stato inteso.

Un altro risolutore di permessi incluso in Seam, il PersistentPermissionResolver consente di caricare i permessi da un dispositivo di memorizzazione persistente, come una database relazionale. Questo risolutore di permessi fornisce una sicurezza orientata alle istanze in stile ACL (Access Control List), permettendo di assegnare specifici permessi sull'oggetto a utenti e ruoli. Allo stesso modo permette inoltre di assegnare in modo persistente permessi con un nome arbitrario (non necessariamente basato sull'oggetto o la classe).

A permission store is required for PersistentPermissionResolver to connect to the backend storage where permissions are persisted. Seam provides one PermissionStore implementation out of the box, JpaPermissionStore, which is used to store permissions inside a relational database. It is possible to write your own permission store by implementing the PermissionStore interface, which defines the following methods:

Tabella 15.8. PermissionStore interface

Tipo restituito

Metodo

Descrizione

List<Permission>

listPermissions(Object target)

This method should return a List of Permission objects representing all the permissions granted for the specified target object.

List<Permission>

listPermissions(Object target, String action)

This method should return a List of Permission objects representing all the permissions with the specified action, granted for the specified target object.

List<Permission>

listPermissions(Set<Object> targets, String action)

This method should return a List of Permission objects representing all the permissions with the specified action, granted for the specified set of target objects.

boolean

grantPermission(Permission)

This method should persist the specified Permission object to the backend storage, returning true if successful.

boolean

grantPermissions(List<Permission> permissions)

This method should persist all of the Permission objects contained in the specified List, returning true if successful.

boolean

revokePermission(Permission permission)

This method should remove the specified Permission object from persistent storage.

boolean

revokePermissions(List<Permission> permissions)

This method should remove all of the Permission objects in the specified list from persistent storage.

List<String>

listAvailableActions(Object target)

This method should return a list of all the available actions (as Strings) for the class of the specified target object. It is used in conjunction with permission management to build the user interface for granting specific class permissions (see section further down).


This is the default PermissionStore implementation (and the only one provided by Seam), which uses a relational database to store permissions. Before it can be used it must be configured with either one or two entity classes for storing user and role permissions. These entity classes must be annotated with a special set of security annotations to configure which properties of the entity correspond to various aspects of the permissions being stored.

If you wish to use the same entity (i.e. a single database table) to store both user and role permissions, then only the user-permission-class property is required to be configured. If you wish to use separate tables for storing user and role permissions, then in addition to the user-permission-class property you must also configure the role-permission-class property.

For example, to configure a single entity class to store both user and role permissions:


  <security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission"/>

To configure separate entity classes for storing user and role permissions:


  <security:jpa-permission-store user-permission-class="com.acme.model.UserPermission"
    role-permission-class="com.acme.model.RolePermission"/>

As mentioned, the entity classes that contain the user and role permissions must be configured with a special set of annotations, contained within the org.jboss.seam.annotations.security.permission package. The following table lists each of these annotations along with a description of how they are used:

Tabella 15.9. Entity Permission annotations

Annotazione

Target

Descrizione

@PermissionTarget

FIELD,METHOD

This annotation identifies the property of the entity that will contain the permission target. The property should be of type java.lang.String.

@PermissionAction

FIELD,METHOD

This annotation identifies the property of the entity that will contain the permission action. The property should be of type java.lang.String.

@PermissionUser

FIELD,METHOD

This annotation identifies the property of the entity that will contain the recipient user for the permission. It should be of type java.lang.String and contain the user's username.

@PermissionRole

FIELD,METHOD

This annotation identifies the property of the entity that will contain the recipient role for the permission. It should be of type java.lang.String and contain the role name.

@PermissionDiscriminator

FIELD,METHOD

This annotation should be used when the same entity/table is used to store both user and role permissions. It identifies the property of the entity that is used to discriminate between user and role permissions. By default, if the column value contains the string literal user, then the record will be treated as a user permission. If it contains the string literal role, then it will be treated as a role permission. It is also possible to override these defaults by specifying the userValue and roleValue properties within the annotation. For example, to use u and r instead of user and role, the annotation would be written like this:

  @PermissionDiscriminator(userValue = "u", roleValue = "r")

Here is an example of an entity class that is used to store both user and role permissions. The following class can be found inside the SeamSpace example:



@Entity
public class AccountPermission implements Serializable {  
   private Integer permissionId;
   private String recipient;
   private String target;
   private String action;
   private String discriminator;
   
   @Id @GeneratedValue
   public Integer getPermissionId() {
      return permissionId;
   }
   
   public void setPermissionId(Integer permissionId) {
      this.permissionId = permissionId;
   }
   
   @PermissionUser @PermissionRole
   public String getRecipient() {
      return recipient;
   }
   
   public void setRecipient(String recipient) {
      this.recipient = recipient;
   }
   
   @PermissionTarget
   public String getTarget() {
      return target;
   }
   
   public void setTarget(String target) {
      this.target = target;
   }
   
   @PermissionAction
   public String getAction() {
      return action;
   }
   
   public void setAction(String action) {
      this.action = action;
   }
   
   @PermissionDiscriminator
   public String getDiscriminator() {
      return discriminator;
   }
   
   public void setDiscriminator(String discriminator) {
      this.discriminator = discriminator;
   }
}          
          

As can be seen in the above example, the getDiscriminator() method has been annotated with the @PermissionDiscriminator annotation, to allow JpaPermissionStore to determine which records represent user permissions and which represent role permissions. In addition, it can also be seen that the getRecipient() method is annotated with both @PermissionUser and @PermissionRole annotations. This is perfectly valid, and simply means that the recipient property of the entity will either contain the name of the user or the name of the role, depending on the value of the discriminator property.

When storing or looking up permissions, JpaPermissionStore must be able to uniquely identify specific object instances to effectively operate on its permissions. To achieve this, an identifier strategy may be assigned to each target class for the generation of unique identifier values. Each identifier strategy implementation knows how to generate unique identifiers for a particular type of class, and it is a simple matter to create new identifier strategies.

The IdentifierStrategy interface is very simple, declaring only two methods:

public interface IdentifierStrategy {

   boolean canIdentify(Class targetClass);
   String getIdentifier(Object target);
}

The first method, canIdentify() simply returns true if the identifier strategy is capable of generating a unique identifier for the specified target class. The second method, getIdentifier() returns the unique identifier value for the specified target object.

Seam provides two IdentifierStrategy implementations, ClassIdentifierStrategy and EntityIdentifierStrategy (see next sections for details).

To explicitly configure a specific identifier strategy to use for a particular class, it should be annotated with org.jboss.seam.annotations.security.permission.Identifier, and the value should be set to a concrete implementation of the IdentifierStrategy interface. An optional name property can also be specified, the effect of which is dependent upon the actual IdentifierStrategy implementation used.

This identifier strategy is used to generate unique identifiers for entity beans. It does so by concatenating the entity name (or otherwise configured name) with a string representation of the primary key value of the entity. The rules for generating the name section of the identifier are similar to ClassIdentifierStrategy. The primary key value (i.e. the id of the entity) is obtained using the PersistenceProvider component, which is able to correctly determine the value regardless of which persistence implementation is used within the Seam application. For entities not annotated with @Entity, it is necessary to explicitly configure the identifier strategy on the entity class itself, for example:

@Identifier(value = EntityIdentifierStrategy.class)

public class Customer { 

For an example of the type of identifier values generated, assume we have the following entity class:

@Entity

public class Customer {
  private Integer id;
  private String firstName;
  private String lastName;
  
  @Id 
  public Integer getId() { return id; }
  public void setId(Integer id) { this.id = id; }
  
  public String getFirstName() { return firstName; }
  public void setFirstName(String firstName) { this.firstName = firstName; }
  
  public String getLastName() { return lastName; }
  public void setLastName(String lastName) { this.lastName = lastName; }
}

For a Customer instance with an id value of 1, the value of the identifier would be "Customer:1". If the entity class is annotated with an explicit identifier name, like so:

@Entity

@Identifier(name = "cust")
public class Customer { 

Then a Customer with an id value of 123 would have an identifier value of "cust:123".

In much the same way that Seam Security provides an Identity Management API for the management of users and roles, it also provides a Permissions Management API for the management of persistent user permissions, via the PermissionManager component.

The PermissionManager component is an application-scoped Seam component that provides a number of methods for managing permissions. Before it can be used, it must be configured with a permission store (although by default it will attempt to use JpaPermissionStore if it is available). To explicitly configure a custom permission store, specify the permission-store property in components.xml:



<security:permission-manager permission-store="#{ldapPermissionStore}"/>      
      

The following table describes each of the available methods provided by PermissionManager:


Seam includes basic support for serving sensitive pages via the HTTPS protocol. This is easily configured by specifying a scheme for the page in pages.xml. The following example shows how the view /login.xhtml is configured to use HTTPS:


<page view-id="/login.xhtml" scheme="https"/>

This configuration is automatically extended to both s:link and s:button JSF controls, which (when specifying the view) will also render the link using the correct protocol. Based on the previous example, the following link will use the HTTPS protocol because /login.xhtml is configured to use it:


<s:link view="/login.xhtml" value="Login"/>

Browsing directly to a view when using the incorrect protocol will cause a redirect to the same view using the correct protocol. For example, browsing to a page that has scheme="https" using HTTP will cause a redirect to the same page using HTTPS.

It is also possible to configure a default scheme for all pages. This is useful if you wish to use HTTPS for a only few pages. If no default scheme is specified then the normal behavior is to continue use the current scheme. So once the user accessed a page that required HTTPS, then HTTPS would continue to be used after the user navigated away to other non-HTTPS pages. (While this is good for security, it is not so great for performance!). To define HTTP as the default scheme, add this line to pages.xml:


<page view-id="*" scheme="http" />

Of course, if none of the pages in your application use HTTPS then it is not required to specify a default scheme.

You may configure Seam to automatically invalidate the current HTTP session each time the scheme changes. Just add this line to components.xml:


<web:session invalidate-on-scheme-change="true"/>

This option helps make your system less vulnerable to sniffing of the session id or leakage of sensitive data from pages using HTTPS to other pages using HTTP.

Though strictly not part of the security API, Seam provides a built-in CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) algorithm to prevent automated processes from interacting with your application.

OpenID is a community standard for external web-based authentication. The basic idea is that any web application can supplement (or replace) its local handling of authentication by delegating responsibility to an external OpenID server of the user's chosing. This benefits the user, who no longer has to remember a name and password for every web application he uses, and the developer, who is relieved of some of the burden of maintaining a complex authentication system.

When using OpenID, the user selects an OpenID provider, and the provider assigns the user an OpenID. The id will take the form of a URL, for example http://maximoburrito.myopenid.com however, it's acceptable to leave off the http:// part of the identifier when logging into a site. The web application (known as a relying party in OpenID-speak) determines which OpenID server to contact and redirects the user to the remote site for authentication. Upon successful authentication the user is given the (cryptographically secure) token proving his identity and is redirected back to the original web application.The local web application can then be sure the user accessing the application controls the OpenID he presented.

It's important to realize at this point that authentication does not imply authorization. The web application still needs to make a determination of how to use that information. The web application could treat the user as instantly logged in and give full access to the system or it could try and map the presented OpenID to a local user account, prompting the user to register if he hasn't already. The choice of how to handle the OpenID is left as a design decision for the local application.

Seam rende facile la costruzione di applicazioni internazionali. Prima di tutto verranno percorse le varie fasi necessarie per rendere internazionale e tradotta un'applicazione. In seguito si darà un'occhiata al modo in cui Seam gestisce i gruppi di stringhe associate ai componenti (resource bundle).

Un'applicazione JEE consiste di molti componenti ed ognuno di essi deve essere configurato opportunamente affinché l'applicazione venga tradotta.

Partendo dalla base, il primo passo è assicurarsi che il server e il client del database utilizzino la codifica di caratteri corretta per la traduzione. Di solito si vorrà utilizzare UTF-8. Come fare questo non è oggetto di questa guida.

Ci sarà bisogno di tradurre le stringhe per tutti i messaggi dell'applicazione (per esempio le etichette dei campi nelle pagine). In primo luogo occorre assicurarsi che il resource bundle sia codificato utilizzando la giusta codifica di carattere. Per default viene usato ASCII. Benché la codifica ASCII sia sufficiente per molte lingue, essa non fornisce i caratteri per tutte le lingue.

I resource bundles devono essere creati in ASCII, oppure devono utilizzare una notazione Unicode per rappresentare i caratteri Unicode. Poiché un file .properties non viene compilato in byte-code, non c'è modo di dire alla JVM quale codifica caratteri utilizzare. Perciò occorre usare caratteri ASCII oppure usare la notazione Unicode per i caratteri che non fanno parte dell'insieme ASCII. E' possibile rappresentare un carattere Unicode in un file Java usando la notazione \uXXXX, dove XXXX è la rappresentazione esadecimale del carattere.

E' possibile scrivere la traduzione delle etichette (<xlink>Etichette</xlink>) nei resource bundles con la codifica del proprio sistema e poi convertire il contenuto del file nel formato con le notazioni Unicode attraverso lo strumento native2ascii fornito con JDK. Questo strumento converte un file scritto nella codifica originale in uno dove i caratteri non-ASCII sono rappresentati come sequenze di notazioni Unicode.

L'uso di questo strumento è descritto qui per Java 5 oppure qui per Java 6. Ad esempio, per convertire un file da UTF-8:

$ native2ascii -encoding UTF-8 messages_cs.properties > messages_cs_escaped.properties

Ogni sessione utente registrata ha associata un'istanza di java.util.Locale (disponibile nell'applicazione come un componente chiamato locale). In condizioni normali non sarà necessario fare alcuna configurazione particolare per impostare la lingua. Seam delega a JSF il compito di determinare la lingua attiva:

E' possibile impostare la lingua manualmente tramite le proprietà di configurazione di Seam org.jboss.seam.international.localeSelector.language, org.jboss.seam.international.localeSelector.country e org.jboss.seam.internationale.localeSelector.variant, ma non c'è una vera buona ragione per farlo.

E' comunque utile consentire all'utente di impostare la lingua manualmente tramite l'interfaccia utente. Seam fornisce una funzionalità per sovrascrivere il linguaggio determinato dall'algoritmo descritto sopra. Tutto ciò che è necessario fare è aggiungere il seguente brano ad una form in una pagina JSP o Facelets:


<h:selectOneMenu value="#{localeSelector.language}">
    <f:selectItem itemLabel="English" itemValue="en"/>
    <f:selectItem itemLabel="Deutsch" itemValue="de"/>
    <f:selectItem itemLabel="Francais" itemValue="fr"/>
    <f:selectItem itemLabel="Italiano" itemValue="it"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}"
    value="#{messages['ChangeLanguage']}"/>

Oppure, se si vuole mostrare una lista delle lingue gestite da faces-config.xml, si può usare:


<h:selectOneMenu value="#{localeSelector.localeString}">
    <f:selectItems value="#{localeSelector.supportedLocales}"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}"
    value="#{messages['ChangeLanguage']}"/>

Quando l'utente seleziona una voce dal menu a discesa e poi fa click sul bottone di comando, la lingua di Seam e di JSF viene sovrascritta per il resto della sessione.

Tutto ciò porta a domandarsi dove siano definite le lingue gestite. Tipicamente nell'elemento <locale-config> del file di configurazione JSF (/META-INF/faces-config.xml) si indica una lista di lingue per le quali si dispone dei corrispondenti resource bundle. Ad ogni modo si è imparato ad apprezzare che il meccanismo di configurazione dei componenti Seam è più completo di quello fornito in Java EE. Per questa ragione è possibile configurare le lingue gestite e la lingua di default del server usando il componente org.jboss.seam.international.localeConfig. Per usarlo occorre prima dichiarare il namespace XML per il pacchetto international di Seam nel descrittore dei componenti Seam, quindi definire la lingua di default e le lingue gestite come segue:


<international:locale-config default-locale="fr_CA" supported-locales="en fr_CA fr_FR it_IT"/>

Ovviamente se c'è la dichiarazione che una certa lingua è gestita, sarà meglio fornire il resource bundle corrispondente! Nel prossimo capitolo si imparerà come si definiscono le etichette per una lingua specifica.

JSF gestisce l'internazionalizzazione delle etichette e del testo descrittivo nell'interfaccia utente tramite l'uso di f:loadBundle>. Questo approccio è possibile nelle applicazioni Seam. In alternativa è possibile sfruttare i vantaggi offerti dal componente Seam messages per mostrare label costruite tramite modelli con espressioni EL.

Seam fornisce un java.util.ResourceBundle (disponibile all'applicazione come un org.jboss.seam.core.resourceBundle). Occorre rendere disponibili le nostre etichette tradotte tramite questo speciale resource bundle. Per default il resource bundle usato da Seam si chiama messages così che occorre definire le etichette in file chiamati messages.properties, messages_en.properties, messages_en_AU.properties, ecc. Questi file di solito risiedono nella cartella WEB-INF/classes.

Quindi, in messages_en.properties:

Hello=Hello

E in messages_en_AU.properties:

Hello=G'day

E' possibile indicare un nome diverso per il resource bundle impostando la proprietà di configurazione Seam org.jboss.seam.core.resourceLoader.bundleNames. E' possibile persino specificare un elenco di nomi di resource bundle sui quali devono essere ricercati i messaggi (a partire dall'ultimo).


<core:resource-loader>
    <core:bundle-names>
        <value>mycompany_messages</value>
        <value>standard_messages</value>       
    </core:bundle-names>
</core:resource-loader>

Se si vuole definire un messaggio solo per una particolare pagina, è possibile specificarlo in un resource bundle con lo stesso nome dell'identificativo della view JSF, omettendo il / iniziale e l'estensione del file finale. Così è possibile mettere il nostro messaggio in welcome/hello_en.properties se si desidera mostrare il messaggio solo in /welcome/hello.jsp.

E' anche possibile specificare esplicitamente un nome di resource bundle in pages.xml:


<page view-id="/welcome/hello.jsp" bundle="HelloMessages"/>

Quindi possiamo usare i messaggi definiti in HelloMessages.properties in /welcome/hello.jsp.

Le applicazioni Seam sono anche molto facilmente personalizzabili nell'aspetto. Le API per i temi sono molto simili alle API per la traduzione, ma ovviamente questi due concetti sono ortogonali e alcune applicazione gestiscono sia le traduzioni che i temi.

Prima di tutto occorre configurare l'insieme dei temi gestiti:


<theme:theme-selector cookie-enabled="true">
    <theme:available-themes>
        <value>default</value>
        <value>accessible</value>
        <value>printable</value>
    </theme:available-themes>
</theme:theme-selector>

Notare che il primo tema elencato è il tema di default.

I temi sono definiti in file di proprietà con lo stesso nome del tema. Ad esempio, il tema default è definito come un insieme di voci in default.properties. Ad esempio default.properties potrebbe definire:

css ../screen.css
template /template.xhtml

Di solito le voci nel resource bundle di un tema saranno percorsi a fogli di stile CSS o immagini e nomi di modelli facelets (a differenza dei resource bundle per le traduzioni che normalmente contengono testo).

Ora è possibile usare queste voci nella pagine JSP o facelets. Ad esempio, per gestire con un tema il foglio di stile di una pagina facelets:


<link href="#{theme.css}" rel="stylesheet" type="text/css" />

Oppure, quando la definizione della pagina risiede in una sottocartella:


<link href="#{facesContext.externalContext.requestContextPath}#{theme.css}" 
    rel="stylesheet" type="text/css" />

In modo più flessibile, facelets consente di gestire con i temi il modello usato da un <ui:composition>:


<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    template="#{theme.template}">

Così come per selezionare la lingua, c'è un componente che consente all'utente di cambiare liberamente il tema:


<h:selectOneMenu value="#{themeSelector.theme}">
    <f:selectItems value="#{themeSelector.themes}"/>
</h:selectOneMenu>
<h:commandButton action="#{themeSelector.select}" value="Select Theme"/>

I siti web orientati alla collaborazione tra utenti richiedono un linguaggio per marcare in modo comprensibile il testo formattato da inserire nei post di un forum, nelle pagine wiki, nei commenti, ecc. Seam fornisce il controllo <s:formattedText/> per mostrare il testo formattato in modo conforme con il linguaggio Seam Text. Seam Text è realizzato utilizzando un interprete basato su ANTLR. Comunque non c'è bisogno di sapere niente di ANTLR per utilizzarlo.

Ecco un semplice esempio:

E' semplice rendere il testo *evidenziato*, |a spaziatura fissa|,
~cancellato~, sovra^scritto^ o _sottolineato_.

Se mostriamo questo testo usando <s:formattedText/>, otteniamo il seguente codice HTML:


E' semplice rendere il testo *evidenziato*, |a spaziatura fissa|,
~cancellato~, sovra^scritto^ o _sottolineato_.

E' possibile usare una riga vuota per indicare un nuovo paragrafo e un + per indicare un titolo:

+Questo è un grande titolo
/Dovrai/ avere del testo dopo il titolo!
 
++Questo è un titolo più piccolo
Questo è il primo paragrafo. Lo possiamo dividere in più 
righe, ma per terminarlo serve una riga vuota.

Questo è il secondo paragrafo.

(Notare che un semplice a-capo viene ignorato, è necessaria una riga vuota per avere il testo in un nuovo paragrafo). Questo è il codice HTML risultante:


<h1
>Questo � un grande titolo</h1>
<p>
<i
>Dovrai</i
> avere del testo dopo il titolo!
</p>
 
<h2
>Questo � un titolo pi� piccolo</h2>
<p>
Questo � il primo paragrafo. Lo possiamo dividere in pi� 
righe, ma per terminarlo serve una riga vuota.
</p>

<p>
Questo � il secondo paragrafo.
</p
>

Le liste ordinate sono generate dal carattere #. Le liste non ordinate dal carattere =:

Una lista ordinata:
        
#prima voce
#seconda voce
#e anche la /terza/ voce

Una lista non ordinata:

=una voce
=un'altra voce

<p>
Una lista ordinata:
</p>
 
<ol
>       
<li
>prima voce</li>
<li
>seconda voce</li>
<li
>e anche una <i
>terza</i
> voce</li>
</ol>

<p>
Una lista non ordinata:
</p>

<ul>
<li
>una voce</li>
<li
>un'altra voce</li>
</ul
>

I brani con citazioni devono essere racchiusi tra virgolette:

L'altro ragazzo disse:
        
"Nyeah nyeah-nee 
/nyeah/ nyeah!"

Ma cosa pensi abbia voluto dire con "nyeah-nee"?

<p>
L'altro ragazzo disse:
</p>
        
<q
>Nyeah nyeah-nee
<i
>nyeah</i
> nyeah!</q>

<p>
Ma cosa pensi abbia voluto dire con <q
>nyeah-nee</q
>?
</p
>

Seam adesso include un componente per la generazione di documenti usando iText. Il primo focus del supporto di Seam ai documenti iText è per la generazione dei documenti PDF, ma Seam offre anche un supporto base per la generazione di documenti RTF.

Il supporto a iText è fornito da jboss-seam-pdf.jar. Questo JAR contiene i controlli JSF di iText, che sono usati per costruire le viste che possono generare il PDF, e il componente DocumentStore, che serve i documenti renderizzati per l'utente. Per includere il supporto PDF nell'applicazione, si metta jboss-seam-pdf.jar nella directory WEB-INF/lib assieme al file JAR di iText. Non serve alcuna ulteriore configurazione per usare il supporto a iText di Seam.

Il modulo iText di Seam richiede l'uso dei Facelets come tecnologia per la vista.Versioni future della libreria potrebbero supportare anche l'uso di JSP. In aggiunta, si richiede l'uso del pacchetto seam-ui.

Il progetto examples/itext contiene un esempio di supporto PDF. Viene mostrato il corretto impacchettamento per il deploy e l'esempio contiene un gran numero di funzionalità per la generazione PDF attualmente supportate.

<p:document>

Descrizione

I documenti vengono generati dai file XHTML facelet usando dei tag nel namespace http://jboss.com/products/seam/pdf. I documenti dovrebbero sempre avere il tag document alla radice del documento. Il tag document prepara Seam a generare un documento nel DocumentStore e compie un redirect HTML a quel contenuto memorizzato.

Attributi

  • type — Il tipo di documento da produrre. Valori validi sono PDF, RTF e HTML. Seam imposta come default la generazione PDF, e molte caratteristiche funzionano correttamente solo quando si generano documenti PDF.

  • pageSize — La dimensionedella pagina da generare. I valori maggiormente usati dovrebbero essere LETTER e A4. Una lista completa delle dimensioni di pagina supportate si trova nella classe com.lowagie.text.PageSize. In alternativa, pageSize può direttamente fornire la larghezza e l'altezza della pagina. Per esempio, il valore "612 792" è equivalente alla dimensione della pagina LETTER.

  • orientation — L'orientamento della pagina. Valori validi sono portrait e landscape. Nella modalità landscape, i valori dell'altezza e della larghezza della pagina sono invertiti.

  • margins — Valori di margine left, right, top e bottom.

  • marginMirroring — Indica che le impostazioni dei margini dovrebbero essere invertite in pagine alternate.

  • disposition — Quando si generano PDF in un browser, questo determina la Content-Disposition HTTP del documento. Valori validi sono inline, che indica che il documento deve essere mostrato in una finestra del browser se possibile, e attachment, che indica che il documento deve essere trattato come download. Il valore di default è inline.

  • fileName — Per gli allegati questo valore sovrascrive il nome del file scaricato.

Attributi dei metadati

  • title

  • subject

  • keywords

  • author

  • creator

Utilizzo


<p:document xmlns:p="http://jboss.com/products/seam/pdf"
>                                                      
  The document goes here.                                                                                             
</p:document
>

I documenti utili dovranno contenere più che il solo testo; comunque i componenti standard UI sono idonei per la generazione HTML e non sono utili per generare contenuto in PDF. Invece Seam fornisce dei componenti UI speciali per generare contenuto in PDF idoneo. Tag quali <p:image> e <p:paragraph> sono la base per i semplici documenti. Tag come <p:font> forniscono informazioni di stile a tutto il contenuto che sta intorno.

<p:paragraph>

Descrizione

La maggior parte dell'uso del testo dovrebbe essere sezionato in paragrafi, affinché i frammenti del testo possano scorrere, formattati ed con uno stile in gruppi logici.

Attributi

  • firstLineIndent

  • extraParagraphSpace

  • leading

  • multipliedLeading

  • spacingBefore — Lo spazio bianco da inserire prima dell'elemento.

  • spacingAfter — Lo spazio bianco da inserire dopo l'elemento.

  • indentationLeft

  • indentationRight

  • keepTogether

Utilizzo


<p:paragraph alignment="justify">
    This is a simple document.  It isn't very fancy.
</p:paragraph
>

<p:text>

Descrizione

Il tag text consente ai frammenti del testo di essere prodotti dai dati dell'applicazione usando normali meccanismi convertitori JSF. E' molto simile al tag outputText impiegato quando si generano documenti HTML.

Attributi

  • value — Il valore da visualizzare. Questo sarà tipicamente un'espressione di binding.

Utilizzo


<p:paragraph>
    The item costs <p:text value="#{product.price}">
        <f:convertNumber type="currency" currencySymbol="$"/>
    </p:text>
</p:paragraph
>

<p:html>

Descrizione

Il tag html genera contenuto HTML in PDF.

Attributi

  • value — Il testo da visualizzare.

Utilizzo



<p:html value="This is HTML with <b
>some markup</b
>." />
<p:html>
    <h1
>This is more complex HTML</h1>
    <ul>
        <li
>one</li>
        <li
>two</li>
        <li
>three</li>
    </ul>
</p:html>

<p:html>
    <s:formattedText value="*This* is |Seam Text| as HTML.  It's very^cool^." />
</p:html
>                                    

<p:font>

Descrizione

Il tag font definisce il font di default da usarsi per tutto il testo contenuto in esso.

Attributi

  • name — Il nome del font, per esempio: COURIER, HELVETICA, TIMES-ROMAN, SYMBOL o ZAPFDINGBATS.

  • size — La dimensione del punto nel font.

  • style — Gli stili del font. Una combinazione di: NORMAL, BOLD, ITALIC, OBLIQUE, UNDERLINE, LINE-THROUGH.

  • encoding — La codifica del set di caratteri.

Utilizzo


<p:font name="courier" style="bold" size="24">
    <p:paragraph
>My Title</p:paragraph>
</p:font
>

<p:newPage>

Descrizione

p:newPage inserisce un'interruzione di pagina.

Utilizzo


<p:newPage />

<p:image>

Descrizione

p:image inserisce un'immagine in un documento. Le immagini possono essere caricate dal classpath o dal contesto dell'applicazione web usando l'attributo value.

Le risorse possono anche essere generate dinamicamente dal codice dell'applicazione. L'attributo imageData può specificare un'espressione di value binding il cui valore è un oggetto java.awt.Image.

Attributi

  • value — Un nome di risorsa oppure un binding di metodo ad un'immagine generata dall'applicazione.

  • rotation — La rotazione dell'immagine in gradi.

  • height — L'altezza dell'immagine.

  • width — La larghezza dell'immagine.

  • alignment— L'allineamento dell'immagine. (vedere Sezione 18.1.7.2, «Valori per l'allineamento» per i possibili valori)

  • alt — Testo alternativo per la rappresentazione dell'immagine.

  • indentationLeft

  • indentationRight

  • spacingBefore — Lo spazio bianco da inserire prima dell'elemento.

  • spacingAfter — Lo spazio bianco da inserire dopo l'elemento.

  • widthPercentage

  • initialRotation

  • dpi

  • scalePercent — Il fattore di scala (come percentuale) da usare per l'immagine. Questo può esprimersi come valore di percentuale singola oppure come valori di due percentuali che rappresentano percentuali separate per la scala lungo x o lungo y.

  • wrap

  • underlying

Utilizzo


<p:image value="/jboss.jpg" />

<p:image value="#{images.chart}" />

<p:anchor>

Descrizione

p:anchor definisce i link cliccabili da un documento. Supporta i seguenti attributi:

Attributi

  • name — Il nome di una destinazione d'ancora dentro il documento.

  • reference — La destinazione a cui il link di riferisce. I link ad altri punti del documento dovrebbero iniziare con un "#". Per esempio, "#link1" si riferisce ad una posizione dell'ancora con il name impostato a link1. I link possono anche essere URL completi ad un punto della risorsa fuori dal documento.

Utilizzo


<p:listItem
><p:anchor reference="#reason1"
>Reason 1</p:anchor
></p:listItem
> 
...
<p:paragraph>
    <p:anchor name="reason1"
>It's the quickest way to get "rich"</p:anchor
> 
    ... 
</p:paragraph
>

<p:header>

<p:footer>

Descrizione

I componenti p:header e p:footer forniscono la possibilità di collocare il testo per l'intestazione ed il pié di pagina in ogni pagina del documento generato. Le dichiarazioni di header e footer dovrebbero apparire all'inizio del documento.

Attributi

  • alignment — L'allineamento della sezione header/footer. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i valori dell'allineamento)

  • backgroundColor — Il colore del background di header/footer. (Si veda Sezione 18.1.7.1, «Valori dei colori» per i valori dei colori)

  • borderColor — Il colore del bordo di header/footer. I singoli lati dei bordi possono essere impostati usando borderColorLeft, borderColorRight, borderColorTop e borderColorBottom.(Si veda Sezione 18.1.7.1, «Valori dei colori»per i valori dei colori)

  • borderWidth — La larghezza del bordo. I singoli lati dei bordi possono essere specificati usando borderWidthLeft, borderWidthRight, borderWidthTop e borderWidthBottom.

Utilizzo


<f:facet name="header">
  <p:font size="12">
    <p:footer borderWidthTop="1" borderColorTop="blue" 
              borderWidthBottom="0" alignment="center">
        Why Seam? [<p:pageNumber />]
    </p:footer>
  </p:font>
</f:facet
>

<p:pageNumber>

Descrizione

Il numero della pagina corrente può essere collocato dentro un header o un footer usando il tag p:pageNumber. Il tag del numero della pagina può essereusato solamente nel contesto dell'header o footer e può essere usato solo una volta.

Utilizzo


<p:footer borderWidthTop="1" borderColorTop="blue" 
          borderWidthBottom="0" alignment="center">
    Why Seam? [<p:pageNumber />]
</p:footer>

<p:chapter>

<p:section>

Descrizione

Se il documento generato segue una struttura libro/articolo, i tag p:chapter e p:section possono essere usati per fornire la struttura necessaria. Le sezioni possono essere usate soltanto dentro i capitoli, ma possono essere innestate con profondità arbitraria. La maggior parte dei visualizzatori PDF forniscono una facile navigazione tra i capitoli e le sezioni di un documento.

Attributi

  • alignment — L'allineamento della sezione header/footer. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i valori dell'allineamento)

  • number — Il numero del capitolo. Ogni capitolo dovrebbe avere un numero assegnato.

  • numberDepth — La profondità della numerazione per le sezioni. Tutte le sezioni sono numerate relativamente ai loro capitoli/sezioni circostanti. La quarta sezione della prima sezione del capitolo terzo avrebbe sezione 3.1.4, se visualizzata con una profondità di default di tre. Per omettere il numero del capitolo, occorre usare una profondità di numerazione pari a 2. In questo caso, il numero della sezione visualizzato sarebbe 1.4.

Utilizzo


<p:document xmlns:p="http://jboss.com/products/seam/pdf"
            title="Hello">

   <p:chapter number="1">
      <p:title
><p:paragraph
>Hello</p:paragraph
></p:title>
      <p:paragraph
>Hello #{user.name}!</p:paragraph>
   </p:chapter>

   <p:chapter number="2">
      <p:title
><p:paragraph
>Goodbye</p:paragraph
></p:title>
      <p:paragraph
>Goodbye #{user.name}.</p:paragraph>
   </p:chapter>

</p:document
> 

<p:header>

Descrizione

Ogni capitolo o sezione può contenere un p:title. Il titolo verrà mostrato vicino al numero del capitolo/sezione. Il corpo del titolo può contenere del semplice testo oppure un p:paragraph.

Le strutture di lista possono essere visualizzate usando i tag p:list e p:listItem. Le liste possono contenere sottoliste arbitrariamente innestate. Gli elementi di lista non possono essere usati fuori da una lista. Il seguente documento utilizza il tag ui:repeat per mostrare una lista di valori recuperata da un componente Seam.


<p:document xmlns:p="http://jboss.com/products/seam/pdf"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            title="Hello">
   <p:list style="numbered">
      <ui:repeat value="#{documents}" var="doc">
         <p:listItem
>#{doc.name}</p:listItem>
      </ui:repeat>
   </p:list>
</p:document
>

<p:list>

Attributi

  • style — Lo stile per l'ordinamento ed il contrassegno della lista. Un valore fra: NUMBERED, LETTERED, GREEK, ROMAN, ZAPFDINGBATS, ZAPFDINGBATS_NUMBER. Se non è fornito nessuno stile, gli elementi della lista sono contrassegnati.

  • listSymbol — Per le liste contrassegnate, specifica il simbolo del contrassegno.

  • indent — Il livello di indentazione della lista.

  • lowerCase — Per gli stili di lista che usano le lettere, indica se le lettere debbano essere minuscole.

  • charNumber — Per ZAPFDINGBATS, indica il codice del carattere per il carattere di contrassegno.

  • numberType — Per ZAPFDINGBATS_NUMBER, indica lo stile della numerazione.

Utilizzo


<p:list style="numbered">
  <ui:repeat value="#{documents}" var="doc">
    <p:listItem
>#{doc.name}</p:listItem>
  </ui:repeat>
</p:list
>

<p:listItem>

Descrizione

p:listItem supporta i seguenti attributi:

Attributi

  • alignment — L'allineamento della sezione header/footer. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i valori dell'allineamento)

  • alignment — L'allineamento dell'elemento della lista. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i possibili valori)

  • indentationLeft — La quantità di indentazione sinistra.

  • indentationRight — La quantità di indentazione destra.

  • listSymbol — Sovrascrive il simbolo di default della lista per quest'elemento della lista.

Utilizzo


...

Le strutture della tabella possono essere create usando i tag p:table e p:cell. A differenza di molte strutture di tabella, non c'è alcuna dichiarazione esplicita di riga. Se una tabella ha 3 colonne, allora le 3 celle formeranno automaticamente una riga. Le righe di header e footer possono essere dichiarate, e gli header ed i footer verranno ripetuti nel caso una struttura di tabella prosegua su più pagine.

<p:table>

Descrizione

p:table supporta i seguenti attributi:

Attributi

  • columns — Il numero di colonne (celle) che formano una riga della tabella.

  • widths — Le larghezze relative di ciascuna colonna. Dovrebbe esserci un valore per ciascuna colonna. Per esempio: larghezze="2 1 1" dovrebbe indicare che ci sono 3 colonne e la prima colonna è due volte la dimensione della seconda e della terza colonna.

  • headerRows — Il numero iniziale di righe che sono considerate righe dell'header e del footer e devono essere ripetute se la tabella prosegue per più pagine.

  • footerRows — Il numero iniziale di righe che sono considerate righe del footer. Questo valore è sottratto dal valore headerRows. Se il documento ha 2 righe che formano l'header ed una riga che forma il footer, headerRows deve essere impostato a 3 footerRows deve essere impostato a 1.

  • widthPercentage — La percentuale della larghezza della pagina che occupa la tabella.

  • horizontalAlignment — L'allineamento orizzontale della tabella. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i possibili valori)

  • skipFirstHeader

  • runDirection

  • lockedWidth

  • splitRows

  • spacingBefore — Lo spazio bianco da inserire prima dell'elemento.

  • spacingAfter — Lo spazio bianco da inserire dopo l'elemento.

  • extendLastRow

  • headersInEvent

  • splitLate

  • keepTogether

Utilizzo


<p:table columns="3" headerRows="1">
  <p:cell
>name</p:cell>
  <p:cell
>owner</p:cell>
  <p:cell
>size</p:cell>
  <ui:repeat value="#{documents}" var="doc">
    <p:cell
>#{doc.name}</p:cell>
    <p:cell
>#{doc.user.name}</p:cell>
    <p:cell
>#{doc.size}</p:cell>
  </ui:repeat>
</p:table
>

<p:cell>

Descrizione

p:cell supporta i seguenti attributi.

Attributi

  • colspan — Le celle possono proseguire per più di una colonna dichiarando un colspan maggiore di 1. Le tabelle non hanno la possibilità di proseguire su più righe.

  • horizontalAlignment — L'allineamento orizzontale della cella. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i possibili valori)

  • verticalAlignment — L'allineamento verticale della cella. (Si veda Sezione 18.1.7.2, «Valori per l'allineamento» per i possibili valori)

  • padding — Il padding su un particolare lato può essere specificato usando paddingLeft, paddingRight, paddingTop e paddingBottom.

  • useBorderPadding

  • leading

  • multipliedLeading

  • indent

  • verticalAlignment

  • extraParagraphSpace

  • fixedHeight

  • noWrap

  • minimumHeight

  • followingIndent

  • rightIndent

  • spaceCharRatio

  • runDirection

  • arabicOptions

  • useAscender

  • grayFill

  • rotation

Utilizzo


<p:cell
>...</p:cell
>

Charting support is also provided with jboss-seam-pdf.jar. Charts can be used in PDF documents or can be used as images in an HTML page. Charting requires the JFreeChart library (jfreechart.jar and jcommon.jar) to be added to the WEB-INF/lib directory. Four types of charts are currently supported: pie charts, bar charts and line charts. Where greater variety or control is needed, it is possible to construct charts using Java code.

<p:chart>

Descrizione

Displays a chart created in Java by a Seam component.

Attributi

  • chart — The chart object to display.

  • height — L'altezza del grafico.

  • width — La larghezza del grafico.

Utilizzo


<p:chart chart="#{mycomponent.chart}" width="500" height="500" />
         

<p:barchart>

Descrizione

Visualizza un grafico a barre.

Attributi

  • chart — The chart object to display, if programmatic chart creation is being used.

  • dataset — The dataset to be displayed, if programmatic dataset is being used.

  • borderVisible — Controlla se mostrare oppure no un bordo attorno a tutto il grafico.

  • borderPaint — Il colore del bordo, se visibile;

  • borderBackgroundPaint — Il colore di default per lo sfondo del grafico.

  • borderStroke

  • domainAxisLabel — L'etichetta del testo per gli assi del dominio.

  • domainLabelPosition — L'angolo delle etichette di categoria degli assi. I valori validi sono STANDARD, UP_45, UP_90, DOWN_45 e DOWN_90. In alternativa, il valore può essere un valore positivo o negativo in radianti.

  • domainAxisPaint — Il colore dell'etichetta degli assi di dominio.

  • domainGridlinesVisible— Controlla se mostrare oppure no all'interno del grafico le griglie per gli assi del dominio.

  • domainGridlinePaint— Il colore delle griglie di dominio, se visibili.

  • domainGridlineStroke — Lo stile del tratteggio delle griglie di dominio, se visibili.

  • height — L'altezza del grafico.

  • width — La larghezza del grafico.

  • is3D — Un valore booleano che indica di visualizzare il grafico in 3D invece che in 2D.

  • legend — Un valore booleano che indica se oppure no includere la legenda nel grafico.

  • legendItemPaint— Il colore di default delle etichette di testo nella legenda.

  • legendItemBackgoundPaint— Il colore dello sfondo della legenda, se diverso dal colore di sfondo del grafico.

  • legendOutlinePaint— Il colore del bordo attorno alla legenda.

  • orientation — L'orientamento del disegno, o vertical (di default) o horizontal.

  • plotBackgroundPaint— Il colore dello sfondo del disegno.

  • plotBackgroundAlpha— Il livello alfa (trasparenza) dello sfondo del disegno. Dovrebbe essere un numero tra 0 (completamente trasparente) e 1 (completamente opaco).

  • plotForegroundAlpha— Il livello alfa (trasparenza) del disegno. Dovrebbe essere un numero tra 0 (completamente trasparente) e 1 (completamente opaco).

  • plotOutlinePaint— Il colore delle griglie di range, se visibili.

  • plotOutlineStroke — Lo stile del tratteggio delle griglie di range, se visibili.

  • rangeAxisLabel — L'etichetta di testo per l'asse di range.

  • rangeAxisPaint — Il colore per l'etichetta dell'asse di range.

  • rangeGridlinesVisible— Controlla se visualizzare nel grafico oppure no le griglie per l'asse di range.

  • rangeGridlinePaint— Il colore delle griglie di range, se visibili.

  • rangeGridlineStroke — Lo stile del tratteggio delle griglie di range, se visibili.

  • title — Il titolo del grafico.

  • titlePaint— Il colore del titolo del grafico.

  • titleBackgroundPaint— Il colore di sfondo attorno al titolo del grafico.

  • width — La larghezza del grafico.

Utilizzo


<p:barchart title="Bar Chart" legend="true"
            width="500" height="500">
    <p:series key="Last Year">
        <p:data columnKey="Joe" value="100" />
        <p:data columnKey="Bob" value="120" />
    </p:series
>        <p:series key="This Year">
        <p:data columnKey="Joe" value="125" />
        <p:data columnKey="Bob" value="115" />
    </p:series>
</p:barchart
>

<p:linechart>

Descrizione

Mostra un grafico a linea.

Attributi

  • chart — The chart object to display, if programmatic chart creation is being used.

  • dataset — The dataset to be displayed, if programmatic dataset is being used.

  • borderVisible — Controlla se mostrare oppure no un bordo attorno a tutto il grafico.

  • borderPaint — Il colore del bordo, se visibile;

  • borderBackgroundPaint — Il colore di default per lo sfondo del grafico.

  • borderStroke

  • domainAxisLabel — L'etichetta del testo per gli assi del dominio.

  • domainLabelPosition — L'angolo delle etichette di categoria degli assi. I valori validi sono STANDARD, UP_45, UP_90, DOWN_45 e DOWN_90. In alternativa, il valore può essere un valore positivo o negativo in radianti.

  • domainAxisPaint — Il colore dell'etichetta degli assi di dominio.

  • domainGridlinesVisible— Controlla se mostrare oppure no all'interno del grafico le griglie per gli assi del dominio.

  • domainGridlinePaint— Il colore delle griglie di dominio, se visibili.

  • domainGridlineStroke — Lo stile del tratteggio delle griglie di dominio, se visibili.

  • height — L'altezza del grafico.

  • width — La larghezza del grafico.

  • is3D — Un valore booleano che indica di visualizzare il grafico in 3D invece che in 2D.

  • legend — Un valore booleano che indica se oppure no includere la legenda nel grafico.

  • code — Il valore da codificare nel barcode.

  • legendItemBackgoundPaint — Il colore di sfondo della legenda, se diverso dal colore di sfondo del grafico.

  • code — Il valore da codificare nel barcode.

  • orientation — L'orientamento del disegno, o vertical (di default) o horizontal.

  • code — Il valore da codificare nel barcode.

  • plotBackgroundAlpha— Il livello alfa (trasparenza) dello sfondo del disegno. Dovrebbe essere un numero tra 0 (completamente trasparente) e 1 (completamente opaco).

  • plotForegroundAlpha— Il livello alfa (trasparenza) del disegno. Dovrebbe essere un numero tra 0 (completamente trasparente) e 1 (completamente opaco).

  • plotOutlinePaint — Il colore delle griglie di range, se visibili.

  • plotOutlineStroke — Lo stile del tratteggio delle griglie di range, se visibili.

  • rangeAxisLabel — L'etichetta di testo per l'asse di range.

  • rangeAxisPaint — Il colore per l'etichetta dell'asse di range.

  • rangeGridlinesVisible — Controlla se visualizzare nel grafico oppure no le griglie dell'asse di range.

  • rangeGridlinePaint — Il colore delle griglie di range, se visibili.

  • rangeGridlineStroke — Lo stile del tratteggio delle griglie di range, se visibili.

  • title — Il titolo del grafico.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • width — La larghezza del grafico.

Utilizzo


<p:linechart title="Line Chart"
            width="500" height="500">
    <p:series key="Prices">
        <p:data columnKey="2003" value="7.36" />
        <p:data columnKey="2004" value="11.50" />
        <p:data columnKey="2005" value="34.625" />
        <p:data columnKey="2006" value="76.30" />
        <p:data columnKey="2007" value="85.05" />
    </p:series>
</p:linechart
>

<p:piechart>

Descrizione

Mostra un grafico.

Attributi

  • title — Il titolo del grafico.

  • chart — The chart object to display, if programmatic chart creation is being used.

  • dataset — The dataset to be displayed, if programmatic dataset is being used.

  • code — Il valore da codificare nel barcode.

  • legend — Un valore booleano che indica se oppure no includere la legenda nel grafico. Di default è true.

  • is3D — Un valore booleano che indica di visualizzare il grafico in 3D invece che in 2D.

  • labelLinkMargin — Il margine di collegamento per le etichette.

  • labelLinkPaint — Il motivo usato per le linee di collegamento dell'etichetta.

  • code — Il valore da codificare nel barcode.

  • labelLinksVisible — Un flag che controlla se vengono disegnati o no i link di etichetta.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • labelGap — Il gap tra le etichette ed il disegno come percentuale della larghezza del disegno.

  • labelBackgroundPaint — Il colore usato per disegnare lo sfondo delle etichette di sezione. Se null, lo sfondo non viene riempito.

  • code — Il valore da codificare nel barcode.

  • circular — Un valore booleano che indica se il grafico deve essere disegnato come cerchio. Se false, il grafico viene disegnato come ellisse. Di default è true.

  • direction — La direzione in cui vengono disegnate le sezioni della torta. Un valore tra: clockwise o anticlockwise. Il default è clockwise.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • sectionOutlinesVisible — Indica se viene disegnata una outline per ciascuna sezione del disegno.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

  • code — Il valore da codificare nel barcode.

Utilizzo


<p:piechart title="Pie Chart" circular="false" direction="anticlockwise" 
    startAngle="30" labelGap="0.1" labelLinkPaint="red"
>        
    <p:series key="Prices"
> 
        <p:data key="2003" columnKey="2003" value="7.36" /> 
        <p:data key="2004" columnKey="2004" value="11.50" /> 
        <p:data key="2005" columnKey="2005" value="34.625" /> 
        <p:data key="2006" columnKey="2006" value="76.30" /> 
        <p:data key="2007" columnKey="2007" value="85.05" /> 
    </p:series
> 
</p:piechart
>

<p:series>

Descrizione

I dati di categoria posso essere spezzati in serie. I tag delle serie vengono usati per categorizzare un set di dati con serie ed applicare lo stile all'intera serie.

Attributi

  • key — Il nome delle serie.

  • seriesPaint — Il colore di ciascun item nelle serie.

  • seriesOutlinePaint — Il colore dell'outline per ciascun elemento nella serie.

  • seriesOutlineStroke — Il tratteggio usato per disegnare ciascun elemento nella serie.

  • seriesVisible — Un valore booleano che indicare se la serie debba essere visualizzata.

  • seriesVisibleInLegend — Un valore booleano che indicare se la serie debba essere elencata nella legenda.

Utilizzo


<p:series key="data1">
    <ui:repeat value="#{data.pieData1}" var="item">
        <p:data columnKey="#{item.name}" value="#{item.value}" />
    </ui:repeat>
</p:series
>

<p:data>

Descrizione

Il tag dei dati descrive ciascun punto di dati da mostrare nel grafico.

Attributi

  • key — Il nome dell'item di dati.

  • series — I nomi delle serie, quando non è incorporato dentro un <p:series>.

  • value — Il valore numerico dei dati.

  • explodedPercent — Per i grafici a torta, indica come viene esploso un pezzo della torta.

  • sectionOutlinePaint — Per i grafici a barre, il colore dell'outline di sezione.

  • sectionOutlineStroke — Per i grafici a barre, il tipo di tratteggio dell'outline di sezione.

  • sectionPaint — Per i grafici a barre, il colore della sezione.

Utilizzo


<p:data key="foo" value="20" sectionPaint="#111111" 
        explodedPercent=".2" />
<p:data key="bar" value="30" sectionPaint="#333333" />
<p:data key="baz" value="40" sectionPaint="#555555" 
        sectionOutlineStroke="my-dot-style" />

<p:color>

Descrizione

Il componente del colore dichiara un colore oppure un gradiente che può venire referenziato quando si disegnano forme piene.

Attributi

  • color — Il valore del colore. Per gradienti di colore, questo è il colore iniziale. Sezione 18.1.7.1, «Valori dei colori»

  • color2 — Per gradienti di colore, questo è il colore che termina il gradiente.

  • point — Le coordinate dove inizia il colore gradiente.

  • point2 — Le coordinate dove finisce il colore gradiente.

Utilizzo


<p:color id="foo" color="#0ff00f"/>
<p:color id="bar" color="#ff00ff" color2="#00ff00" 
                  point="50 50" point2="300 300"/>

<p:stroke>

Descrizione

Descrive un tratteggio usato per disegnare le linee in un grafico.

Attributi

  • width — La larghezza del tratteggio.

  • cap — Il tipo di line cap. Valori validi sono butt, round e square

  • join — Il tipo di line join. Valori validi sono miter, round e bevel

  • miterLimit — Per join di tipo miter, questo valore è il limite della dimensione del join.

  • dash — Il valore del dash imposta il pattern dash da usare per disegnare la linea. Gli interi separati da spazio indicano la lunghezza di ciascun segmento in modo alternato disegnato e non disegnato.

  • dashPhase — La fase di dash indica l'offset nel pattern dash che con cui la linea dovrebbe essere disegnata.

Utilizzo


<p:stroke id="dot2" width="2" cap="round" join="bevel" dash="2 3" />

Se si ha un PDF complesso pregenerato con campi definiti (con nome), lo si può facilmente riempire con valori dell'applicazione e presentarlo all'utente.

<p:form>

Descrizione

Definisce un modello di form da popolare

Attributi

  • URL — Un URL che punta al file PDF da usarsi come template. Se il valore non ha la partedi protocollo (://), il file viene letto localmente.

  • filename — Il nome del file da usare per il file PDF generato.

  • exportKey — Colloca un file PDF generato in un oggetto DocumentData sotto la chiave specificata nel contesto evento. Se impostato, non avverrà alcun redirect.

<p:field>

Descrizione

Unisce un nome campo al suo valore

Attributi

  • name — Il nome del campo.

  • value — Il valore del campo.

  • readOnly — Il campo deve essere di sola lettura? Di default è true.



                                <p:form
                                       xmlns:p="http://jboss.com/products/seam/pdf"
                                       URL="http://localhost/Concept/form.pdf">
                                    <p:field name="person.name" value="Me, myself and I"/>
                                </p:form>
                        
                

La generazione del documento funziona senza nessuna ulteriore configurazione. Comunque ci sono alcuni punti della configurazione necessari per applicazioni più serie.

L'implementazione di default serve i documenti PDF da un URL generico, /seam-doc.seam. Molti browser (e utenti) preferiscono vedere URL che contengono il vero nome del PDF, come /myDocument.pdf. Questa capacità richiede una configurazione. Per servire file PDF, tutte le risorse *.pdf devono essere mappate sul DocumentStoreServlet:


<servlet>
    <servlet-name
>Document Store Servlet</servlet-name>
    <servlet-class
>org.jboss.seam.document.DocumentStoreServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name
>Document Store Servlet</servlet-name>
    <url-pattern
>*.pdf</url-pattern>
</servlet-mapping
>

L'opzione use-extensions nel componente document store completa la funzionalità istruendo il document store su come generare l'URL con l'estensione corretta del nome per i tipi di documenti da generare.


<components xmlns="http://jboss.com/products/seam/components"
    xmlns:document="http://jboss.com/products/seam/document"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://jboss.com/products/seam/document http://jboss.com/products/seam/document-2.1.xsd
        http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">
    <document:document-store use-extensions="true"/>
</components
>

Document store memorizza i documenti nello scope conversazione, ed i documenti scadranno quando termina la conversazione. A quel punto i riferimenti al documento saranno invalidati. Si può specificare la vista di default da mostrare quando un documento non esiste usando la proprietà error-page di documentStore.


<document:document-store use-extensions="true" error-page="/documentMissing.seam" />

Seam supporta inoltre la generazione di fogli elettronici the Microsoft® Excel® spreadsheet application tramite l'eccellente libreria JExcelAPI . Il documento generato è compatibile con the Microsoft® Excel® spreadsheet application versione 95, 97, 2000, XP e 2003. Attualmente viene esposto un limitato set di funzionalità di questa libreria, ma lo scopo finale è riuscire a fare tutto ciò che la libreria consente. Si prega di fare riferimento alla documentazione di JExcelAPI per ulteriori informazioni, possibilità e limitazioni.

The Microsoft® Excel® spreadsheet application jboss-seam-excel.jar. Questo JAR contiene i controlli the Microsoft® Excel® spreadsheet application JSF, che vengono impiegati per costruire le viste che possono generare il documento, ed il componente DocumentStore, che serve il documento generato all'utente. Per includere il supporto ad the Microsoft® Excel® spreadsheet application nella propria applicazione, si includa jboss-seam-excel.jar nella directory WEB-INF/lib assieme al file jxl.jar. Inoltre, occorre configurare il servlet DocumentStore in web.xml.

Il modulo The Microsoft® Excel® spreadsheet application di Seam richiede l'uso di Facelets come tecnologia per la vista. Inoltre richiede l'uso del pacchetto seam-ui.

Il progetto examples/excel contiene un esempio del supporto the Microsoft® Excel® spreadsheet application in azione. Mostra come eseguire il correttto impacchettamento per il deploy e mostra le funzionalità esposte.

La personalizzazione del modulo per supportare altri tipi di API the Microsoft® Excel® spreadsheet application è diventata molto semplice. Si implementi l'interfaccia ExcelWorkbook, e si registri in components.xml.


<excel:excelFactory>
   <property name="implementations">
      <key
>myExcelExporter</key>
      <value
>my.excel.exporter.ExcelExport</value>
   </property>
</excel:excelFactory
>
      

e si registri il namespace excel nei tag dei componenti con


xmlns:excel="http://jboss.com/products/seam/excel"

Poi si imposti il tipo UIWorkbook per myExcelExporter e verrà usato il proprio esportatore. Di default è "jxl", ma è stato aggiunto anche il supporto per CSV, usando il tipo "csv".

Si veda Sezione 18.6, «Configurazione di iText» per informazioni su come configurare il document servlet per servire i documenti con estensione .xls.

Se si incontrano problemi nell'accedere al file generato con IE (specialmente con https), ci si assicuri di non avere messo restrizioni nel browser (si veda http://www.nwnetworks.com/iezones.htm/), restrizioni di sicurezza in web.xml oppure una combinazione delle due.

L'uso base del supporto ai fogli elettronici è molto semplice; viene usato come il familiare <h:dataTable> e si può associare una List, Set, Map, Array o DataModel.



            <e:workbook xmlns:e="http://jboss.com/products/seam/excel">
               <e:worksheet>
                  <e:cell column="0" row="0" value="Hello world!"/>
               </e:worksheet>
            </e:workbook>
        
      

Non è molto utile, guardiamo quindi ai casi più comuni:



            <e:workbook xmlns:e="http://jboss.com/products/seam/excel">
               <e:worksheet value="#{data}" var="item">
                  <e:column>
                     <e:cell value="#{item.value}"/>
                  </e:column>
               </e:worksheet>
            </e:workbook>
         
      

In primo luogo si ha a livello più alto un elemento workbook, che serve come container e non ha attributi. L'elemento figlio worksheet ha due attributi; value="#{data}" è il binding EL ai dati e var="item" è il nome dell'elemento corrente. Innestato dentro worksheet c'è una singola colonna e dentro essa si vede la cella, che è l'associazione finale ai dati dell'elemento iterato.

Questo è ciò che serve sapere per cominciare a mettere dati nei fogli elettronici!

I workbook sono i padri di livello più alto dei worksheet e dei link ai fogli di stile.

<e:workbook>

Attributi

  • type — Definisce quale module esporto usare. Il valore è una string e può essere o "jxl" o "csv". Il default è "jxl".

  • description — La descrizione del link. Il valore è una stringa.

  • arrayGrowSize — L'ammontare di memoria con cui incrementare la memoria allocata per la memorizzazione dei dati di workbook. Per processi che leggono molti workbook piccoli dentro un WAS, può essere necessario ridurre la dimensione di default. Il valore di default è 1 megabyte. Il valore è un numero (byte).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • characterSet — Il set di caratteri. Questo viene usato solo quando viene letto il foglio elettronico, e non ha alcun effetto quando viene scritto. Il valore è una stringa (codifica del set di caratteri).

  • description — La descrizione del link. Il valore è una stringa.

  • excelDisplayLanguage — Il linguaggio in cui viene mostrato il file generato. Il valore è una stringa (codice a due caratteri ISO 3166 del paese).

  • description — La descrizione del link. Il valore è una stringa.

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • initialFileSize — Ammontare iniziale di memoria allocata per memorizzare i dati del workbook quando viene letto un foglio elettronico. Per processi che leggono molti workbook piccoli dentro un WAS può essere necessario ridurre la dimensione di default. Il valore di default è 5 megabytes. Il valore è un numero (byte).

  • locale — Il locale usato da JExcelApi per generare il foglio elettronico. Impostare questo valore non ha effetti sulla lingua o la regione del file excel generato. Il valore è una stringa.

  • mergedCellCheckingDisabled — Se il controllo della cella unita debba essere disabilitato. Il valore è un booleano.

  • description — La descrizione del link. Il valore è una stringa.

  • propertySets — I set di proprietà (come le macro) devono essere abilitati per essere copiati assieme al workbook? Lasciando abilitato questa caratteristica, il processo JXL userà più memoria. Il valore è un booleano.

  • rationalization — I formati delle celle devono essere razionalizzati prima di scrivere il foglio? Il valore è un booleano. Il default è true.

  • supressWarnings — Gli avvisi devono essere soppressi? A causa di un cambiamento nella versione 2.4 del logging, ora il comportamento degli avvisi è impostato a livello di JVM (dipende dal tipo di logger usato). Il valore è un booleano.

  • temporaryFileDuringWriteDirectory — Usato in congiunzione con useTemporaryFileDuringWrite per impostare la directory target per i file temporanei. Il valore può essere NULL, nel qual caso viene usata la directory temporanea di default del sistema. Il valore è una stringa (la directory in cui scrivere i file temporanei).

  • useTemporaryFileDuringWrite — Se un file temporaneo debba essere usato durante la generazione del workbook. Se non impostato, il workbook avverrà interamente in memoria. Impostando questo flag si valuti il trade-off tra l'uso della memoria e la performance. Il valore è un booleano.

  • bottomMargin — Il margine inferiore. Il valore è un numero (pollici).

  • filename — Il nome del file da usare per il download. Il valore è una stringa. Si noti che se si mappa il DocumentServlet su qualche pattern, questa estensione del file deve corrispondere.

  • exportKey — Una chiave sotto cui memorizzare i dati di risultato in un oggetto DocumentData nello scope evento. Se usato, non c'è redirezione.

Elementi figli

Facets

  • nessuno



            <e:workbook>
               <e:worksheet>
                  <e:cell value="Hello World" row="0" column="0"/>
               </e:worksheet>
            <e:workbook>
         
      

definisce un workbook con un foglio di lavoro ed un saluto in A1

I worksheet sono figli dei workbook e padri delle colonne e dei comandi worksheet. Possono anche contenere celle, formule, immagini e collegamenti esplicitamente collocati. Sono le pagine che creano il workbook.

<e:worksheet>

  • value — Espressione-EL sui dati retrostanti. Il valore è una stringa. Il target di quest'espressione è esaminato per un Iterable. Si noti che se il target è una Map, l'iterazione viene fatta su Map.Entry entrySet(), e quindi occorre usare un .key o un .value per individuare i riferimenti.

  • var — Il nome della variabile della riga iteratore corrente che può successivamente essere referenziata negli attributi di valore della cella. Il valore è una stringa.

  • name — Il nome del worksheet. Il valore è una stringa. Il default è Sheet# dove # è l'indice del worksheet. Se esiste il nome del worksheet dato, tale foglio viene selezionato. Questo può essere usato per unire diversi set di dati in un singolo worksheet, solamente definendo lo stesso nome per questi (usando startRow e startCol per assicurarsi che non occupino lo stesso spazio).

  • startRow — Definisce la riga di inizio dei dati. Il valore è un numero. Usato per collocare i dati in altri posti rispetto all'angolo in alto a sinistra (è utile in particolare se si hanno set di dati multipli per un singolo worksheet). Il default è 0.

  • startColumn — Definisce la colonna di inizio dei dati. Il valore è un numero. Usato per collocare i dati in altri posti rispetto all'angolo in alto a sinistra (è utile in particolare se si hanno set di dati multipli per un singolo worksheet). Il default è 0.

  • automaticFormulaCalculation — Le formule devono essere automaricamente ricalcolate? Il valore è un booleano.

  • bottomMargin — Il margine inferiore. Il valore è un numero (pollici).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • normalMagnification — Il fattore di ingrandimento normale (non zoom o fattore di scala). Il valore è un numero (percentuale).

  • description — La descrizione del link. Il valore è una stringa.

  • pageBreakPreviewMagnification — Il fattore di ingrandimento dell'anteprima per l'interruzione di pagina (non fattori di zoom o di scala). Il valore è un numero (percentuale).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • paperSize — La dimensione dellapagina da usare quando si stampa questo foglio. Il valore è una stringa che può essere un valore tra "a4", "a3", "letter", "legal" ecc. (Si veda jxl.format.PaperSize ).

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • bottomMargin — Il margine inferiore. Il valore è un numero (pollici).

  • recalculateFormulasBeforeSave — Le formule devono essere ricalcolate quando viene salvato il foglio? Il valore è un boolean. Il default è false.

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

  • zoomFactor — Fattore di zoom. Da non confondere con il fattore di zoom (che è relazionato alla vista dello schermo) con il fattore di scala (che si riferisce al fattore di scala in fase di stampa). Il valore è un numero (percentuale).

Elementi figli

Facets

  • header— I contenuti che verranno messi in cima al blocco di dati, sopra le intestazioni della colonna (se presenti).

  • footer— I contenuti che verranno messi in fondo al blocco di dati, sotto i pié di pagina della colonna (se presenti).



            <e:workbook>
               <e:worksheet name="foo" startColumn="1" startRow="1">
                      <e:column value="#{personList}" var="person">
                         <f:facet name="header">
                            <e:cell value="Last name"/>
                         </f:facet>
                     <e:cell value="#{person.lastName}"/>
                  </e:column>
               </e:worksheet>
            <e:workbook>
         
      

definisce un worksheet con nome "foo", che inizia in B2.

Le colonne sono figle dei worksheet e padri delle celle, immagini, formule e collegamenti. Sono la struttura che controlla l'iterazione dei dati del worksheet. Si veda Sezione 19.14.5, «Impostazioni colonna» per la formattazione.

<e:column>

Attributi

  • nessuno

Elementi figli

Facets

  • header — Questo facet può contenere un <e:cell>, <e:formula>, <e:image> o <e:hyperLink> che verrà usato come intestazione per la colonna.

  • footer — Questo facet può contenere un <e:cell>, <e:formula>, <e:image> o <e:hyperLink> che verrà usato come pié di pagina per la colonna.



            <e:workbook>
                   <e:worksheet>
                      <e:column value="#{personList}" var="person">
                         <f:facet name="header">
                            <e:cell value="Last name"/>
                         </f:facet>
                         <e:cell value="#{person.lastName}"/>
                      </e:column>
                   </e:worksheet>
            <e:workbook>
         
      

definisce una colonna con un intestazione ed un output iterato.

Le celle sono innestate dentro le colonne (per l'iterazione) o dentro i worksheet (per il collocamento diretto usando gli attributi column e row) e sono responsabili dell'output del valore (solitamente tramite un'espressione EL che coinvolge l'attributo var della datatable. Vedere ???

<e:cell>

Attributi

  • column — La colonna dove mettere la cella. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da 0.

  • row — La riga dove mettere la cella. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da 0.

  • value — Il valore da mostrare. Solitamente un'espressione-EL che fa riferimento all'attributo della variabile della datatable che lo contiene. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • rightMargin — Il margine destro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

Elementi figli

Facets

  • nessuno



            <e:workbook>
               <e:worksheet
>         
                  <e:column value="#{personList}" var="person">
                     <f:facet name="header">
                            <e:cell value="Last name"/>
                         </f:facet>
                         <e:cell value="#{person.lastName}"/>
                      </e:column>
               </e:worksheet>
            </e:workbook
>     
         
      

definisce una colonna con un intestazione ed un output iterato.

Le validazioni vengono innestate dentro celle o formule. Esse aggiungono vincoli ai dati di cella.

<e:numericValidation>

Attributi

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

    • "equal" - richiede che il valore della cella corrisponda a quello definito nell'attributo value

    • "greater_equal" - richiede che il valore della cella sia maggiore o uguale al valore definito nell'attributo value

    • "less_equal" - richiede che il valore della cella sia minore o uguale al valore definito nell'attributo value

    • "less_than" - richiede che il valore della cella sia minore del valore definito nell'attributo value

    • "not_equal" - richiede che il valore della cella non corrisponda al valore definito nell'attributo value

    • "between" - richiede che il valore della cella sia compreso tra i valori definiti negli attributi value e value2

    • "not_between" - richiede che il valore della cella non sia compreso tra i valori definiti negli attributi value e value2

Elementi figli

  • nessuno

Facets

  • nessuno



               <e:workbook>
                  <e:worksheet>
                     <e:column value="#{personList}" var="person"
>                   
                        <e:cell value="#{person.age">
                           <e:numericValidation condition="between" value="4" 
                              value2="18"/>
                        </e:cell>
                     </e:column>
                  </e:worksheet>
               </e:workbook
>            
            
         

aggiunge la validazione numerica alla cella specificando che il valore deve essere tra 4 e 18.

<e:rangeValidation>

Attributi

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

Elementi figli

  • nessuno

Facets

  • nessuno



               <e:workbook>
                  <e:worksheet>
                     <e:column value="#{personList}" var="person"
>                   
                            <e:cell value="#{person.position">
                           <e:rangeValidation startColumn="0" startRow="0" 
                              endColumn="0" endRow="10"/>
                        </e:cell>
                     </e:column>
                  </e:worksheet>
               </e:workbook
>            
            
         

aggiunge la validazione ad una cella specificando che il valore deve essere in valori specificati nel range A1:A10.

<e:listValidation>

Attributi

  • nessuno

Elementi figli

  • Zero o più elementi di validazione lista.

Facets

  • nessuno

e:listValidation è solo un container per mantenere diversi tag e:listValidationItem.

<e:listValidationItem>

Attributi

  • value — Un valore da validare.

Elementi figli

  • nessuno

Facets

  • nessuno



               <e:workbook>
                  <e:worksheet>
                     <e:column value="#{personList}" var="person"
>            
                        <e:cell value="#{person.position">
                           <e:listValidation>
                              <e:listValidationItem value="manager"/>
                              <e:listValidationItem value="employee"/>
                           </e:listValidation>
                        </e:cell>
                     </e:column>
                  </e:worksheet>
               </e:workbook
>              
            
         

aggiunge la valodazione ad una cella specificando che il valore deve essere "manager" o "employee".

Le formule sono innestate nelle colonne (per iterazione) o dentro i worksheet (per collocazione diretta usando gli attributi column e row) e aggiungono calcoli o funzioni ai range di celle. Sono essenzialmente celle, si veda Sezione 19.6, «Celle» per gli attributi disponibili. Si noti che possono impiegare template ed avere definizioni proprie di font, ecc. come le normali celle.

La formula della cella è collocata nell'attributo value come una normale annotazione the Microsoft® Excel® spreadsheet application. Si noti che quando si fanno formule con riferimenti ad altri fogli, i worksheet devo esistere prima di eseguire riferimenti ad essa. Il valore è una stringa.



            <e:workbook>
               <e:worksheet name="fooSheet">
                  <e:cell column="0" row="0" value="1"/>
               </e:worksheet>
               <e:worksheet name="barSheet">
                  <e:cell column="0" row="0" value="2"/>
                  <e:formula column="0" row="1" 
                     value="fooSheet!A1+barSheet1!A1">
                     <e:font fontSize="12"/>
                  </e:formula>
               </e:worksheet>
            </e:workbook
>         
         
      

definisce una formula in B2 che somma le celle A1 nei worksheet FooSheet e BarSheet.

Le immagini sono innestate dentro le colonne (per iterazione) o dentro i worksheet (per collocazione diretta usando gli attributi startColumn/startRow e rowSpan/columnSpan). Gli span sono opzionali e se omessi, l'immagine verrà inserita senza ridimensionamento.

<e:image>

Attributi

  • startColumn — La colonna iniziale dell'immagine. Il default è il contatore interno. Il valore è un numero. Si noti che il valore inizia da zero.

  • startRow — La riga iniziale dell'immagine. Il default è il contatore interno. Il valore è un numero. Si noti che il valore inizia da zero.

  • columnSpan — Lo span di colonna dell'immagine. Il default è quello risultante nella larghezza di default dell'immagine. Il default è un float.

  • rowSpan — Lo span di colonna dell'immagine. Il default è quello risultante nella altezza di default dell'immagine. Il default è un float.

  • description — La descrizione del link. Il valore è una stringa.

Elementi figli

  • nessuno

Facets

  • nessuno



            <e:workbook>
               <e:worksheet>
                  <e:image startRow="0" startColumn="0" rowSpan="4" 
                     columnSpan="4" URI="http://foo.org/logo.jpg"/>
               </e:worksheet>
            </e:workbook
>           
         
      

definisce un'immagine in A1:A5 basato sui dati in questione

Gli hyperlink sono innestati dentro le colonne (per iterazione) o dentro i worksheet (per collocazione diretta usando gli attributi startColumn/startRow e endColumn/endRow). Aggiungono agli URI una navigazione tramite link.

<e:hyperlink>

Attributi

  • startColumn — La colonna iniziale del hyperlink. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da zero.

  • startRow — La riga iniziale del hyperlink. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da zero.

  • endColumn — La colonna finale del hyperlink. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da zero.

  • endRow — La riga finale del hyperlink. Il default è un contatore interno. Il valore è un numero. Si noti che il valore parte da zero.

  • description — La descrizione del link. Il valore è una stringa.

  • description — La descrizione del link. Il valore è una stringa.

Elementi figli

  • nessuno

Facets

  • nessuno



            <e:workbook>
               <e:worksheet>
                  <e:hyperLink startRow="0" startColumn="0" endRow="4" 
                     endColumn="4" URL="http://seamframework.org" 
                     description="The Seam Framework"/>
               </e:worksheet>
            </e:workbook
>          
         
      

definisce un hyperlink che punta a SFWK nell'area A1:E5

Intestazioni e pié di pagina sono figli dei fogli di lavoro e contengono facet che a loro volta contengono una stringa con i comandi da analizzare.

<e:header>

Attributi

  • nessuno

Elementi figli

  • nessuno

Facets

  • left — I contenuti della parte sinistra di intestazione/pié di pagina.

  • center — i contenuti della parte centrale di intestazione/pié di pagina.

  • right — i contenuti della parte destra di intestazione/pié di pagina.

<e:footer>

Attributi

  • nessuno

Elementi figli

  • nessuno

Facets

  • left — I contenuti della parte sinistra di intestazione/pié di pagina.

  • center — i contenuti della parte centrale di intestazione/pié di pagina.

  • right — i contenuti della parte destra di intestazione/pié di pagina.

Il contenuto dei facets è una stringa che può contenere vari comandi delimitati da # come segue:

<e:workbook> <e:worksheet> <e:hyperLink startRow="0" startColumn="0" endRow="4" endColumn="4" URL="http://seamframework.org" description="The Seam Framework"/> </e:worksheet> </e:workbook >

Inserisce la data corrente

#page_number#

Inserisce il numero della pagina corrente

#time#

Inserisce l'orario corrente

#total_pages#

Inserisce il conteggio totale della pagina

#worksheet_name#

Inserisce il nome del worksheet

#workbook_name#

Inserisce il nome del workbook

#bold#

Attiva il font in grassetto, usare un altro #bold# per disattivarlo

#italics#

Attiva il font in corsivo, usare un altro #italic# per disattivarlo

#underline#

Attiva la sottolineatura, usare un altro #underline# per disattivarla

#double_underline#

Attiva la doppia sottolineatura, usare un altro #double_underline# per disattivarla

#outline#

Attiva il font outline, usare un altro #outline# per disattivarlo

#shadow#

Attiva il font ombreggiato, usare un altro #shadow# per disattivarlo

#strikethrough#

Attiva il font barrato, usare un altro #strikethrough# per disattivarlo

#subscript#

Attiva il font subscripted, usare un altro #subscript# per disattivarlo

#superscript#

Attiva il font superscript, usare un altro #superscript# per disattivarlo

#font_name#

Imposta il nome dei font, si usa come #font_name=Verdana#

#font_size#

Imposta la dimensione dei font, si usa come #font_size=12#



            <e:workbook>
               <e:worksheet
>         
                  <e:header>
                     <f:facet name="left">
                        This document was made on #date# and has #total_pages# pages
                     </f:facet>
                     <f:facet name="right">
                        #time#
                     </f:facet>
                  </e:header>
               <e:worksheet>
            </e:workbook>
         
      

Stampa le aree ed i titoli figli dei worksheet e dei templatee fornisce...stampa aree e titoli.

<e:printArea>

Attributi

  • firstColumn — La colonna dell'angolo in alto a sinistra dell'area. Il parametro è un numero. Si noti che il valore parte da zero.

  • firstRow — La riga dell'angolo in alto a sinistra dell'area. Il parametro è un numero. Si noti che il valore parte da zero.

  • lastColumn — La colonna dell'angolo in basso a destra dell'area. Il parametro è un numero. Si noti che il valore parte da zero.

  • lastRow — La riga dell'angolo in basso a destra dell'area. Il parametro è un numero. Si noti che il valore parte da zero.

Elementi figli

  • nessuno

Facets

  • nessuno



            <e:workbook>
               <e:worksheet
>            
                  <e:printTitles firstRow="0" firstColumn="0" 
                     lastRow="0" lastColumn="9"/>
                  <e:printArea firstRow="1" firstColumn="0" 
                     lastRow="9" lastColumn="9"/>
               </e:worksheet>
            </e:workbook>
         
      

definisce un titolo di stampa tra A1:A10 ed un'area di stampa tra B2:J10.

I comandi per i fogli di lavoro sono figli dei workbook e vengono solitamente eseguiti solo una volta.

Fornisce un raggruppamento di colonne e righe.

<e:groupRows>

Attributi

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

Elementi figli

  • nessuno

Facets

  • nessuno

<e:groupColumns>

Attributi

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • description — La descrizione del link. Il valore è una stringa.

Elementi figli

  • nessuno

Facets

  • nessuno



               <e:workbook>
                  <e:worksheet
>            
                     <e:groupRows startRow="4" endRow="9" collapse="true"/>
                     <e:groupColumns startColumn="0" endColumn="9" collapse="false"/>
                  </e:worksheet>
               </e:workbook>
            
         

raggruppa dalla riga 5 alla 10 e dalla colonna 5 alla 10 in modo che le righe siano inizialmente collassate (ma non le colonne).

Fornisce l'unione delle celle

<e:mergeCells>

Attributi

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • topMargin — Il margine superiore. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

  • leftMargin — Il margine sinistro. Il valore è un numero (pollici).

Elementi figli

  • nessuno

Facets

  • nessuno



               <e:workbook>
                  <e:worksheet>
                     <e:mergeCells startRow="0" startColumn="0" endRow="9" endColumn="9"/>
                  </e:worksheet>
               </e:workbook
>            
            
         

unisce le celle nel range A1:J10

Il controllo dell'aspetto dell'output è fatto con una combinazione di attributi stile-CSS e di attributi tag. I più comuni (font, bordi, background, ecc.) sono CSS ed alcune impostazioni generali sono negli attributi tag.

Gli attributi CSS vanno in cascata dal padre ai figli e da un tag si scende in cascata attraverso classi CSS referenziate negli attributi styleClass ed infine lungo gli attributi CSS definiti nell'attributo style. Si può collocarli ovunque, ma ad esempio impostare la larghezza di colonna in una cella innestata dentro tale colonna ha poco senso.

Questo gruppo di attributi XLS-CSS definisce un font ed i suoi attributi

xls-font-family

Il nome del font. Assicurarsi che sia supportato del proprio sistema.

xls-font-size

La dimensione del font. Si usi un numero intero.

xls-font-color

Il colore dei font (si veda jxl.format.Colour ).

xls-font-bold

Il font deve essere in grassetto? I valori validi sono "true" and "false"

xls-font-italic

Il font deve essere in corsivo? I valori validi sono "true" and "false"

xls-font-script-style

The script style of the font (see jxl.format.ScriptStyle ).

xls-font-underline-style

Lo stile sottolineato del font (si veda jxl.format.UnderlineStyle ).

xls-font-struck-out

Il font deve essere barrato? I valori validi sono "true" e "false"

xls-font

Una notazione breve per impostare tutti i valori. Si collochi il nome del font in fondo e si usino i tick mark per i font separati da uno spazio, es. 'Times New Roman'. Si usi "italic", "bold" e "struckout".

Esempio style="xls-font: red bold italic 22 Verdana"

Questo gruppo di attributi XLS-CSS definisce i bordi della cella

xls-border-left-color

Il colore del bordo del lato sinistro della cella (si veda jxl.format.Colour ).

xls-border-left-line-style

Lo stile di linea del bordo del lato sinistro della cella (si veda jxl.format.LineStyle ).

xls-border-left

Una notazione breve per impostare lo stile della linea ed il colore del lato sinistro della cella, es. style="xls-border-left: thick red"

xls-border-top-color

Il colore del bordo del lato alto della cella (si veda jxl.format.Colour ).

xls-border-top-line-style

Lo stile di linea del bordo del lato alto della cella (si veda jxl.format.LineStyle ).

xls-border-top

Una notazione breve per impostare lo stile della linea ed il colore del lato alto della cella, es. style="xls-border-top: red thick"

xls-border-right-color

Il colore del bordo del lato destro della cella (si veda jxl.format.Colour ).

xls-border-right-line-style

Lo stile di linea del bordo del lato destro della cella (si veda jxl.format.LineStyle ).

xls-border-right

Una notazione breve per impostare lo stile della linea ed il colore del lato destro della cella, es. style="xls-border-right: thick red"

xls-border-bottom-color

Il colore del bordo del lato basso della cella (si veda jxl.format.Colour ).

xls-border-bottom-line-style

Lo stile di linea del bordo del lato basso della cella (si veda jxl.format.LineStyle ).

xls-border-bottom

Una notazione breve per impostare lo stile della linea ed il colore del lato basso della cella, es. style="xls-border-bottom: thick red"

xls-border

Una notazione breve per impostare lo stile della linea ed il colore per tutti i lati della cella, es. style="xls-border: thick red"

Questo gruppo di attributi XLS-CSS definisce il background della cella

xls-background-color

Il colore del background (si veda jxl.format.LineStyle ).

xls-background-pattern

Il pattern del background (si veda jxl.format.Pattern ).

xls-background

Una notazione breve per impostare il pattern ed il colore del background. Vedere sopra per le regole.

Questo gruppo di attributi XLS-CSS definisce le larghezze di colonna, ecc.

xls-column-width

La larghezza della colonna. Si usino valori ampi (~5000) per cominciare. Usata da e:column nella modalità xhtml.

xls-column-widths

La larghezza della colonna. Si usino valori ampi (~5000) per cominciare. Usata dall'excel exporter, collocato nell'attributo style della datatable. Si usino valori numerici o * per bypassare una colonna.

Esempio style="xls-column-widths: 5000, 5000, *, 10000"

xls-column-autosize

Deve essere fatto un tentativo per autodimensionare la colonna? I valori validi sono "true" and "false".

xls-column-hidden

La colonna deve essere nascosta? I valori validi sono "true" e "false".

xls-column-export

La colonna deve essere mostrata nell'esportazione? I valori validi sono "true" e "false". Il valore di default è "true".

Questo gruppo di attributi XLS-CSS definisce le proprietà della cella

xls-alignment

L'allineamento del valore della cella (si veda jxl.format.Alignment ).

xls-force-type

Il tipo forzato dei dati della cella. Il valore è una stringa che può essere una tra "general", "number", "text", "date", "formula" o "bool". Il tipo è automaticamente individuato e quindi quest'attributo si usa raramente.

xls-format-mask

La maschera di formato della cella, si veda Sezione 19.6.2, «Maschere per il formato»

xls-indentation

L'indentazione del valore della cella. Il valore è numerico.

xls-locked

Se la cella debba essere bloccata. Da usare con il livello workbook bloccato. I valori validi sono "true" e "false".

xls-orientation

L'orientamento del valore della cella (si veda jxl.format.Orientation ).

xls-vertical-alignment

L'allineamento verticale del valore della cella (si veda jxl.format.VerticalAlignment ).

xls-shrink-to-fit

I valori della cella devono adattarsi alla dimensione? I valori validi sono "true" e "false".

xls-wrap

La cella deve contenere nuove linee? I valori validi sono "true" e "false".

Il nucleo delle funzionalità the Microsoft® Excel® spreadsheet application è basato sull'eccellente libreria JExcelAPI che può essere trovata in http://jexcelapi.sourceforge.net/ e la maggior parte delle funzionalità e le possibili limitazioni sono ereditate da qua.

Se si usano il forum o la mailing list, si prega di ricordare che non questi non possono fornire informazioni su come Seam impiega la loro libreria, ogni problema dovrebbe essere riportato in JBoss Seam JIRA sotto il modulo "excel".

Con Seam è semplice gestire i feed RSS tramite la libreria YARFRAW. Il supporto RSS in questa versione è da considerarsi nello stato di anteprima.

Il progetto examples/rss contiene un esempio del supporto RSS in azione. Mostra il modo appropriato per posizionare le librerie e illustra le funzionalità esposte.

Un feed è una pagina xhtml che consiste in un feed e una lista di elementi annidati.



            <r:feed 
               xmlns="http://www.w3.org/1999/xhtml" 
               xmlns:ui="http://java.sun.com/jsf/facelets" 
               xmlns:r="http://jboss.com/products/seam/rss"
               title="#{rss.feed.title}"
               uid="#{rss.feed.uid}"
               subtitle="#{rss.feed.subtitle}"
               updated="#{rss.feed.updated}"
               link="#{rss.feed.link}">
               <ui:repeat value="#{rss.feed.entries}" var="entry">
                  <r:entry
                     uid="#{entry.uid}"
                     title="#{entry.title}"
                     link="#{entry.link}"
                     author="#{entry.author}"
                     summary="#{entry.summary}"
                     published="#{entry.published}"
                     updated="#{entry.updated}"
                          />
                   </ui:repeat>
            </r:feed>
        
      

I feed sono delle entità di primo livello che descrivono le proprietà della sorgente di informazioni. Possono contenere uno o più elementi.

<r:feed>

Attributi

  • uid — Un identificativo unico opzionale. Il valore è una stringa.

  • title — Il titolo del feed. Il valore è una stringa.

  • subtitle — Il sottotitolo del feed. Il valore è una stringa.

  • updated — Quando è stato aggiornato il feed? Il valore è una data.

  • link — Il link alla fonte dell'informazione. Il valore è una stringa.

  • feedFormat — Il formato del feed. Il valore è una stringa con default ATOM1. I valori ammessi sono RSS10, RSS20, ATOM03 e ATOM10.

Elementi contenuti

  • Zero o più elementi

Facets

  • nessuna

Gli elementi rappresentano i "titoli" del feed.

<r:feed>

Attributi

  • uid — Un identificativo unico opzionale. Il valore è una stringa.

  • title — Il titolo dell'elemento. Il valore è una stringa.

  • link — Un link all'articolo. Il valore è una stringa.

  • author — L'autore dell'articolo. Il valore è una stringa.

  • summary — Il corpo dell'articolo. Il valore è una stringa.

  • textFormat — Il formato del corpo e del titolo dell'articolo. Il valore è una stringa e i valori ammessi sono "text" e "html". Il valore di default è "html".

  • published — Quando è stato pubblicato l'articolo? Il valore è una data.

  • updated — Quando è stato aggiornato l'articolo? Il valore è una data.

Elementi contenuti

  • nessuna

Facets

  • nessuna

Seam include ora dei componenti opzionali per modellare e spedire le email.

Il supporto email è fornito da jboss-seam-mail.jar. Questo JAR contiene i controlli JSF per la mail, che vengono impiegati per costruire le email, ed il componente manager mailSession.

Il progetto examples/mail contiene un esempio di supporto email in azione. Mostra un pacchetto idoneo e contiene esempi che mostrano le funzionalità chiave attualmente supportate.

Si può testare la propria mail usando l'ambiente di test di Seam. Si veda Sezione 36.3.4, «Test d'integrazione di Seam Mail».

Non occorre imparare un nuovo linguaggio di template per usare Seam Mail — un'email è semplicemente un facelet!


<m:message xmlns="http://www.w3.org/1999/xhtml"
    xmlns:m="http://jboss.com/products/seam/mail"
    xmlns:h="http://java.sun.com/jsf/html">
  
    <m:from name="Peter" address="peter@example.com" />
    <m:to name="#{person.firstname} #{person.lastname}"
>#{person.address}</m:to>
    <m:subject
>Try out Seam!</m:subject>
    
    <m:body>
        <p
><h:outputText value="Dear #{person.firstname}" />,</p>
        <p
>You can try out Seam by visiting 
        <a href="http://labs.jboss.com/jbossseam"
>http://labs.jboss.com/jbossseam</a
>.</p>
        <p
>Regards,</p>
        <p
>Pete</p>
    </m:body>
    
</m:message
>

Il tag <m:message> racchiude l'intero messaggio e dice a Seam di iniziare a generare la mail. Dentro al tag <m:message> si usa un tag <m:from> per impostare il mittente, un tag <m:to> per specificare un destinatario (si noti che EL viene impiegato come in un facelet normale), e un tag <m:subject>.

Il tag <m:body> racchiude il corpo della mail. Si possono usare tag HTML dentro il corpo così come componenti JSF.

Adesso che si ha un modello di email, come lo si spedisce? Alla fine della generazione di m:message viene chiamato mailSession per spedire l'email, e quindi semplicemente occorre chiedere a Seam di generare la vista:

@In(create=true)

private Renderer renderer;
   
public void send() {
    try {
       renderer.render("/simple.xhtml");
       facesMessages.add("Email sent successfully");
   } 
   catch (Exception e) {
       facesMessages.add("Email sending failed: " + e.getMessage());
   }
}

Per esempio, se si digita un indirizzo email non valido, allora viene lanciata un'eccezione che viene catturata e quindi mostrata all'utente.

Seam semplifica l'uso degli allegati ad una mail. Supporta la maggior parte dei tipi java standard usati con i file.

Se si vuole spedire le email jboss-seam-mail.jar:


<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar"/>

Seam caricherà il file dal classpath e lo allegherà alla mail. Di default verrà allegato come jboss-seam-mail.jar; se si vuole avere un altro nome basta aggiungere l'attributo fileName:


<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar" fileName="this-is-so-cool.jar"/>

Si può anche allegare un java.io.File, un java.net.URL:


<m:attachment value="#{numbers}"/>

Oppure un byte[] o un java.io.InputStream:


<m:attachment value="#{person.photo}" contentType="image/png"/>

Si noterà che per byte[] e java.io.InputStream occorrerà specificare il tipo MIME dell'allegato (poiché quest'informazione non viene trasportata come parte del file).

Ancora meglio si può allegare un PDF generato da Seam, o ogni altra vista JSF standard, semplicemente mettendo un <m:attachment> attorno ai normali tag:


<m:attachment fileName="tiny.pdf">
    <p:document
>                                                      
        A very tiny PDF                                                                                                
    </p:document>
</m:attachment
>

Con un set di file da allegare (per esempio un gruppo di fotografie caricate da un database= si utilizza un <ui:repeat>:


<ui:repeat value="#{people}" var="person">
    <m:attachment value="#{person.photo}" contentType="image/jpeg" fileName="#{person.firstname}_#{person.lastname}.jpg"/>
</ui:repeat
>

E se si vuole mostrare un'immagine allegata inline:


<m:attachment 
    value="#{person.photo}" 
    contentType="image/jpeg" 
    fileName="#{person.firstname}_#{person.lastname}.jpg" 
    status="personPhoto" 
    disposition="inline" />
<img src="cid:#{personPhoto.contentId}" />

Ci si potrebbe chiedere cosa faccia cid:#{...}. IETF specifica che mettendo questo come src dell'immagine, gli allegati verranno visualizzati quando si tenta di localizzare l'immagine (il Content-ID deve corrispondere) — magia!

Occorre dichiarare l'allegato prima di tentare di accedere allo stato dell'oggetto.

A volte si vogliono aggiungere altre intestazioni alla mail. Seam fornisce supporta per alcune di queste (si veda Sezione 21.5, «Tag»). Per esempio, si può impostare l'importanza della mail e chiedere una conferma di lettura:


<m:message xmlns:m="http://jboss.com/products/seam/mail"
    importance="low"
    requestReadReceipt="true"/>

Altrimenti si può aggiunge una qualsiasi intestazione al messaggio usando il tag <m:header>:


<m:header name="X-Sent-From" value="JBoss Seam"/>

Se si usa EJB, allora si può impiegare MDB (Message Driven Bean) per ricevere una mail. JBoss fornisce un adattatore JCA — mail-ra.rar — ma la versione distribuita cone JBoss AS ha un numero di limitazioni (e non è incorporata in alcune versioni) quindi si raccomanda di usare mail-ra.rar distribuito con Seam (si trova nella directory extras/ nella distribuzione Seam). mail-ra.rar deve essere messo in $JBOSS_HOME/server/default/deploy; se la versione di JBoss già lo contiene, lo si sostituisca.

Lo si può configurare come:

@MessageDriven(activationConfig={

    @ActivationConfigProperty(propertyName="mailServer", propertyValue="localhost"),
    @ActivationConfigProperty(propertyName="mailFolder", propertyValue="INBOX"),
    @ActivationConfigProperty(propertyName="storeProtocol", propertyValue="pop3"),
    @ActivationConfigProperty(propertyName="userName", propertyValue="seam"),
    @ActivationConfigProperty(propertyName="password", propertyValue="seam")
})
@ResourceAdapter("mail-ra.rar")
@Name("mailListener")
public class MailListenerMDB implements MailListener {
    @In(create=true)
    private OrderProcessor orderProcessor;
    public void onMessage(Message message) {
       // Process the message
       orderProcessor.process(message.getSubject());
    }
   
}

Ogni messaggio ricevuto causerà una chiamata a onMessage(Message message). La maggior parte delle annotazioni di Seam funzioneranno dentro MDB, manon sarà possibile accedere al contesto di persistenza.

Si possono trovare maggiori informazioni su mail-ra.rar all'indirizzo http://wiki.jboss.org/wiki/Wiki.jsp?page=InboundJavaMail.

Se non si usa JBoss AS si può comunque usare mail-ra.rar oppure si cerchi se il proprio application server include un adattatore simile.

Per includere il supporto email nella propria applicazione, si includa jboss-seam-mail.jar nella directory WEB-INF/lib. Se si usa JBoss AS non c'è nessuna configurazione ulteriore per usare il supporto email di Seam. Altrimenti occorre assicurarsi di avere JavaMail API, un'implementazione di JavaMail API presente (l'API e l'impl usante in JBoss AS sono distribuite con seam in lib/mail.jar), ed una copia di Java Activation Framework (distribuito con seam in lib/activation.jar.

Il componente mailSession usa JavaMail per dialogare con un server SMTP 'reale'.

L'esempio mail di Seam usa Meldware (da buni.org) come server mail. Meldware è un pacchetto groupware che fornisce SMTP, POP3, IMAP, webmail, un calendario condiviso ed un tool di amministrazione grafico; è scritto come applicazione JEE e quindi può essere deployato in JBoss AS assieme alla propria applicazione Seam.

Attenzione

La versione di Meldware distribuita con Seam (scaricata a richiesta) è configurata per lo sviluppo - caselle di posta, utenti e alias (indirizzi email) sono creati ogni volta che si esegue il deploy dell'applicazione. Se si vuole usare Meldware in produzione occorre installare l'ultima release da buni.org.

Le email vengono generate usando tag nel namespace http://jboss.com/products/seam/mail. I documenti dovrebbero sempre avere il tag message alla radice del messaggio. Il tag message prepara Seam a generare una email.

I tag standard per il templating di facelets possono venire usati normalmente. Dentro il corpo si può usare qualsiasi tag JSF; se viene richiesto l'accesso a risorse esterne (fogli di stile, javascript) allora ci si assicuri di impostare urlBase.

<m:message>

Tag radice di un messaggio mail

<m:from>

Si imposti l'indirizzo From: per la mail. Se ne può avere uno soltanto per email.

<m:replyTo>

Si imposti l'indirizzo Reply-to: per la mail. Se ne può avere uno soltanto per email.

<m:to>

Si aggiunga un destinatario alla mail. Si usino più tag <m:to> per destinatari multipli. Questo tag può essere collocato in modo sicuro dentro un tag repeat quale <ui:repeat>.

<m:cc>

Si aggiunga un destinatario cc alla mail. Si usino più tag <m:cc> per destinatari cc multipli. Questo tag può essere collocato in modo sicuro dentro un tag iteratore quale <ui:repeat>.

<m:bcc>

Si aggiunga un destinatario ccn alla mail. Si usino più tag <m:bcc> per destinatari ccn multipli. Questo tag può essere collocato in modo sicuro dentro un tag repeat quale <ui:repeat>.

<m:header>

Aggiungere un'intestazione alla mail (es. X-Sent-From: JBoss Seam)

<m:attachment>

Aggiungere un allegato alla mail.

<m:subject>

Impostiamo l'oggetto della mail.

<m:body>

Si imposti il corpo della mail. Supporta un facet alternativo che, se viene generata una mail HTML, può contenere testo alternativo per un lettore di email che non supporta HTML.

Seam semplifica molto l'esecuzione di lavori asincroni da richiesta web. Quando la maggior parte delle persone pensa all'asincronicità in Java EE, pensano all'uso di JMS. Questo è certamente un modo per approcciare il problema in Seam, ed è quello giusto quando si hanno dei requisiti di qualità di servizio molto stringenti e ben definiti. Seam semplifica l'invio e la ricezione di messaggi JMS usando i componenti Seam.

Ma per molti casi d'uso, JMS è eccessivo. Seam aggiunge come nuovo layer un semplice metodo asincrono ed un meccanismo d'eventi nella scelta del dispatcher:

  • java.util.concurrent.ScheduledThreadPoolExecutor (di default)

  • Il servizio EJB timer (per ambienti EJB 3.0)

  • Quartz

Eventi asincroni e chiamate a metodi hanno le stesse aspettative di qualità di servizio del meccanismo sottostante di dispatcher. Il dispatcher di default, basato su ScheduledThreadPoolExecutor opera efficientemente ma non fornisce alcun supporto ai task asincroni di persistenza, e quindi non garantisce che un task verrà effettivamente eseguito. Se si lavora in un ambiente che supporta EJB 3.0 e si aggiunge la seguente linea a components.xml:


<async:timer-service-dispatcher/>

allora i task asincroni verranno processati dal servizio EJB timer del container. Se non si è familiari come il servizio Timer, nessuna paura, non si deve interagire direttamente con esso per usare i metodi asincroni in Seam. La cosa importante da sapere è che qualsiasi buona implementazione di EJB 3.0 avrà l'opzione di usare i timer di persistenza, che danno garanzia che i task verranno processati.

Un'altra alternativa è quella di usare la libreria open source Quartz per gestire il metodo asincrono. Occorre incorporare il JAR della libreria Quartz (che si trova nella directory lib) nell'EAR e dichiararla come modulo Java in application.xml. Il dispatcher Quartz può essere configurato aggiungendo un file di proprietà Quartz al classpath. Deve essere nominato seam.quartz.properties. Inoltre occorre aggiungere la seguente linea a components.xml per installare il dispatcher Quartz.


<async:quartz-dispatcher/>

L'API di Seam per il ScheduledThreadPoolExecutor di default, il Timer EJB3, e lo Scheduler Quartz sono più o meno la stessa cosa. Vengono azionati come "plug and play" aggiungendo una linea a components.xml.

Nella forma più semplice, una chiamata asincrona consente che una chiamata di metodo venga processata asincronicamente (in un thread differente) dal chiamante. Solitamente si usa una chiamata asincrona quando si vuole ritornare una risposta immediata al client, e si lascia in background il lavoro dispendioso da processare. Questo pattern funziona molto bene nelle applicazioni che usano AJAX, dove il client può automaticamente interrogare il server per ottenere il risultato del lavoro.

Per i componenti EJB si annota l'interfaccia locale per specificare che un metodo viene processato asincronicamente.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processPayment(Payment payment);
}

(Per i componenti JavaBean, se si vuole, si può annotare la classe d'implementazione del componente.)

L'uso dell'asincronicità è trasparente alla classe bean:

@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    public void processPayment(Payment payment)
    {
        //do some work!
    }
}

E è anche trasparente al client:

@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String pay()
    {
        paymentHandler.processPayment( new Payment(bill) );
        return "success";
    }
}

Il metodo asincrono viene processato in un contesto eventi completamente nuovo e non ha accesso allo stato del contesto sessione o conversazione del chiamante. Comunque, il contesto di processo di business viene propagato.

Le chiamate del metodo asincrono possono essere schedulate per un'esecuzione successiva usando le annotazioni @Duration, @Expiration e @IntervalDuration.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public void processScheduledPayment(Payment payment, @Expiration Date date);
    @Asynchronous
    public void processRecurringPayment(Payment payment, 
                                        @Expiration Date date, 
                                        @IntervalDuration Long interval)'
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
        return "success";
    }
    public String scheduleRecurringPayment()
    {
        paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(), 
                                                ONE_MONTH );
        return "success";
    }
}

Entrambi il server ed il client possono accedere all'oggetto Timer associato all'invocazione. L'oggetto Timer mostrato sotto è il timer EJB3 quando viene usato il dispatcher EJB3. Per il ScheduledThreadPoolExecutor di default, l'oggetto restituito è Future di JDK. Per il dispatcher Quartz, viene ritornato QuartzTriggerHandle, che verrà discusso nella prossima sessione.

@Local

public interface PaymentHandler
{
    @Asynchronous
    public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}
@Stateless

@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    @In Timer timer;
    
    public Timer processScheduledPayment(Payment payment, @Expiration Date date)
    {
        //do some work!
        
        return timer; //note that return value is completely ignored
    }
}
@Stateful

@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        Timer timer = paymentHandler.processScheduledPayment( new Payment(bill), 
                                                              bill.getDueDate() );
        return "success";
    }
}

I metodi asincroni non possono ritornare al chiamante alcun altro valore.

Il dispatcher Quartz (vedere indietro come viene installato) consente di usare le annotazioni @Asynchronous, @Duration, @Expiration, e @IntervalDuration come sopra. Ma possiede anche altre potenti caratteristiche. Il dispatcher Quartz supporta tre nuove annotazioni.

L'annotazione @FinalExpiration specifica una data finale per il task ricorrente. Si noti che si può iniettare il QuartzTriggerHandle.



        @In QuartzTriggerHandle timer;
        
    // Defines the method in the "processor" component
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalDuration Long interval,
                                 @FinalExpiration Date endDate, 
                                 Payment payment) 
    { 
        // do the repeating or long running task until endDate
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    // Starts now, repeats every hour, and ends on May 10th, 2010
    Calendar cal = Calendar.getInstance ();
    cal.set (2010, Calendar.MAY, 10);
    processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);

Si noti che il metodo restituisce l'oggetto QuartzTriggerHandle, che si può usare per arrestare, mettere in pausa e ripristinare lo scheduler. L'oggetto QuartzTriggerHandle è serializzabile, e quindi puoi salvarlo nel database se deve essere presente per un periodo di tempo esteso.

QuartzTriggerHandle handle =

         processor.schedulePayment(payment.getPaymentDate(), 
                                   payment.getPaymentCron(), 
                                   payment);
        payment.setQuartzTriggerHandle( handle );
        // Save payment to DB
        
        // later ...
        
        // Retrieve payment from DB
        // Cancel the remaining scheduled tasks
        payment.getQuartzTriggerHandle().cancel();

L'annotazione @IntervalCron supporta la sintassi Unix del cron job per la schedulazione dei task. Per esempio, il seguente metodo asincrono gira alle 2:10pm e alle 2:44pm ogni Mercoledì del mese di Marzo.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalCron String cron, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);

L'annotazione @IntervalBusinessDay supporta l'invocazione sullo scenario di "Giorno Lavorativo ennesimo". Per esempio il seguente metodo asincrono gira alle ore 14.00 del secondo giorno lavorativo di ogni mese. Di default esclude tutti i weekend e le festività federali americane fino al 2010 dai giorni lavorativi.



    // Define the method
    @Asynchronous
    public QuartzTriggerHandle schedulePayment(@Expiration Date when, 
                                 @IntervalBusinessDay NthBusinessDay nth, 
                                 Payment payment) 
    { 
        // do the repeating or long running task
    }
    
    ... ...
    
    // Schedule the task in the business logic processing code
    QuartzTriggerHandle handle = 
      processor.schedulePayment(new Date(), 
          new NthBusinessDay(2, "14:00", WEEKLY), payment);

L'oggetto NthBusinessDay contiene la configurazione del trigger d'invocazione. Si possono specificare più vacanze (esempio, vacanze aziendali, festività non-US, ecc.) attraverso la proprietà additionalHolidays.



public class NthBusinessDay implements Serializable
{
      int n;
      String fireAtTime;
      List <Date
> additionalHolidays;
      BusinessDayIntervalType interval;
      boolean excludeWeekends;
      boolean excludeUsFederalHolidays;
      public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }
      public NthBusinessDay ()
      {
        n = 1;
        fireAtTime = "12:00";
        additionalHolidays = new ArrayList <Date
> ();
        interval = BusinessDayIntervalType.WEEKLY;
        excludeWeekends = true;
        excludeUsFederalHolidays = true;
      }     
      ... ...
}

Le annotazioni @IntervalDuration, @IntervalCron, e @IntervalNthBusinessDay sono mutualmente esclusive. Se usate nello stesso metodo, verrà lanciata una RuntimeException.

Ogni dispatcher asincrono si comporta in modo differente quando attraverso di esso viene propagata un'eccezione. Per esempio, il dispatcher java.util.concurrent sospenderà ogni altra esecuzione di una chiamata che si ripete, ed il servizio timer EJB3 inghiottirà quest'eccezione. Quindi Seam cattura ogni eccezione che si propaga fuori da una chiamata asincrona prima che questa raggiunga il dispatcher.

Di default ogni eccezione che si propaga fuori da un'esecuzione asincrona verrà catturata e loggata come errore. Si può personalizzare questo comportamento facendo override del componente org.jboss.seam.async.asynchronousExceptionHandler.

@Scope(ScopeType.STATELESS)

@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler extends AsynchronousExceptionHandler { 
   @Logger Log log;
   
   @In Future timer;
   
   @Override
   public void handleException(Exception exception) {
      log.debug(exception);
      timer.cancel(false);
   }
   
}

Per esempio, usando il dispatcher java.util.concurrent, viene iniettato il suo oggetto di controllo e vengono cancellate tutte le future invocazioni quando si incontra un'eccezione.

Si può alterare questo comportamento per un componente individuale, implementando sul componente il metodo public void handleAsynchronousException(Exception exception);. Per esempio:

   public void handleAsynchronousException(Exception exception) {

      log.fatal(exception);
   }

Seam facilita l'invio e la ricezione di messaggi JMS da e verso componenti Seam.

In quasi tutte le applicazioni gestionali il database è il principale collo di bottiglia e lo strato meno scalabile dell'ambiente di esecuzione. Chi utilizza ambienti PHP/Ruby cercherà di sostenere che le architetture cosiddette "shared nothing" (nessuna condivisione) hanno una buona scalabilità. Benché questo possa essere letteralmente vero, non si conoscono molte applicazioni multi utente che possano essere implementate senza la condivisione di risorse tra diversi nodi di un cluster. In effetti ciò a cui questi sprovveduti stanno pensando è un'architettura con "nessuna condivisione eccetto il database". Ovviamente condividere il database è il principale problema di scalabilità di una applicazione multi utente, perciò affermare che questa architettura è altamente scalabile è assurdo e ci dice molto sul tipo di applicazioni sul cui sviluppo questi signori spendono la maggior parte del tempo.

Quasi tutto ciò che riusciamo a fare per condividere il database meno frequentemente è ben fatto.

E questo richiede una cache (memoria tampone). Un'applicazione Seam ben progettata comprenderà una ricca e stratificata strategia di cache che interessi ogni strato dell'applicazione:

  • Il database, chiaramente, ha la propria cache. Questo è enormemente importante, ma non può scalare come una cache nello strato applicativo.

  • La soluzione ORM scelta (Hibernate o qualche altra implementazione JPA) ha una cache di secondo livello per i dati provenienti dal database. Questa è una caratteristica molto potente, ma spesso male utilizzata. In un ambiente cluster, mantenere i dati in una cache in modo consistente dal punto di vista delle transazioni con l'intero cluster e con il database, è molto dispendioso. Ha più senso per i dati condivisi tra molti utenti e che vengono aggiornati raramente. Nelle architetture tradizionali senza stato, spesso si cerca di usare la cache di secondo livello per conservare lo stato di una conversazione. Questo è sempre un male ed è particolarmente sbagliato in Seam.

  • Il contesto Conversation di Seam è una cache per lo stato della conversazione. I componenti che vengono messi nel contesto conversation posso mantenere lo stato relativo all'interazione dell'utente.

  • In particolare, un persistence context gestito da Seam (o un persistence context EJB gestito dal container associato ad uno stateful session bean legato ad una conversazione) agisce con una cache per i dati che sono stati letti nella conversazione attuale. Questa cache tende ad avere una frequenza di utilizzo piuttosto alta! Seam ottimizza la replica dei persistence context gestiti da Seam in un ambiente cluster e non c'è bisogno di curare la consistenza transazionale con il database (il lock ottimistico è sufficiente) perciò non è necessario preoccuparsi troppo per le implicazioni sulle prestazioni di questa cache, a meno che non si leggano migliaia di oggetti con un singolo persistence context.

  • L'applicazione può conservare uno stato non transazionale nell'application context di Seam. Lo stato mantenuto nell'applicazione context ovviamente non è visibile agli altri nodi del cluster.

  • L'applicazione può conservare uno stato transazionale usando il componente cacheProvider di Seam, il quale si integra con JBossCache, JBoss POJO Cache oppure EHCache nell'ambiente Seam. Questo stato risulterà visibile agli altri nodi se la cache gestisce il funzionamento in modalità cluster.

  • Infine Seam consente di conservare dei frammenti generati di una pagina JSF. A differenza della cache di secondo livello dell'ORM, questa cache non viene automaticamente invalidata quando i dati cambiano, perciò è necessario scrivere il codice applicativo necessario per realizzare una invalidazione esplicita, oppure importare degli opportuni criteri di scadenza.

Per ulteriori informazioni sulla cache di secondo livello è necessario fare riferimento alla documentazione della soluzione ORM scelta poiché si tratta di un argomento estremamente complesso. In questo paragrafo verrà affrontato l'uso diretto della cache, tramite il componente cacheProvider o come cache dei frammenti di pagina, tramite il controllo <s:cache>.

Il componente cacheProvider fornito dal framework gestisce una istanza di:

E' possibile mettere con sicurezza nella cache qualsiasi oggetto Java immutabile; esso verrà conservato nella cache e replicato nel cluster (assumendo che la replica sia gestita e abilitata). Se si vuole mantenere degli oggetti mutabili nella cache occorre leggere la documentazione della cache sottostante per scoprire come notificare la cache dei cambiamenti negli oggetti.

Per usare cacheProvider è necessario includere nel progetto i jar dell'implementazione della cache:

Se l'applicazione Seam viene assemblata in un EAR si raccomanda di inserire direttamente nell'EAR la configurazione e i jar della cache.

Sarà inoltre necessario fornire un file di configurazione per JBossCache. Posizionare treecache.xml con un'opportuna configurazione di cache nel classpath (ad esempio nel jar ejb oppure in WEB-INF/classes). JBossCache ha molte impostazioni ostiche e terribili perciò non verranno discusse qui. Per ulteriori informazioni fare riferimento alla documentazione di JBossCache.

E' possibile trovare un treecache.xml di esempio in examples/blog/resources/treecache.xml.

EHCache senza un file di configurazione funzionerà nella sua configurazione di default.

Per modificare il file di configurazione in uso, configurare la cache in components.xml:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:cache="http://jboss.com/products/seam/cache">
   <cache:jboss-cache-provider configuration="META-INF/cache/treecache.xml" />
</components
>

A questo punto è possibile iniettare la cache in qualsiasi componente Seam:

@Name("chatroomUsers")

@Scope(ScopeType.STATELESS)
public class ChatroomUsers
{
    @In CacheProvider cacheProvider;
    @Unwrap
    public Set<String
> getUsers() throws CacheException   {
        Set<String
> userList = (Set<String
>) cacheProvider.get("chatroom", "userList");
        if (userList==null) {
            userList = new HashSet<String
>();
            cacheProvider.put("chatroom", "userList", userList);
        }
        return userList;
    }
}

Se nell'applicazione si vogliono avere più configurazioni della cache basta usare components.xml per configurare più componenti cacheProvider:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:cache="http://jboss.com/products/seam/cache">
   <cache:jboss-cache-provider name="myCache" configuration="myown/cache.xml"/>
   <cache:jboss-cache-provider name="myOtherCache" configuration="myother/cache.xml"/>
</components
>

L'uso più interessante della cache in Seam è la tag <s:cache>, che è la soluzione offerta da Seam per il problema di conservare in cache frammenti di pagine JSF. Internamente <s:cache> usa pojoCache, perciò è necessario seguire i passi elencati in precedenza prima di poterla usare (mettere i jar nell'EAR, sistemare le terribili opzioni di configurazione, ecc).

<s:cache> viene usata per conservare quei contenuti generati che cambiano raramente. Ad esempio la pagina di benvenuto del nostro blog mostra le voci del blog più recenti:


<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
   <h:dataTable value="#{blog.recentEntries}" var="blogEntry">
      <h:column>
         <h3
>#{blogEntry.title}</h3>
         <div>
            <s:formattedText value="#{blogEntry.body}"/>
         </div>
      </h:column>
   </h:dataTable>
</s:cache
>

La chiave (key) consente di avere più versioni in cache di ogni frammento di pagina. In questo caso c'è una versione in cache per ogni blog. La regione (region) determina la cache o il nodo region in cui tutte le versioni verranno conservate. Diversi nodi possono avere diverse regole di scadenza (queste sono le cose che si impostano con le summenzionate terribili opzioni di configurazione).

Ovviamente il grosso problema di <s:cache> è che è troppo semplice sapere quando i dati contenuti cambiano (ad esempio quando il blogger pubblica uno nuovo contenuto). Così è necessario eliminare i frammento in cache manualmente:

public void post() {

    ...
    entityManager.persist(blogEntry);
    cacheProvider.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}

In alternativa, se non è critico che le modifiche siano immediatamente visibili agli utenti, è possibile impostare un tempo di scadenza breve nel nodo della cache.

Seam si integra con JBossWS per consentire allo standard JEE web service di sfruttrare pienamente il framework contestuale di Seam, includendo il supporto ai web service conversazionali. Questo capitolo passa in rassegna tutti i passi richiesti per consentire ai web service di funzionare in ambiente Seam.

Per consentire a Seam di intercettare le richieste web service in modo tale da creare i contesti Seam necessari per la richiesta, deve essere configurato uno speciale handler SOAP; org.jboss.seam.webservice.SOAPRequestHandler è un'implementazione SOAPHandler che esegue il lavoro di gestione del ciclo di vita di Seam durante lo scope di una richiesta web service.

Uno speciale file di configurazione, standard-jaxws-endpoint-config.xml, deve essere collocato nella directory META-INF del file jar che contiene le classi web service. Questo file contiene la seguente configurazione handler SOAP:


<jaxws-config xmlns="urn:jboss:jaxws-config:2.0" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xmlns:javaee="http://java.sun.com/xml/ns/javaee"
              xsi:schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2_0.xsd">
   <endpoint-config>
      <config-name
>Seam WebService Endpoint</config-name>
      <pre-handler-chains>
         <javaee:handler-chain>
            <javaee:protocol-bindings
>##SOAP11_HTTP</javaee:protocol-bindings>
            <javaee:handler>
               <javaee:handler-name
>SOAP Request Handler</javaee:handler-name>
               <javaee:handler-class
>org.jboss.seam.webservice.SOAPRequestHandler</javaee:handler-class>
            </javaee:handler>
         </javaee:handler-chain>
      </pre-handler-chains>
   </endpoint-config>
</jaxws-config
>

Quindi come vengono propagate le conversazioni tra le richieste web service? Seam usa un elemento di intestazione SOAP presente in entrambi i messaggi di richiesta e di risposta SOAP per portare l'ID della conversazione dal consumatore al servizio, e viceversa. Ecco un esempio di richiesta web service che contiene un ID di conversazione:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:seam="http://seambay.example.seam.jboss.org/">
  <soapenv:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </soapenv:Header>
  <soapenv:Body>
    <seam:confirmAuction/>
  </soapenv:Body>
</soapenv:Envelope
>    
    

As you can see in the above SOAP message, there is a conversationId element within the SOAP header that contains the conversation ID for the request, in this case 2. Unfortunately, because web services may be consumed by a variety of web service clients written in a variety of languages, it is up to the developer to implement conversation ID propagation between individual web services that are intended to be used within the scope of a single conversation.

An important thing to note is that the conversationId header element must be qualified with a namespace of http://www.jboss.org/seam/webservice, otherwise Seam will not be able to read the conversation ID from the request. Here's an example of a response to the above request message:


<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
  <env:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </env:Header>
  <env:Body>
    <confirmAuctionResponse xmlns="http://seambay.example.seam.jboss.org/"/>
  </env:Body>
</env:Envelope
>    
    

As you can see, the response message contains the same conversationId element as the request.

Let's walk through an example web service. The code in this section all comes from the seamBay example application in Seam's /examples directory, and follows the recommended strategy as described in the previous section. Let's first take a look at the web service class and one of its web service methods:

@Stateless

@WebService(name = "AuctionService", serviceName = "AuctionService")
public class AuctionService implements AuctionServiceRemote
{
   @WebMethod
   public boolean login(String username, String password)
   {
      Identity.instance().setUsername(username);
      Identity.instance().setPassword(password);
      Identity.instance().login();
      return Identity.instance().isLoggedIn();
   }
   // snip
}

As you can see, our web service is a stateless session bean, and is annotated using the JWS annotations from the javax.jws package, as defined by JSR-181. The @WebService annotation tells the container that this class implements a web service, and the @WebMethod annotation on the login() method identifies the method as a web service method. The name and serviceName attributes in the @WebService annotation are optional.

As is required by the specification, each method that is to be exposed as a web service method must also be declared in the remote interface of the web service class (when the web service is a stateless session bean). In the above example, the AuctionServiceRemote interface must declare the login() method as it is annotated as a @WebMethod.

As you can see in the above code, the web service implements a login() method that delegates to Seam's built-in Identity component. In keeping with our recommended strategy, the web service is written as a simple facade, passing off the real work to a Seam component. This allows for the greatest reuse of business logic between web services and other clients.

Let's look at another example. This web service method begins a new conversation by delegating to the AuctionAction.createAuction() method:

   @WebMethod

   public void createAuction(String title, String description, int categoryId)
   {
      AuctionAction action = (AuctionAction) Component.getInstance(AuctionAction.class, true);
      action.createAuction();
      action.setDetails(title, description, categoryId);
   }

And here's the code from AuctionAction:

   @Begin

   public void createAuction()
   {
      auction = new Auction();
      auction.setAccount(authenticatedAccount);
      auction.setStatus(Auction.STATUS_UNLISTED);        
      durationDays = DEFAULT_AUCTION_DURATION;
   }

From this we can see how web services can participate in long running conversations, by acting as a facade and delegating the real work to a conversational Seam component.

Seam integrates the RESTEasy implementation of the JAX-RS specification (JSR 311). You can decide how "deep" the integration into your Seam application is going to be:

First, get the RESTEasy libraries and the jaxrs-api.jar, deploy them with the other libraries of your application. Also deploy the integration library, jboss-seam-resteasy.jar

On startup, all classes annotated @javax.ws.rs.Path will be discovered automatically and registered as HTTP resources. Seam automatically accepts and serves HTTP requests with its built-in SeamResourceServlet. The URI of a resource is build as follows:

As an example, the following resource definition would return a plaintext representation for any GET requests using the URI http://your.hostname/seam/resource/rest/customer/123:

@Path("/customer")

public class MyCustomerResource {
    @GET
    @Path("/{customerId}")
    @Produces("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return ...;
    }
}

No additional configuration is required, you do not have to edit web.xml or any other setting if these defauls are acceptable. However, you can configure RESTEasy in your Seam application. First import the resteasy namespace into your XML configuration file header:


<components
   xmlns="http://jboss.com/products/seam/components"
   xmlns:resteasy="http://jboss.com/products/seam/resteasy"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation=
     http://jboss.com/products/seam/resteasy
         http://jboss.com/products/seam/resteasy-2.1.xsd
     http://jboss.com/products/seam/components
         http://jboss.com/products/seam/components-2.1.xsd"
>

You can then change the /rest prefix as mentioned earlier:


<resteasy:application resource-path-prefix="/restv1"/>

The full base path to your resources is now /seam/resource/restv1/{resource} - note that your @Path definitions and mappings do NOT change. This is an application-wide switch usually used for versioning of the HTTP API.

You can disable stripping of the base path if you'd like to map the full path in your resources:


<resteasy:application strip-seam-resource-path="false"/>

The path of a resource is now mapped with e.g. @Path("/seam/resource/rest/customer"). We do not recommend disabling this feature, as your resource class mappings are then bound to a particular deployment scenario.

Seam will scan your classpath for any deployed @javax.ws.rs.Path resources and any @javax.ws.rs.ext.Provider classes. You can disable scanning and configure these classes manually:


<resteasy:application
     scan-providers="false"
     scan-resources="false"
     use-builtin-providers="true">

     <resteasy:resource-class-names>
         <value
>org.foo.MyCustomerResource</value>
         <value
>org.foo.MyOrderResource</value>
         <value
>org.foo.MyStatelessEJBImplementation</value>
     </resteasy:resource-class-names>

     <resteasy:provider-class-names>
         <value
>org.foo.MyFancyProvider</value>
     </resteasy:provider-class-names>

 </resteasy:application
>

RESTEasy supports plain EJBs (EJBs that are not Seam components) as resources. Instead of configuring the JNDI names in a non-portable fashion in web.xml (see RESTEasy documentation), you can simply list the EJB implementation classes, not the business interfaces, in components.xml as shown above. Note that you have to annotate the @Local interface of the EJB with @Path, @GET, and so on - not the bean implementation class. This allows you to keep your application deployment-portable with the global Seam jndi-pattern switch on <core:init/>. Note that EJB resources will not be found even if scanning of resources is enabled, you always have to list them manually. Again, this is only relevant for EJB resources that are not also Seam components and that do not have a @Name annotation.

The use-built-in-providers switch enables (default) or disables the RESTEasy built-in providers. We recommend you leave them enabled, as they provide plaintext, JSON, and JAXB marshalling out of the box.

Finally, you can configure media type and language URI extensions:


<resteasy:application>

    <resteasy:media-type-mappings>
       <key
>txt</key
><value
>text/plain</value>
    </resteasy:media-type-mappings>

    <resteasy:language-mappings>
       <key
>deutsch</key
><value
>de-DE</value>
    </resteasy:language-mappings>

</resteasy:application
>

This definition would map the URI suffix of .txt.deutsch to additional Accept and Accept-Language header values text/plain and de-DE.

Any resource and provider instances are managed by RESTEasy by default. That means a resource class will be instantiated by RESTEasy and serve a single request, after which it will be destroyed. This is the default JAX-RS lifecycle. Providers are instantiated once for the whole application and are effectively singletons and supposed to be stateless.

You can write resources and providers as Seam components and benefit from the richer lifecycle management of Seam, and interception for bijection, security, and so on. Simply make your resource class a Seam component:

@Name("customerResource")

@Path("/customer")
public class MyCustomerResource {
    @In
    CustomerDAO customerDAO;
    @GET
    @Path("/{customerId}")
    @Produces("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return customerDAO.find(id).getName();
    }
}

An instance of customerResource is now handled by Seam when a request hits the server. This is a Seam JavaBean component that is EVENT-scoped, hence no different than the default JAX-RS lifecycle. However, you get full Seam injection support and all other Seam components and contexts are available to you. Currently also supported are SESSION, APPLICATION, and STATELESS resource components. Remember that any HTTP request has to transmit a valid session identifier (cookie, URI path parameter) for correct handling of the server-side session context.

Conversation-scoped resource components and mapping of conversations is currently not supported but will be available soon.

Provider classes can also be Seam components, they must be APPLICATION-scoped or STATELESS.

Resources and providers can be EJBs or JavaBeans, like any other Seam component.

Section 3.3.4 of the JAX-RS specification defines how checked or unchecked exceptions are handled by the JAX RS implementation. In addition to using an exception mapping provider as defined by JAX-RS, the integration of RESTEasy with Seam allows you to map exceptions to HTTP response codes within Seam's pages.xml facility. If you are already using pages.xml declarations, this is easier to maintain than potentially many JAX RS exception mapper classes.

Exception handling within Seam requires that the Seam filter is executed for your HTTP request. Ensure that you do filter all requests in your web.xml, not - as some Seam examples might show - a request URI pattern that doesn't cover your REST requests. The following example intercepts all HTTP requests and enables Seam exception handling:


<filter>
    <filter-name
>Seam Filter</filter-name>
    <filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name
>Seam Filter</filter-name>
    <url-pattern
>/*</url-pattern>
</filter-mapping
>

To convert the unchecked UnsupportedOperationException thrown by your resource methods to a 501 Not Implemented HTTP status response, add the following to your pages.xml descriptor:


<exception class="java.lang.UnsupportedOperationException">
    <http-error error-code="501">
        <message
>The request operation is not supported</message>
    </http-error>
</exception
>

Custom or checked exceptions are handled the same:


<exception class="my.CustomException" log="false">
    <http-error error-code="503">
        <message
>The service is currently not available: #{org.jboss.seam.handledException.message}</message>
    </http-error>
</exception
>

You do not have to send an HTTP error to the client if an exception occurs. Seam allows you to map the exception as a redirect to a view of your Seam application. As this feature is typically used for human clients (web browsers) and not for REST API remote clients, you should pay extra attention to conflicting exception mappings in pages.xml.

Note that the HTTP response still passes through the servlet container, so an additional mapping might apply if you have <error-page> mappings in your web.xml configuration. The HTTP status code would then be mapped to a rendered HTML error page with status 200 OK!

Seam fornisce un metodo per accedere in modo remoto i componenti da una pagina web, usando AJAX (Asynchronous Javascript and XML). Il framework per questa funzionalità viene fornito con quasi nessuno sforzo di sviluppo - i componenti richiedono solamente una semplice annotazione per diventare accessibile via AJAX. Questo capitolo descrive i passi richiesti per costruire una pagina web abilitata a AJAX, poi spiega con maggior dettaglio le caratteristiche del framework Seam Remoting.

Per usare remoting, il resource servlet di Seam deve essere innanzitutto configurato nel file web.xml:


<servlet>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <servlet-class
>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name
>Seam Resource Servlet</servlet-name>
  <url-pattern
>/seam/resource/*</url-pattern>
</servlet-mapping
>

Il passo successivi è importare il Javascript necessario nella propria pagina web. Ci sono un minimo di due script da importare. Il primo contiene tutto il codice del framework lato client che abilita le funzionalità di remoting:


<script type="text/javascript" src="seam/resource/remoting/resource/remote.js"
></script
>

Il secondo script contiene gli stub e le definizioni tipo per i componenti da chiamare. Viene generato dinamicamente basandosi sull'interfaccia locale dei propri componenti, ed include le definizioni tipo per tutte le classi che possono essere usate per chiamare i metodi remoti dell'interfaccia. Il nome dello script riflette il nome del componente. Per esempio se si ha un bean di sessione stateless annotato con @Name("customerAction"), allora il tag dello script dovrebbe essere simile a:


<script type="text/javascript" 
          src="seam/resource/remoting/interface.js?customerAction"
></script
>

Se si vuole accedere a più di un componente dalla stessa pagina, allora li si includa tutti come parametri nel tag script:


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction&accountAction"
></script
>

In alternativa si può usare il tag s:remote per importare il Javascript richiesto. Si separi ciascun componente o nome di classe che si vuole importare con una virgola:



  <s:remote include="customerAction,accountAction"/>    
    

L'interazione lato client con i componenti viene eseguita tutta tramite l'oggetto Javascript Seam. Quest'oggetti è definito in remote.js, e lo si userà per fare chiamate asincrone verso il componente. E' suddiviso in due aree di funzionalità; Seam.Component contiene metodi per lavorare con i componenti e Seam.Remoting contiene metodi per eseguire le richieste remote. La via più facile per diventare familiare con quest'oggetto è cominciare con un semplice esempio.

Si cominci con un semplice esempio per vedere come funziona l'oggetto Seam

@Stateless

@Name("helloAction")
public class HelloAction implements HelloLocal {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

You also need to create a local interface for our new component - take special note of the @WebRemote annotation, as it's required to make our method accessible via remoting:

@Local

public interface HelloLocal {
  @WebRemote
  public String sayHello(String name);
}

That's all the server-side code we need to write. Now for our web page - create a new page and import the helloAction component:


<s:remote include="helloAction"/>

To make this a fully interactive user experience, let's add a button to our page:


<button onclick="javascript:sayHello()"
>Say Hello</button
>

We'll also need to add some more script to make our button actually do something when it's clicked:


<script type="text/javascript">
  //<![CDATA[

  function sayHello() {
    var name = prompt("What is your name?");
    Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
  }

  function sayHelloCallback(result) {
    alert(result);
  }

   // ]]>
</script
>

We're done! Deploy your application and browse to your page. Click the button, and enter a name when prompted. A message box will display the hello message confirming that the call was successful. If you want to save some time, you'll find the full source code for this Hello World example in Seam's /examples/remoting/helloworld directory.

So what does the code of our script actually do? Let's break it down into smaller pieces. To start with, you can see from the Javascript code listing that we have implemented two methods - the first method is responsible for prompting the user for their name and then making a remote request. Take a look at the following line:


Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);

The first section of this line, Seam.Component.getInstance("helloAction") returns a proxy, or "stub" for our helloAction component. We can invoke the methods of our component against this stub, which is exactly what happens with the remainder of the line: sayHello(name, sayHelloCallback);.

What this line of code in its completeness does, is invoke the sayHello method of our component, passing in name as a parameter. The second parameter, sayHelloCallback isn't a parameter of our component's sayHello method, instead it tells the Seam Remoting framework that once it receives the response to our request, it should pass it to the sayHelloCallback Javascript method. This callback parameter is entirely optional, so feel free to leave it out if you're calling a method with a void return type or if you don't care about the result.

The sayHelloCallback method, once receiving the response to our remote request then pops up an alert message displaying the result of our method call.

The Seam.Component Javascript object provides a number of client-side methods for working with your Seam components. The two main methods, newInstance() and getInstance() are documented in the following sections however their main difference is that newInstance() will always create a new instance of a component type, and getInstance() will return a singleton instance.

In the configuration section above, the interface, or "stub" for our component is imported into our page either via seam/resource/remoting/interface.js: or using the s:remote tag:


<script type="text/javascript" 
        src="seam/resource/remoting/interface.js?customerAction"
></script
>

<s:remote include="customerAction"/>

By including this script in our page, the interface definitions for our component, plus any other components or types that are required to execute the methods of our component are generated and made available for the remoting framework to use.

There are two types of client stub that can be generated, "executable" stubs and "type" stubs. Executable stubs are behavioural, and are used to execute methods against your session bean components, while type stubs contain state and represent the types that can be passed in as parameters or returned as a result.

The type of client stub that is generated depends on the type of your Seam component. If the component is a session bean, then an executable stub will be generated, otherwise if it's an entity or JavaBean, then a type stub will be generated. There is one exception to this rule; if your component is a JavaBean (ie it is not a session bean nor an entity bean) and any of its methods are annotated with @WebRemote, then an executable stub will be generated for it instead of a type stub. This allows you to use remoting to call methods of your JavaBean components in a non-EJB environment where you don't have access to session beans.

The Seam Remoting Context contains additional information which is sent and received as part of a remoting request/response cycle. At this stage it only contains the conversation ID but may be expanded in the future.

When a remote method is executed, the result is serialized into an XML response that is returned to the client. This response is then unmarshaled by the client into a Javascript object. For complex types (i.e. Javabeans) that include references to other objects, all of these referenced objects are also serialized as part of the response. These objects may reference other objects, which may reference other objects, and so forth. If left unchecked, this object "graph" could potentially be enormous, depending on what relationships exist between your objects. And as a side issue (besides the potential verbosity of the response), you might also wish to prevent sensitive information from being exposed to the client.

Seam Remoting provides a simple means to "constrain" the object graph, by specifying the exclude field of the remote method's @WebRemote annotation. This field accepts a String array containing one or more paths specified using dot notation. When invoking a remote method, the objects in the result's object graph that match these paths are excluded from the serialized result packet.

For all our examples, we'll use the following Widget class:

@Name("widget")

public class Widget
{
  private String value;
  private String secret;
  private Widget child;
  private Map<String,Widget> widgetMap;
  private List<Widget> widgetList;
  
  // getters and setters for all fields
}

Seam Remoting provides experimental support for JMS Messaging. This section describes the JMS support that is currently implemented, but please note that this may change in the future. It is currently not recommended that this feature is used within a production environment.

There are two parameters which you can modify to control how polling occurs. The first one is Seam.Remoting.pollInterval, which controls how long to wait between subsequent polls for new messages. This parameter is expressed in seconds, and its default setting is 10.

The second parameter is Seam.Remoting.pollTimeout, and is also expressed as seconds. It controls how long a request to the server should wait for a new message before timing out and sending an empty response. Its default is 0 seconds, which means that when the server is polled, if there are no messages ready for delivery then an empty response will be immediately returned.

Caution should be used when setting a high pollTimeout value; each request that has to wait for a message means that a server thread is tied up until a message is received, or until the request times out. If many such requests are being served simultaneously, it could mean a large number of threads become tied up because of this reason.

It is recommended that you set these options via components.xml, however they can be overridden via Javascript if desired. The following example demonstrates how to configure the polling to occur much more aggressively. You should set these parameters to suitable values for your application:

Via components.xml:


<remoting:remoting poll-timeout="5" poll-interval="1"/>

Via JavaScript:


// Attendere 1 secondo tra la ricezione della risposta del pool e l'invio della successiva richiesta di pool.
Seam.Remoting.pollInterval = 1;
  
// Attendere fino a 5 secondi sul server per nuovi messaggi
Seam.Remoting.pollTimeout = 5;   

Per chi preferisce utilizzare Google Web Toolkit (GWT) per sviluppare applicazioni AJAX dinamiche, Seam fornisce uno strato di integrazione che consente ai componenti GWT di interagire direttamente con i componenti Seam.

Per usare GWT, si suppone che ci sia già una certa familiarità con gli strumenti GWT. Maggiori informazioni si possono trovare in http://code.google.com/webtoolkit/. Questo capitolo non cercherà di spiegare come funziona GWT o come si usa.

Il primo passo per preparare i componenti Seam che saranno richiamati tramite GWT è di creare sia l'interfaccia per il servizio sincrono che quella per il servizio asincrono per i metodi che si desidera chiamare. Entrambe queste interfacce devono estendere l'interfaccia GWT com.google.gwt.user.client.rpc.RemoteService:

public interface MyService extends RemoteService {

    public String askIt(String question);      
 }

L'interfaccia asincrona deve essere identica, salvo che contiene un parametro aggiuntivo AsyncCallback per ciascuno dei metodi che dichiara:

public interface MyServiceAsync extends RemoteService {

   public void askIt(String question, AsyncCallback callback);
}

L'interfaccia asincrona, MyServiceAsync in questo esempio, sarà implementata da GWT e non dovrà mai essere implementata direttamente.

Il passo successivo è di creare un componente Seam che implementa l'interfaccia sincrona:

@Name("org.jboss.seam.example.remoting.gwt.client.MyService")

public class ServiceImpl implements MyService {
   @WebRemote
   public String askIt(String question) {
   
      if (!validate(question)) {
         throw new IllegalStateException("Hey, this shouldn't happen, I checked on the client, " +
         "but its always good to double check.");
      }
      return "42. Its the real question that you seek now.";
   }
   
   public boolean validate(String q) {
      ValidationUtility util = new ValidationUtility();
      return util.isValid(q);
   }
}

Il nome del componente Seam deve corrispondere al nome completo dell'interfaccia client GWT (come illustrato), altrimento la servlet delle risorse non riuscirà a trovarlo quando un client farà una chiamata GWT. I metodi che si vogliono rendere accessibili tramite GWT devono anche essere annotati con l'annotazione @WebRemote.

Il prossimo passo è scrivere un metodo che restituisce l'interfaccia asincrona al componente. Questo metodo può essere posizionato all'interno della classe del componente GWT e sarà usato da questo per ottenere un riferimento al client di collegamento asincrono:

private MyServiceAsync getService() {       

   String endpointURL = GWT.getModuleBaseURL() + "seam/resource/gwt";      
      
   MyServiceAsync svc = (MyServiceAsync) GWT.create(MyService.class);
   ((ServiceDefTarget) svc).setServiceEntryPoint(endpointURL);
   return svc;     
}

Il passo finale è scrivere il codice del componente GWT che invoca il metodo sul client di collegamento. Il seguente esempio definisce una semplice interfaccia utente con una label, un campo di input e un bottone:



public class AskQuestionWidget extends Composite {
   private AbsolutePanel panel = new AbsolutePanel();
   
   public AskQuestionWidget() {      
      Label lbl = new Label("OK, what do you want to know?");
      panel.add(lbl);
      final TextBox box = new TextBox();
      box.setText("What is the meaning of life?");
      panel.add(box);
      Button ok = new Button("Ask");
      ok.addClickListener(new ClickListener() {
         public void onClick(Widget w) {
            ValidationUtility valid = new ValidationUtility();
            if (!valid.isValid(box.getText())) {
               Window.alert("A question has to end with a '?'");
            } else {
               askServer(box.getText());
            } 
         }
      });
      panel.add(ok);
      
      initWidget(panel);
   }
   private void askServer(String text) {
      getService().askIt(text, new AsyncCallback() {
         public void onFailure(Throwable t) {
            Window.alert(t.getMessage());
         }
         public void onSuccess(Object data) {
            Window.alert((String) data);
         }         
      });      
   }
   
   ...

Facendo click, il bottone invoca il metodo askServer() passando il contenuto del campo input (in questo esempio viene fatta anche una validazione per assicurare che il testo inserito sia una domanda valida). Il metodo askServer() acquisisce un riferimento al client di collegamento asincrono (restituito dal metodo getService()) e invoca il metodo askIt(). Il risultato (o il messaggio di errore se la chiamata fallisce) è mostrato in una finestra di segnalazione.

Il codice completo di questo esempio si trova nella distribuzione Seam sotto la cartella examples/remoting/gwt.

Per eseguire applicazioni GWT c'è un passaggio di compilazione da Java a JavaScript (che compatta il codice e lo rende illeggibile). C'è uno strumento Ant che può essere utilizzato al posto della linea di comando o dello strumento grafico fornito con GWT. Per usarlo occorre avere il jar con lo strumento Ant nel classpath di Ant, e anche l'ambiente GWT scaricato (che sarebbe comunque necessario per far eseguire il componente in modalità hosted).

Quindi nel file Ant va scritto (verso l'inizio del file Ant):


<taskdef uri="antlib:de.samaflost.gwttasks"
   resource="de/samaflost/gwttasks/antlib.xml"
   classpath="./lib/gwttasks.jar"/>
   
   <property file="build.properties"/>

Creare un file build.properties, con il seguente contenuto:

gwt.home=/gwt_home_dir

Questo naturalmente deve puntare alla cartella dove si è installato GWT. A questo punto per usarlo creare il target:


<!-- Le seguenti sono istruzioni pratiche per lo sviluppo in GWT.
   Per usare GWT occorre aver scaricato GWT separatamente -->
   <target name="gwt-compile">
      <!-- in questo caso stiamo riposizionando la roba generata da gwt, perci� in questo caso
      possiamo avere solo un modulo GWT - facciamo in questo modo appositamente per mantenere breve l'URL -->
      <delete>
         <fileset dir="view"/>
      </delete>
      <gwt:compile outDir="build/gwt"
         gwtHome="${gwt.home}"
         classBase="${gwt.module.name}"
         sourceclasspath="src"/>
      <copy todir="view">
         <fileset dir="build/gwt/${gwt.module.name}"/>
      </copy>
   </target
>

Quando viene chiamato, questo target compila l'applicazione GWT e la copia nella cartella indicata (che dovrebbe trovarsi nella parte webapp del war. Si ricordi che GWT genera oggetti HTML e JavaScript). Il codice risultante dalla generazione con gwt-compile non va mai modificato. Le modifiche vanno fatte sulla cartella dei sorgenti GWT.

Si tenga presente che GWT contiene un browser per la modalità hosted che andrebbe utilizzato se si sviluppa con GWT. Non utilizzandolo e compilando ogni volta, si rinuncia alla maggior parte del toolkit (in effetti a chi non può o non vuole usare il browser in modalità hosted, sarebbe da dirgli che NON dovrebbe usare GWT per niente, dato che quella è la cosa migliore!).

The Spring integration (part of the Seam IoC module) allows easy migration of Spring-based projects to Seam and allows Spring applications to take advantage of key Seam features like conversations and Seam's more sophisticated persistence context management.

Note! The Spring integration code is included in the jboss-seam-ioc library. This dependency is required for all seam-spring integration techniques covered in this chapter.

Seam's support for Spring provides the ability to:

  • inject Seam component instances into Spring beans

  • inject Spring beans into Seam components

  • turn Spring beans into Seam components

  • allow Spring beans to live in any Seam context

  • start a spring WebApplicationContext with a Seam component

  • Support for Spring PlatformTransactionManagement

  • provides a Seam managed replacement for Spring's OpenEntityManagerInViewFilter and OpenSessionInViewFilter

  • Support for Spring TaskExecutors to back @Asynchronous calls

Injecting Seam component instances into Spring beans is accomplished using the <seam:instance/> namespace handler. To enable the Seam namespace handler, the Seam namespace must be added to the Spring beans definition file:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:seam="http://jboss.com/products/seam/spring-seam"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                        http://jboss.com/products/seam/spring-seam
                        http://jboss.com/products/seam/spring-seam-2.1.xsd"
>

Now any Seam component may be injected into any Spring bean:


<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty">
        <seam:instance name="someComponent"/>
    </property>
</bean
>

An EL expression may be used instead of a component name:


<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty">
        <seam:instance name="#{someExpression}"/>
    </property>
</bean
>

Seam component instances may even be made available for injection into Spring beans by a Spring bean id.


<seam:instance name="someComponent" id="someSeamComponentInstance"/>

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty" ref="someSeamComponentInstance">
</bean>

Now for the caveat!

Seam was designed from the ground up to support a stateful component model with multiple contexts. Spring was not. Unlike Seam bijection, Spring injection does not occur at method invocation time. Instead, injection happens only when the Spring bean is instantiated. So the instance available when the bean is instantiated will be the same instance that the bean uses for the entire life of the bean. For example, if a Seam CONVERSATION-scoped component instance is directly injected into a singleton Spring bean, that singleton will hold a reference to the same instance long after the conversation is over! We call this problem scope impedance. Seam bijection ensures that scope impedance is maintained naturally as an invocation flows through the system. In Spring, we need to inject a proxy of the Seam component, and resolve the reference when the proxy is invoked.

The <seam:instance/> tag lets us automatically proxy the Seam component.


<seam:instance id="seamManagedEM" name="someManagedEMComponent" proxy="true"/>

<bean id="someSpringBean" class="SomeSpringBeanClass">
    <property name="entityManager" ref="seamManagedEM">
</bean
>

This example shows one way to use a Seam-managed persistence context from a Spring bean. (For a more robust way to use Seam-managed persistence contexts as a replacement for the Spring OpenEntityManagerInView filter see section on Using a Seam Managed Persistence Context in Spring)

The Seam integration package also lets you use Seam's contexts as Spring 2.0 style custom scopes. This lets you declare any Spring bean in any of Seam's contexts. However, note once again that Spring's component model was never architected to support statefulness, so please use this feature with great care. In particular, clustering of session or conversation scoped Spring beans is deeply problematic, and care must be taken when injecting a bean or component from a wider scope into a bean of a narrower scope.

By specifying <seam:configure-scopes/> once in a Spring bean factory configuration, all of the Seam scopes will be available to Spring beans as custom scopes. To associate a Spring bean with a particular Seam scope, specify the Seam scope in the scope attribute of the bean definition.


<!-- Only needs to be specified once per bean factory-->
<seam:configure-scopes/>

...

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>

The prefix of the scope name may be changed by specifying the prefix attribute in the configure-scopes definition. (The default prefix is seam.)

By default an instance of a Spring Component registered in this way is not automatically created when referenced using @In. To have an instance auto-created you must either specify @In(create=true) at the injection point to identify a specific bean to be auto created or you can use the default-auto-create attribute of configure-scopes to make all spring beans who use a seam scope auto created.

Seam-scoped Spring beans defined this way can be injected into other Spring beans without the use of <seam:instance/>. However, care must be taken to ensure scope impedance is maintained. The normal approach used in Spring is to specify <aop:scoped-proxy/> in the bean definition. However, Seam-scoped Spring beans are not compatible with <aop:scoped-proxy/>. So if you need to inject a Seam-scoped Spring bean into a singleton, <seam:instance/> must be used:


<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>

...

<bean id="someSingleton">
    <property name="someSeamScopedSpringBean">
        <seam:instance name="someSpringBean" proxy="true"/>
    </property>
</bean
>

One of the most powerful features of Seam is its conversation scope and the ability to have an EntityManager open for the life of a conversation. This eliminates many of the problems associated with the detachment and re-attachment of entities as well as mitigates occurrences of the dreaded LazyInitializationException. Spring does not provide a way to manage an persistence context beyond the scope of a single web request (OpenEntityManagerInViewFilter). So, it would be nice if Spring developers could have access to a Seam managed persistence context using all of the same tools Spring provides for integration with JPA(e.g. PersistenceAnnotationBeanPostProcessor, JpaTemplate, etc.)

Seam provides a way for Spring to access a Seam managed persistence context with Spring's provided JPA tools bringing conversation scoped persistence context capabilities to Spring applications.

This integration work provides the following functionality:

Spring's persistence context propagation model allows only one open EntityManager per EntityManagerFactory so the Seam integration works by wrapping an EntityManagerFactory around a Seam managed persistence context.


<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
           <property name="persistenceContextName" value="entityManager"/>
</bean
>

Where 'persistenceContextName' is the name of the Seam managed persistence context component. By default this EntityManagerFactory has a unitName equal to the Seam component name or in this case 'entityManager'. If you wish to provide a different unitName you can do so by providing a persistenceUnitName like so:


<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
           <property name="persistenceContextName" value="entityManager"/>
        <property name="persistenceUnitName" value="bookingDatabase:extended"/>
</bean
>

This EntityManagerFactory can then be used in any Spring provided tools. For example, using Spring's PersistenceAnnotationBeanPostProcessor is the exact same as before.


<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

If you define your real EntityManagerFactory in Spring but wish to use a Seam managed persistence context you can tell the PersistenceAnnotationBeanPostProcessor which persistenctUnitName you wish to use by default by specifying the defaultPersistenceUnitName property.

The applicationContext.xml might look like:


<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="bookingDatabase"/>
</bean>
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
           <property name="persistenceContextName" value="entityManager"/>
        <property name="persistenceUnitName" value="bookingDatabase:extended"/>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
        <property name="defaultPersistenceUnitName" value="bookingDatabase:extended"/>
</bean
>

The component.xml might look like:


<persistence:managed-persistence-context name="entityManager"
        auto-create="true" entity-manager-factory="#{entityManagerFactory}"/>

JpaTemplate and JpaDaoSupport are configured the same way for a Seam managed persistence context as they would be fore a Seam managed persistence context.


<bean id="bookingService" class="org.jboss.seam.example.spring.BookingService">
        <property name="entityManagerFactory" ref="seamEntityManagerFactory"/>
</bean
>

The Seam Spring integration also provides support for complete access to a Seam managed Hibernate session using spring's tools. This integration is very similar to the JPA integration.

Like Spring's JPA integration spring's propagation model allows only one open EntityManager per EntityManagerFactory per transaction??? to be available to spring tools. So, the Seam Session integration works by wrapping a proxy SessionFactory around a Seam managed Hibernate session context.


<bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean">
        <property name="sessionName" value="hibernateSession"/>
</bean
>

Where 'sessionName' is the name of the persistence:managed-hibernate-session component. This SessionFactory can then be used in any Spring provided tools. The integration also provides support for calls to SessionFactory.getCurrentInstance() as long as you call getCurrentInstance() on the SeamManagedSessionFactory.

Although it is possible to use the Spring ContextLoaderListener to start your application's Spring ApplicationContext there are a couple of limitations.

To overcome these two limitations the Spring integration includes a Seam component that will start a Spring ApplicationContext. To use this Seam component place the <spring:context-loader/> definition in the components.xml. Specify your Spring context file location in the config-locations attribute. If more than one config file is needed you can place them in the nested <spring:config-locations/> element following standard components.xml multi value practices.


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:spring="http://jboss.com/products/seam/spring"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.com/products/seam/components
                                http://jboss.com/products/seam/components-2.1.xsd
                                http://jboss.com/products/seam/spring
                                http://jboss.com/products/seam/spring-2.1.xsd">

        <spring:context-loader config-locations="/WEB-INF/applicationContext.xml"/>

</components
>

Google Guice è una libreria che fornisce una dependency injection leggera attraverso la risoluzione type-safe. L'integrazione con Guice (parte del modulo Seam IoC) consente l'uso dell'iniezione Guice per tutti i componenti Seam annotati con l'annotazione @Guice. In aggiunta alla regolare bijection, fornita da Seam (che diviene opzionale), Seam delega agli injector Guice noti di soddisfare le dipendenze del componente. Guice può essere utile per legare parti non-Seam di applicazione grosse o legacy assieme a Seam.

Nota

L'integrazione Guice è messa nella libreria jboss-seam-ioc. Questa dipendenza è richiesta per tutte le tecniche di integrazione coperta in questo capitolo. Occorre anche il file JAR Guice nel classpath.

Dire a Seam quale injector Guice usare agganciandolo alla proprietà injection del componente Guice di inizializzazione nel descrittore del componente Seam (components.xml):


<components xmlns="http://jboss.com/products/seam/components"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:guice="http://jboss.org/products/seam/guice"
   xsi:schemaLocation="
      http://jboss.com/products/seam/guice
      http://jboss.com/products/seam/guice-2.1.xsd
      http://jboss.com/products/seam/components
      http://jboss.com/products/seam/components-2.1.xsd">

   <guice:init injector="#{myGuiceInjector}"/>

</components
>

myGuiceInjector deve risolvere ad un componente Seam che implementi l'interfaccia Guice Injector.

Tuttavia dover creare un injector è pura scrittura di codice. Ciò che si vuole essere in grado di fare è semplicemente agganciare Seam ai propri moduli Guice. Fortunamente c'è un componente Seam predefinito che implementa l'interfaccia Injector per fare ciò. Si può configurarlo nel descrittore del componente Seam con il seguente codice.


<guice:injector name="myGuiceInjector">
   <guice:modules
>  
      <value
>com.example.guice.GuiceModule1</value
>  
      <value
>com.example.guice.GuiceModule2</value
>  
   </guice:modules
>  
</guice:injector
>

Certamente si può anche usare un injector che viene già usato in un'altra parte anche non-Seam della proria applicazione. Questa è una delle principali motivazioni per creare quest'integrazione. Poiché l'injector è definito con un'espressione EL, si può ottenerlo in un qualsiasi modo si voglia. Per esempio si può usare il pattern del componente factory di Seam per fornire l'injector.

@Name("myGuiceInjectorFactory")

public InjectorFactory
{
   @Factory(name = "myGuiceInjector", scope = APPLICATION, create = true)
   public Injector getInjector()
   {
      // Your code that returns injector    
   }
}

Ecco tutto! Si controlli l'esempio guice nella distribuzione Seam per vedere in azione l'integrazione Guice!

Hibernate Search è configurato in uno dei file META-INF/persistence.xml o hibernate.cfg.xml.

La configurazione di Hibernate Search ha dei valori di default impostati per la maggior parte dei parametri di configurazione. Ecco qua una configurazione minimale di persistence unit per poter iniziare.


<persistence-unit name="sample">
   <jta-data-source
>java:/DefaultDS</jta-data-source>
   <properties>
      [...]
      <!-- utilizza un file system basato su indice -->
      <property name="hibernate.search.default.directory_provider" 
         value="org.hibernate.search.store.FSDirectoryProvider"/>
      <!-- directory dove gli indici verranno memorizzati -->
      <property name="hibernate.search.default.indexBase" 
         value="/Users/prod/apps/dvdstore/dvdindexes"/>
   </properties>
</persistence-unit
>

Se si pensa di usare Hibernate Annotation o EntityManager 3.2.x (incorporato in JBoss AS 4.2.x e successivi), occorre configurare anche gli opportuni event listener.


<persistence-unit name="sample">
   <jta-data-source
>java:/DefaultDS</jta-data-source>
   <properties>
      [...]
      <!-- utilizza un file system basato su indice -->
      <property name="hibernate.search.default.directory_provider" 
                value="org.hibernate.search.store.FSDirectoryProvider"/>
      <!-- directory dove gli indici verranno memorizzati -->
      <property name="hibernate.search.default.indexBase" 
                value="/Users/prod/apps/dvdstore/dvdindexes"/>

      <property name="hibernate.ejb.event.post-insert" 
                value="org.hibernate.search.event.FullTextIndexEventListener"/>
      <property name="hibernate.ejb.event.post-update" 
                value="org.hibernate.search.event.FullTextIndexEventListener"/>
      <property name="hibernate.ejb.event.post-delete" 
                value="org.hibernate.search.event.FullTextIndexEventListener"/>

   </properties>
</persistence-unit
>

In aggiunta al file di configurazione, devono essere deployati i seguenti jar:

Hibernate Search impiega annotazioni per mappare le entità ad un indice di Lucene, per maggiori informazioni si guardi alla documentazione di riferimento.

Hibernate Search è pienamente integrato con la API e la semantica di JPA/Hibernate. Passare da una query basata su HQL o Criteria richiede solo poche linee di codice. La principale API con cui l'applicazione interagisce è l'API FullTextSession (sottoclasse della Session di Hibernate).

Quando Hibernate Search è presente, JBoss Seam inietta una FullTextSession.

@Stateful

@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
   @In FullTextSession session;
   public void search(String searchString) {
      org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
      org.hibernate.Query query session.createFullTextQuery(luceneQuery, Product.class);
      searchResults = query
            .setMaxResults(pageSize + 1)
            .setFirstResult(pageSize * currentPage)
            .list();
   }
   [...]
}

Nota

FullTextSession estende org.hibernate.Session così che può essere usata come una regolare Hibernate Session.

Se viene usata la Java Persistence API, è proposta un'integrazione più lieve.

@Stateful

@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
   
   @In FullTextEntityManager em;
   public void search(String searchString) {
      org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
      javax.persistence.Query query = em.createFullTextQuery(luceneQuery, Product.class);
      searchResults = query
            .setMaxResults(pageSize + 1)
            .setFirstResult(pageSize * currentPage)
            .getResultList();
   }
   [...]
}

Quando Hibernate Search è presente, viene iniettato un FulltextEntityManager. FullTextEntityManager estende EntityManager con metodi specifici di ricerca, allo stesso modo FullTextSession estende Session.

Quando viene impiegata l'iniezione di un session bean EJB3.0 o message driven (cioè tramite l'annotazione @PersistenceContext), non è possibile rimpiazzare l'interfaccia EntityManager con l'interfaccia FullTextEntityManager nella dichiarazione. Comunque l'implementazione iniettata sarà FullTextEntityManager: è quindi possibile il downcasting.

@Stateful

@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
  
   @PersistenceContext EntityManager em;
   public void search(String searchString) {
      org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
      FullTextEntityManager ftEm = (FullTextEntityManager) em;
      javax.persistence.Query query = ftEm.createFullTextQuery(luceneQuery, Product.class);
      searchResults = query
            .setMaxResults(pageSize + 1)
            .setFirstResult(pageSize * currentPage)
            .getResultList();
   }
   [...]
}

Attenzione

Per persone abituate a Hibernate Seam fuori da Seam, notare che l'uso di Search.createFullTextSession non è necessario.

Controlla gli esempi DVDStore o Blog della distribuzione di JBoss Seam per un uso pratico di Hibernate Search.

La configurazione è un argomento molto noioso ed un passatempo estremamente tedioso. Sfortunatamente sono richieste parecchie linee di XML per integrare Seam con l'implementazione JSF ed il servlet container. Non c'è alcun bisogno di soffermarsi sulle seguenti sezioni; non si dovrà mai scrivere queste cose a mano, poiché basta usare seam-gen per iniziare l'applicazione oppure basta copiare ed incollare il codice dagli esempi di applicazione!

In primo luogo si comincia a guardare alla configurazione base che server ogni volta che si usa Seam con JSF.

Certamente occorre un servlet faces!


<servlet>
    <servlet-name
>Faces Servlet</servlet-name>
    <servlet-class
>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup
>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name
>Faces Servlet</servlet-name>
    <url-pattern
>*.seam</url-pattern>
</servlet-mapping
>

(Si può sistemare il pattern dell'URL a proprio piacimento.)

In aggiunta, Seam richiede la seguente voce nel file web.xml:


<listener>
    <listener-class
>org.jboss.seam.servlet.SeamListener</listener-class>
</listener
>

Questo listener è responsabile dell'avvio di Seam e della distruzione dei contesti di sessione e di applicazione.

Alcune implementazioni JSF hanno un'implementazione erronea della conservazione dello stato lato server, che interferisce con la propagazione della conversazione di Seam. Se si hanno problemi con la propagazione della conversazione durante l'invio di form, si provi a cambiare impostanto la conservazione lato client. Occorre impostare questo in web.xml:


<context-param>
    <param-name
>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value
>client</param-value>
</context-param
>

C'è una piccola parte grigia nella specifica JSF riguardante la mutabilità dei valore dello stato della vista. Poiché Seam usa lo stato della vista JSF per tornare allo scope PAGE, questo in alcuni casi può diventare un problema. Se si usa la conservazione dello stato lato server con JSF-RI e si vuole che il bean con scope PAGE mantenga l'esatto valore per una data vista di pagina, occorre specificare il seguente parametro di contesto. Altrimenti se un utente usa il pulsante "indietro" un componente con scope PAGE avrà l'ultimo valore se questo non è cambiato nella pagina "indietro". (si veda Spec Issue ). Quest'impostazione non è abilitata di default a causa della performance nella serializzazione della vista JSF ad ogni richiesta.


<context-param>
    <param-name
>com.sun.faces.serializeServerState</param-name>
    <param-value
>true</param-value>
</context-param
>

Seam non ha bisogno di filtri servlet per le operazioni base. Comunque ci sono parecchie caratteristiche che dipendono dall'uso dei filtri. Per facilitare le cose, Seam lascia aggiungere e configurare i filtri servlet così come si configurano gli altri componenti predefiniti di Seam. Per sfruttare questa caratteristica occorre installa un filtro master in web.xml:


<filter>
    <filter-name
>Seam Filter</filter-name>
    <filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name
>Seam Filter</filter-name>
    <url-pattern
>/*</url-pattern>
</filter-mapping
>

Il filtro master di Seam deve essere il primo filtro specificato in web.xml. Questo assicura che venga eseguito per primo.

I filtri Seam condividono un numero di attributi comuni, si possono impostare questi in components.xml in aggiunto ai parametri discussi sotto:

Note that the patterns are matched against the URI path of the request (see HttpServletRequest.getURIPath()) and that the name of the servlet context is removed before matching.

Adding the master filter enables the following built-in filters.

In a Seam application, EJB components have a certain duality, as they are managed by both the EJB container and Seam. Actually, it's more that Seam resolves EJB component references, manages the lifetime of stateful session bean components, and also participates in each method call via interceptors. Let's start with the configuration of the Seam interceptor chain.

We need to apply the SeamInterceptor to our Seam EJB components. This interceptor delegates to a set of built-in server-side interceptors that handle such concerns as bijection, conversation demarcation, and business process signals. The simplest way to do this across an entire application is to add the following interceptor configuration in ejb-jar.xml:


<interceptors>
    <interceptor>
        <interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
    </interceptor>
</interceptors>
   
<assembly-descriptor>
    <interceptor-binding>
        <ejb-name
>*</ejb-name>
        <interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
    </interceptor-binding>
</assembly-descriptor>

Seam needs to know where to go to find session beans in JNDI. One way to do this is specify the @JndiName annotation on every session bean Seam component. However, this is quite tedious. A better approach is to specify a pattern that Seam can use to calculate the JNDI name from the EJB name. Unfortunately, there is no standard mapping to global JNDI defined in the EJB3 specification, so this mapping is vendor-specific (and may depend on your own naming conventions as well). We usually specify this option in components.xml.

For JBoss AS, the following pattern is correct:


<core:init jndi-name="earName/#{ejbName}/local" />

In this case, earName is the name of the EAR in which the bean is deployed, Seam replaces #{ejbName} with the name of the EJB, and the final segment represents the type of interface (local or remote).

Outside the context of an EAR (when using the JBoss Embeddable EJB3 container), the first segment is dropped since there is no EAR, leaving us with the following pattern:


<core:init jndi-name="#{ejbName}/local" />

How these JNDI names are resolved and somehow locate an EJB component might appear a bit like black magic at this point, so let's dig into the details. First, let's talk about how the EJB components get into JNDI.

The folks at JBoss don't care much for XML, if you can't tell. So when they designed JBoss AS, they decided that EJB components would get assigned a global JNDI name automatically, using the pattern just described (i.e., EAR name/EJB name/interface type). The EJB name is the first non-empty value from the following list:

Guardiamo un esempio. Si assuma di avere definiti il seguente bean EJB ed un'interfaccia.

package com.example.myapp;


import javax.ejb.Local;
@Local
public class Authenticator
{
    boolean authenticate();
}
package com.example.myapp;
import javax.ejb.Stateless;
@Stateless
@Name("authenticator")
public class AuthenticatorBean implements Authenticator
{ 
    public boolean authenticate() { ... }
}

Assuming your EJB bean class is deployed in an EAR named myapp, the global JNDI name myapp/AuthenticatorBean/local will be assigned to it on JBoss AS. As you learned, you can reference this EJB component as a Seam component with the name authenticator and Seam will take care of finding it in JNDI according to the JNDI pattern (or @JndiName annotation).

So what about the rest of the application servers? Well, according to the Java EE spec, which most vendors try to adhere to religiously, you have to declare an EJB reference for your EJB in order for it to be assigned a JNDI name. That requires some XML. It also means that it is up to you to establish a JNDI naming convention so that you can leverage the Seam JNDI pattern. You might find the JBoss convention a good one to follow.

There are two places you have to define the EJB reference when using Seam on non-JBoss application servers. If you are going to be looking up the Seam EJB component through JSF (in a JSF view or as a JSF action listener) or a Seam JavaBean component, then you must declare the EJB reference in web.xml. Here is the EJB reference for the example component just shown:


<ejb-local-ref>
    <ejb-ref-name
>myapp/AuthenticatorBean/local</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.example.vehicles.action.Authenticator</local>
</ejb-local-ref>

This reference will cover most uses of the component in a Seam application. However, if you want to be able to inject a Seam EJB component into another Seam EJB component using @In, you need to define this EJB reference in another location. This time, it must be defined in ejb-jar.xml, and it's a bit tricker.

Within the context of an EJB method call, you have to deal with a somewhat sheltered JNDI context. When Seam attempts to find another Seam EJB component to satisfy an injection point defined using @In, whether or not it finds it depends on whether an EJB reference exists in JNDI. Strictly speaking, you cannot simply resolve JNDI names as you please. You have to define the references explicitly. Fortunately, JBoss recognized how aggrevating this would be for the developer and all versions of JBoss automatically register EJBs so they are always available in JNDI, both to the web container and the EJB container. So if you are using JBoss, you can skip the next few paragraphs. However, if you are deploying to GlassFish, pay close attention.

For application servers that stubbornly adhere to the EJB specification, EJB references must always be defined explicitly. But unlike with the web context, where a single resource reference covers all uses of the EJB from the web environment, you cannot declare EJB references globally in the EJB container. Instead, you have to specify the JNDI resources for a given EJB component one-by-one.

Let's assume that we have an EJB named RegisterAction (the name is resolved using the three steps mentioned previously). That EJB has the following Seam injection:

@In(create = true)

Authenticator authenticator;

In order for this injection to work, the link must be established in the ejb-jar.xml file as follows:


<ejb-jar>
    <enterprise-beans>
        <session>
            <ejb-name
>RegisterAction</ejb-name>
            <ejb-local-ref>
                <ejb-ref-name
>myapp/AuthenticatorAction/local</ejb-ref-name>
                <ejb-ref-type
>Session</ejb-ref-type>
                <local
>com.example.myapp.Authenticator</local>
            </ejb-local-ref>
        </session>
    </enterprise-beans>

    ...
    
</ejb-jar>

Notice that the contents of the <ejb-local-ref> are identical to what we defined in web.xml. What we are doing is bringing the reference into the EJB context where it can be used by the RegisterAction bean. You will need to add one of these references for any injection of a Seam EJB compoenent into another Seam EJB component using @In. (You can see an example of this setup in the jee5/booking example).

But what about @EJB? It's true that you can inject one EJB into another using @EJB. However, by doing so, you are injecting the actual EJB reference rather than the Seam EJB component instance. In this case, some Seam features will work, while others won't. That's because Seam's interceptor is invoked on any method call to an EJB component. But that only invokes Seam's server-side interceptor chain. What you lose is Seam's state management and Seam's client-side interceptor chain. Client-side interceptors handle concerns such as security and concurrency. Also, when injecting a SFSB, there is no guarantee that you will get the SFSB bound to the active session or conversation, whatever the case may be. Thus, you definitely want to inject the Seam EJB component using @In.

That covers how JNDI names are defined and used. The lesson is that with some application servers, such as GlassFish, you are going to have to specify JNDI names for all EJB components explicitly, and sometimes twice! And even if you are following the same naming convention as JBoss AS, the JNDI pattern in Seam may need to be altered. For instance, the global JNDI names are automatically prefixed with java:comp/env on GlassFish, so you need to define the JNDI pattern as follows:


<core:init jndi-name="java:comp/env/earName/#{ejbName}/local" />

Finally, let's talk about transactions. In an EJB3 environment, we recommend the use of a special built-in component for transaction management, that is fully aware of container transactions, and can correctly process transaction success events registered with the Events component. If you don't add this line to your components.xml file, Seam won't know when container-managed transactions end:


<transaction:ejb-transaction/>

Seam comes packaged and configured with Hibernate as the default JPA provider. If you require using a different JPA provider you must tell seam about it.

Telling seam about a different JPA provider can be be done in one of two ways:

Update your application's components.xml so that the generic PersistenceProvider takes precedence over the hibernate version. Simply add the following to the file:


<component name="org.jboss.seam.persistence.persistenceProvider" 
           class="org.jboss.seam.persistence.PersistenceProvider"
           scope="stateless">
</component
>

If you want to take advantage of your JPA provider's non-standard features you will need to write you own implementation of the PersistenceProvider. Use HibernatePersistenceProvider as a starting point (don't forget to give back to the community :). Then you will need to tell seam to use it as before.


<component name="org.jboss.seam.persistence.persistenceProvider" 
           class="org.your.package.YourPersistenceProvider">
</component
>

All that is left is updating the persistence.xml file with the correct provider class, and what ever properties your provider needs. Don't forget to package your new provider's jar files in the application if they are needed.

If you're running in a Java EE 5 environment, this is all the configuration required to start using Seam!

Once you've packaged all this stuff together into an EAR, the archive structure will look something like this:

my-application.ear/
    jboss-seam.jar
    lib/
        jboss-el.jar
    META-INF/
        MANIFEST.MF
        application.xml
    my-application.war/
        META-INF/
            MANIFEST.MF
        WEB-INF/
            web.xml
            components.xml
            faces-config.xml
            lib/
                jsf-facelets.jar
                jboss-seam-ui.jar
        login.jsp
        register.jsp
        ...
    my-application.jar/
        META-INF/
            MANIFEST.MF
            persistence.xml
        seam.properties
        org/
            jboss/
                myapplication/
                    User.class
                    Login.class
                    LoginBean.class
                    Register.class
                    RegisterBean.class
                    ...

You should declare jboss-seam.jar as an ejb module in META-INF/application.xml; jboss-el.jar should be placed in the EAR's lib directory (putting it in the EAR classpath.

If you want to use jBPM or Drools, you must include the needed jars in the EAR's lib directory.

If you want to use facelets (our recommendation), you must include jsf-facelets.jar in the WEB-INF/lib directory of the WAR.

If you want to use the Seam tag library (most Seam applications do), you must include jboss-seam-ui.jar in the WEB-INF/lib directory of the WAR. If you want to use the PDF or email tag libraries, you need to put jboss-seam-pdf.jar or jboss-seam-mail.jar in WEB-INF/lib.

If you want to use the Seam debug page (only works for applications using facelets), you must include jboss-seam-debug.jar in the WEB-INF/lib directory of the WAR.

Seam ships with several example applications that are deployable in any Java EE container that supports EJB 3.0.

I really wish that was all there was to say on the topic of configuration but unfortunately we're only about a third of the way there. If you're too overwhelmed by all this tedious configuration stuff, feel free to skip over the rest of this section and come back to it later.

Seam is useful even if you're not yet ready to take the plunge into EJB 3.0. In this case you would use Hibernate3 or JPA instead of EJB 3.0 persistence, and plain JavaBeans instead of session beans. You'll miss out on some of the nice features of session beans but it will be very easy to migrate to EJB 3.0 when you're ready and, in the meantime, you'll be able to take advantage of Seam's unique declarative state management architecture.

Seam JavaBean components do not provide declarative transaction demarcation like session beans do. You could manage your transactions manually using the JTA UserTransaction or declaratively using Seam's @Transactional annotation. But most applications will just use Seam managed transactions when using Hibernate with JavaBeans.

The Seam distribution includes a version of the booking example application that uses Hibernate3 and JavaBeans instead of EJB3, and another version that uses JPA and JavaBeans. These example applications are ready to deploy into any J2EE application server.

JBoss Embedded lets you run EJB3 components outside the context of the Java EE 5 application server. This is especially, but not only, useful for testing.

The Seam booking example application includes a TestNG integration test suite that runs on JBoss Embedded via SeamTest.

The booking example application may even be deployed to Tomcat.

Embedded JBoss must by installed into Tomcat for Seam applications to run correctly on it. Embedded JBoss runs with JDK 5 or JDK 6 ( see Sezione 41.1, «Dipendenze JDK» for details on using JDK 6). Embedded JBoss can be downloaded here. The process for installing Embedded JBoss into Tomcat 6 is quite simple. First, you should copy the Embedded JBoss JARs and configuration files into Tomcat.

  • Copy all files and directories under the Embedded JBoss bootstrap and lib directories, except for the jndi.properties file, into the Tomcat lib directory.

  • Remove the annotations-api.jar file from the Tomcat lib directory.

Next, two configuration files need to be updated to add Embedded JBoss-specific functionality.

  • Add the Embedded JBoss listener EmbeddedJBossBootstrapListener to conf/server.xml. It must appear after all other listeners in the file:

    
    <Server port="8005" shutdown="SHUTDOWN">

      <!-- Comment these entries out to disable JMX MBeans support used for the 
           administration web application -->
      <Listener className="org.apache.catalina.core.AprLifecycleListener" />
      <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
      <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
      <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener" />
    
      <!-- Add this listener -->
      <Listener className="org.jboss.embedded.tomcat.EmbeddedJBossBootstrapListener" />
  • WAR file scanning should be enabled by adding the WebinfScanner listener to conf/context.xml:

    
    <Context>
        <!-- Default set of monitored resources -->
        <WatchedResource
    >WEB-INF/web.xml</WatchedResource>
        
        <!-- Uncomment this to disable session persistence across Tomcat restarts -->
        <!--
        <Manager pathname="" />
        -->
    
      <!-- Add this listener -->
      <Listener className="org.jboss.embedded.tomcat.WebinfScanner" />
    
    </Context
    >
  • If you are using Sun JDK 6, you need to set the Java option sun.lang.ClassLoader.allowArraySyntax to true in the JAVA_OPTS environment variable used by the Catalina startup script (catalina.bat on Windows or catalina.sh on Unix).

    Open the script appropriate for your operating system in a text editor. Add a new line immediately below the comments at the top of the file where you will define the JAVA_OPTS environment variable. On Windows, use the following syntax:

    set JAVA_OPTS=%JAVA_OPTS% -Dsun.lang.ClassLoader.allowArraySyntax=true

    On Unix, use this syntax instead:

    JAVA_OPTS="$JAVA_OPTS -Dsun.lang.ClassLoader.allowArraySyntax=true"

For more configuration options, please see the Embedded JBoss Tomcat integration wiki entry.

Seam's jBPM integration is not installed by default, so you'll need to enable jBPM by installing a built-in component. You'll also need to explicitly list your process and pageflow definitions. In components.xml:


<bpm:jbpm>
    <bpm:pageflow-definitions>
        <value
>createDocument.jpdl.xml</value>
        <value
>editDocument.jpdl.xml</value>
        <value
>approveDocument.jpdl.xml</value>
    </bpm:pageflow-definitions>
    <bpm:process-definitions>
        <value
>documentLifecycle.jpdl.xml</value>
    </bpm:process-definitions>
</bpm:jbpm
>

No further special configuration is needed if you only have pageflows. If you do have business process definitions, you need to provide a jBPM configuration, and a Hibernate configuration for jBPM. The Seam DVD Store demo includes example jbpm.cfg.xml and hibernate.cfg.xml files that will work with Seam:


<jbpm-configuration>

  <jbpm-context>
    <service name="persistence">
       <factory>
          <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
             <field name="isTransactionEnabled"
><false/></field>
          </bean>
       </factory>
    </service>
    <service name="tx" factory="org.jbpm.tx.TxServiceFactory" />
    <service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
    <service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
    <service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
    <service name="authentication" 
             factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" />
  </jbpm-context>

</jbpm-configuration
>

The most important thing to notice here is that jBPM transaction control is disabled. Seam or EJB3 should control the JTA transactions.

It is very important that the timeout for Stateful Session Beans is set higher than the timeout for HTTP Sessions, otherwise SFSB's may time out before the user's HTTP session has ended. JBoss Application Server has a default session bean timeout of 30 minutes, which is configured in server/default/conf/standardjboss.xml (replace default with your own configuration).

The default SFSB timeout can be adjusted by modifying the value of max-bean-life in the LRUStatefulContextCachePolicy cache configuration:


<container-cache-conf>
    <cache-policy
>org.jboss.ejb.plugins.LRUStatefulContextCachePolicy</cache-policy>
    <cache-policy-conf>
        <min-capacity
>50</min-capacity>
        <max-capacity
>1000000</max-capacity>
        <remover-period
>1800</remover-period>

        <!-- SFSB timeout in seconds; 1800 seconds == 30 minutes -->
        <max-bean-life
>1800</max-bean-life
>  

        <overager-period
>300</overager-period>
        <max-bean-age
>600</max-bean-age>
        <resizer-period
>400</resizer-period>
        <max-cache-miss-period
>60</max-cache-miss-period>
        <min-cache-miss-period
>1</min-cache-miss-period>
        <cache-load-factor
>0.75</cache-load-factor>
    </cache-policy-conf>
</container-cache-conf
>

The default HTTP session timeout can be modified in server/default/deploy/jbossweb-tomcat55.sar/conf/web.xml for JBoss 4.0.x, or in server/default/deploy/jboss-web.deployer/conf/web.xml for JBoss 4.2.x or later. The following entry in this file controls the default session timeout for all web applications:


<session-config>
    <!-- HTTP Session timeout, in minutes -->
    <session-timeout
>30</session-timeout>
</session-config
>

To override this value for your own application, simply include this entry in your application's own web.xml.

Seam scans all jars containing /seam.properties, /META-INF/components.xml or /META-INF/seam.properties on startup for resources. For example, all classes annotated with @Name are registered with Seam as Seam components.

You may also want Seam to handle custom resources. A common use case is to handle a specific annotation and Seam provides specific support for this. First, tell Seam which annotations to handle in /META-INF/seam-deployment.properties:

# A colon-separated list of annotation types to handle
org.jboss.seam.deployment.annotationTypes=com.acme.Foo:com.acme.Bar

Then, during application startup you can get hold of all classes annotated with @Foo:

@Name("fooStartup")
@Scope(APPLICATION)
@Startup
public class FooStartup {

   @In("#{deploymentStrategy.annotatedClasses['com.acme.Foo']}")
   private Set<Class<Object
>
> fooClasses;
   
   @In("#{hotDeploymentStrategy.annotatedClasses['com.acme.Foo']}")
   private Set<Class<Object
>
> hotFooClasses;

   @Create
   public void create() {
      for (Class clazz: fooClasses) {
         handleClass(clazz);
      }
      for (Class clazz: hotFooClasses) {
         handleClass(clazz);
      }
   }
   
   public void handleClass(Class clazz) {
       // ...
   }

}

You can also handle any resource. For example, you process any files with the extension .foo.xml. To do this, we need to write a custom deployment handler:

public class FooDeploymentHandler implements DeploymentHandler {
    private static DeploymentMetadata FOO_METADATA = new DeploymentMetadata()
    {

        public String getFileNameSuffix() {
            return ".foo.xml";
        }
    };
    
   public String getName() {
      return "fooDeploymentHandler";
   }

    public DeploymentMetadata getMetadata() {
        return FOO_METADATA;
    }
}

Here we are just building a list of any files with the suffix .foo.xml.

Then, we need to register the deployment handler with Seam in /META-INF/seam-deployment.properties. You can register multiple deployment handler using a comma separated list.

# For standard deployment
org.jboss.seam.deployment.deploymentHandlers=com.acme.FooDeploymentHandler
# For hot deployment
org.jboss.seam.deployment.hotDeploymentHandlers=com.acme.FooDeploymentHandler

Seam uses deployment handlers internally to install components and namespaces. You can easily access the deployment handler during an APPLICATION scoped component's startup:

@Name("fooStartup")
@Scope(APPLICATION)
@Startup
public class FooStartup {

   @In("#{deploymentStrategy.deploymentHandlers['fooDeploymentHandler']}")
   private FooDeploymentHandler myDeploymentHandler;
   
   @In("#{hotDeploymentStrategy.deploymentHandlers['fooDeploymentHandler']}")
   private FooDeploymentHandler myHotDeploymentHandler;

   @Create
   public void create() {
      for (FileDescriptor fd: myDeploymentHandler.getResources()) {
           handleFooXml(fd);
      }
      
      for (FileDescriptor f: myHotDeploymentHandler.getResources()) {
           handleFooXml(fd);
      }
   }

   public void handleFooXml(FileDescriptor fd) {
       // ...
   }
}

Quando si scrive un'applicazione Seam vengono impiegate molte annotazioni. Seam ti consente di usare annotazioni per ottenere uno stile dichiarativo di programmazione. La maggior parte delle annotazioni che si useranno sono definite dalla specifica EJB3.0. Le annotazioni per la validazione dei dati sono definite dal pacchetto Hibernate Validator. Infine Seam definisce un suo set di annotazioni che verranno descritte in questo capitolo.

Tutte queste annotazioni sono definite nel pacchetto org.jboss.seam.annotations.

Il primo gruppo di annotazioni consente di definire un componente Seam. Queste annotazioni appaiono sulla classe del componente.

@Name
@Name("componentName")

Definisce il nome del componente Seam per una classe. Quest'annotazione è richiesta per tutti i componenti Seam.

@Scope
@Scope(ScopeType.CONVERSATION)

Definisce il contesto di default del componente. I possibili valori sono definiti dalla enumeration ScopeType: EVENT, PAGE, CONVERSATION, SESSION, BUSINESS_PROCESS, APPLICATION, STATELESS.

Quando non viene specificato nessuno scope, quello di default dipende dal tipo di componente. Per session bean stateless, il default è STATELESS. Per gli entity bean ed i session bean stateful, il default è CONVERSATION. Per i JavaBean, il default è EVENT.

@Role
@Role(name="roleName", scope=ScopeType.SESSION)

Consente ad un componente Seam di venir associato a variabili di contesto multiple. Le annotazioni @Name/@Scope definiscono un "ruolo di default". Ciascuna annotazione @Role definisce un ruolo addizionale.

@Roles
@Roles({

        @Role(name="user", scope=ScopeType.CONVERSATION),
        @Role(name="currentUser", scope=ScopeType.SESSION)
   })

Consente la specificazione di ruoli multipli addizionali.

@BypassInterceptors
@BypassInterceptors

Disabilita tutti gli interceptor di Seam per un particolare componente o metodo di un componente.

@JndiName
@JndiName("my/jndi/name")

Specifica il nome JNDI che Seam userà per la ricerca del componente EJB. Se non è stato specificato esplicitamente alcun nome, Seam userà il pattern JNDI specificato da org.jboss.seam.core.init.jndiPattern.

@Conversational
@Conversational

Specifica che il componente con scope conversazione è conversazionale, il che significa che nessun metodo del componente potrà essere chiamato amenoché sia attiva una conversazione long-running.

@PerNestedConversation
@PerNestedConversation

Limita lo scope di un componente con scope conversazione alla sola conversazione padre in cui è stato istanziato. L'istanza del componente non sarà visibile alle conversazioni figlie innestate, che otterranno la propria istanza.

Attenzione: questo è mal definito, poiché implica che un componente sarà visibile per alcune parti del ciclo di richiesta, ed invisibile dopo questo. Non è raccomandato che un'applicazioni usi questa caratteristica!

@Startup
@Scope(APPLICATION) @Startup(depends="org.jboss.seam.bpm.jbpm")

Specifica che un componente con scope applicazione venga avviato immediatamente in fase di inizializzazione. E' usato principalmente per certi componenti predefiniti che avviano un'infrastruttura critica, quali JNDI, datasource, ecc.

@Scope(SESSION) @Startup

Specifica che un componente con scope di sessione viene avviato immediatamente alla creazione della sessione.

@Install
@Install(false)

Specifica se oppure no, un componente debba essere installato di default. La mancanza dell'annotazione @Install indica che un componente viene installato.

@Install(dependencies="org.jboss.seam.bpm.jbpm")

Specifica che un componente debba essere installato solo se i componenti elencati come dipendenze sono pure installati.

@Install(genericDependencies=ManagedQueueSender.class)

Specifica che un componente debba essere installato solo se un componente che è implementato da una certa classe è installato. Questo è utile quando la dipendenza non ha un singolo nome ben noto.

@Install(classDependencies="org.hibernate.Session")

Specifica che un componente debba essere installato solo se la classe (con nome) è nel classpath.

@Install(precedence=BUILT_IN)

Specifica la precedenza del componente. Se esistono più componenti con lo stesso nome, verrà installato quello con precedenza più alta. I valori definiti di precedenza sono (in ordine ascendente):

@Synchronized
@Synchronized(timeout=1000)

Specifica che un componente venga acceduto in modo concorrente da più client, e che Seam serializzi le richieste. Se una richiesta non è in grado di ottenere il lock sul componente in un determinato periodo di timeout, viene sollevata un'eccezione.

@ReadOnly
@ReadOnly

Specifica che un componente JavaBean o metodo di componente non richieda la replicazione dello stato alla fine dell'invocazione.

@AutoCreate
@AutoCreate

Specifica che un componente verrà automaticamente creato, anche se il client non specifica create=true.

Le prossime due annotazioni controllano la bijection. Questi attributi vengono impiegati nelle variabili d'istanza di un componente o nei metodi di accesso alle proprietà.

@In
@In

Specifica che un attributo di componente debba essere iniettato da una variabile di contesto all'inizio di ogni invocazione del componente. Se la variabile di contesto è null, viene lanciata un'eccezione.

@In(required=false)

Specifica che un attributo di componente debba essere iniettato da una variabile di contesto all'inizio di ogni invocazione del componente. La variabile di contesto può essere null.

@In(create=true)

Specifica che un attributo di componente debba essere iniettato da una variabile di contesto all'inizio di ogni invocazione del componente. Se la variabile di contesto è null, viene istanziata da Seam un'istanza del componente.

@In(value="contextVariableName")

Specifica il nome della variabile di contesto in modo esplicito, invece di usare il nome annotato della variabile d'istanza.

@In(value="#{customer.addresses['shipping']}")

Specifica che un attributo di componente debba essere iniettato valutando un'espressione JSF EL all'inizio di ogni invocazione del componente.

@Out
@Out

Specifica che un attributo di componente (di Seam) venga messo in outjection nella sua variabile di contesto alla fine dell'invocazione. Se l'attributo è null, viene lanciata un'eccezione.

@Out(required=false)

Specifica che un attributo di componente (di Seam) venga messo in outjection nella sua variabile di contesto alla fine dell'invocazione. L'attributo può essere null.

@Out(scope=ScopeType.SESSION)

Specifica che un attributo di componente che non è un tipo di componente di Seam venga messo in outjection in uno specifico scope alla fine dell'invocazione.

In alternativa, se non è specificato alcuno scope in modo esplicito, viene usato lo scope del componente con l'attributo @Out (o lo scope EVENT se il componente è stateless).

@Out(value="contextVariableName")

Specifica il nome della variabile di contesto in modo esplicito, invece di usare il nome annotato della variabile d'istanza.

Si noti che è piuttosto comune l'uso di queste annotazioni assieme, per esempio:

@In(create=true) @Out private User currentUser;

La prossima annotazione supporta il pattern manager component, dove un componente Seam che gestisce il ciclo di vita di un'istanza di qualche altra classe deve essere iniettata. Appare sul metodo getter del componente.

La prossima annotazione supporta il pattern factory component dove un componente Seam è responsabile dell'inizializzazione del valore di una variabile di contesto. Questo è utile in particolare per inizializzare qualsiasi stato occorrente per il rendering della risposta in una richiesta non-faces. Appare su un metodo di componente.

Questa annotazione consente di iniettare un Log:

L'ultima annotazione consente di iniettare un valore di un parametro di richiesta:

Queste annotazioni forniscono una demarcazione dichiarativa della conversazione. Appaiono sui metodi dei componenti Seam, solitamente metodi action listener.

Ogni richiesta web ha un contesto di conversazione associato ad essa. La maggior parte di queste conversazioni finisce alla fine della richesta. Se si vuole che una conversazione si espanda lungo richieste multiple, si deve "promuovere" la conversazione corrente a conversazione long-running chiamando un metodo marcato con @Begin.

@Begin
@Begin

Specifica che la conversazione long-running inizia quando questo metodo restuisce un esito non-null senza eccezioni.

@Begin(join=true)

Specifica che se una conversazione long-running è già in esecuzione, il contesto della conversazione viene semplicemente propagato.

@Begin(nested=true)

Specifica che se una conversazione ong-running è già in esecuzione, inizia un nuovo contesto di conversazione innestata. La conversazione innestata terminerà quando viene incontrato il successivo @End, e verrà ripristinata la conversazione più esterna. E' perfettamente legale che esistano conversazioni innestate concorrenti nella stessa conversazione più esterna.

@Begin(pageflow="process definition name")

Specifica il nome della definizione di processo jBPM che definisce il pageflow per questa conversazione.

@Begin(flushMode=FlushModeType.MANUAL)

Specifica la modalità di flush di un qualsiasi contesto di persistenza gestito da Seam. flushMode=FlushModeType.MANUAL supporta l'uso di conversazioni atomiche dove tutte le operazioni di scrittura vengono accodate nel contesto di conversazione fino ad una chiamata esplicita al metodo flush() (che solitamente avviene alla fine della conversazione).

  • join — determina il comportamento quando una conversazione long-running è già attiva. Se true, il contesto viene propagato. Se false, viene lanciata un'eccezione. Di default è false. Quest'impostazione è ignorata quando è specificato nested=true.

  • nested — specifica che una conversazione innestata sia avviata se è già presente una conversazione long-running.

  • flushMode — imposta la modalità flush di ogni sessione Hibernate gestita da Seam o contesto di persistenza JPA che vengono creati durante la conversazione.

  • pageflow — un nome di definizione di processo jBPM deployato via org.jboss.seam.bpm.jbpm.pageflowDefinitions.

@End
@End

Specifica che una conversazione long-running termina quando questo metodo restituisce un esito non-null senza eccezioni.

@StartTask
@StartTask

"Inizia" un task jBPM. Specifica che una conversazione long-running inizi quando questo metodo restituisce un esito non-null senza eccezione. Questa conversazione è associata ad un task jBPM specificato nel parametro della richiesta. Dentro il contesto di questa conversazione è definito anche il contesto del processo di business per l'istanza del processo di business dell'istanza task.

@BeginTask
@BeginTask

Ripristina il lavoro su un task jBPM incompleto. Specifica che la conversazione long-running inizi quando questo metoto restituisce un esito non-null senza eccezioni. Questa conversazione è associata al task jBPM specificata nel parametro di richiesta. Dentro il contesto di questa conversazione, è definito anche un contesto di business process per l'istanza del processo di business dell'istanza task.

@EndTask
@EndTask

"Termina" un task jBPM. Specifica che una conversazione long-running termina quando il metodo ritorna un esito non-null, e che il task corrente è completo. Lancia una transizione jBPM. La transizione lanciata sarà la transizione di default amenoché l'applicazione chiami Transition.setName() sul componente predefinito chiamato transition.

@EndTask(transition="transitionName")

Lancia la transizione jBPM data.

@CreateProcess
@CreateProcess(definition="process definition name")

Crea una nuova istanza di processo jBPM quando il metodo restituisce un esito non-null senza eccezione. L'oggetto ProcessInstance sarà disponibile in una variabile di contesto chiamata processInstance.

@ResumeProcess
@ResumeProcess(processIdParameter="processId")

Reinserisce lo scope di un'istanza esistente di processo jBPM quando il metodo ritorna un esisto non-null senza eccezione. L'oggetto ProcessInstance sarà disponibile in una variabile di contesto chiamata processInstance.

@Transition
@Transition("cancel")

Marca un metodo come segnalante una transizione nell'istanza del processo jBPM corrente quando il metodo restituisce un risultato non-null.

Seam fornisce un'annotazione che consente di forza un rollback di una transazione JTA per certi esiti action listener.

Queste annotazioni sono utili per i componenti Seam JavaBean. Se si usano i componenti EJB 3.0, occorre usare l'annotazione standard Java EE 5.

Queste annotazioni rendono più facile lavorare con JSF.

Le seguenti annotazioni facilitano l'implementazione di liste cliccabili con dietro un bean di sessione stateful. Appaiono sugli attributi.

@DataModel
@DataModel("variableName")

Fa l'outjection di una proprietà di tipo List, Map, Set o Object[] come un DataModel JSF nello scope del componente proprietario (o nello scope EVENT se il componente proprietario è STATELESS). In caso di Map, ogni riga di DataModel è una Map.Entry.

@DataModelSelection
@DataModelSelection

Inietta il valore selezionato dal DataModel JSF (questo è l'elemento della collezione sottostante, o valore di mappa). Se è definito solo un attributo @DataModel per un componente, verrà iniettato il valore selezionato da quel DataModel. Altrimenti, il nome del componente di ciascun DataModel dovrà essere specificato nell'attributo di valore per ogni @DataModelSelection.

Se sul @DataModel associato è specificato lo scope PAGE, allora in aggiunta alla DataModel Selection iniettata, verrà iniettato anche il DataModel associato. In questo caso, se la proprietà annotata con @DataModel è un metodo getter, allora il metodo setter per la proprietà deve essere parte della Business API del componente Seam che lo contiene.

@DataModelSelectionIndex
@DataModelSelectionIndex

Espone l'indice della selezione del DataModel JSF come un attributo del componente (questo è il numero riga della collezione sottostante, o la chiave della mappa). Se per un componente è definito solo un attributo @DataModel, verrà iniettato il valore selezionato da quel DataModel. Altrimenti il nome del componente di ciascun @DataModel deve essere specificato nell'attributo valore per ogni @DataModelSelectionIndex.

Questo capitolo descrive i componenti predefiniti di Seam e le loro proprietà di configurazione. I componenti predefiniti verranno creati anche se non sono elencati nel file components.xml, ma occorre fare l'override delle proprietà di default o specificare più di un compoenente di un certo tipo, viene usato components.xml.

Si noti che si puà sostituire uno dei componenti predefiniti con le proprie implementazioni semplicemente specificando il nome di uno dei componenti predefiniti nella propria classe usando @Name.

Il seguente set di componenti è fornito come supplemento JSF.

org.jboss.seam.faces.dateConverter

Fornisce un converter JSF di default per le proprietà di tipo java.util.Date.

Questo converter è automaticamente registrato con JSF. E' fornito per risparmiare allo sviluppatore il dover specificare un DateTimeConverter su un campo d'input o su un parametro di pagina. Di default, si assume che il tipo sia una data (in opposto a tempo o tempo e data) ed usa lo stile d'input short corretto con il locale dell'utente. Per Locale.US, il pattern d'input è mm/DD/yy. Comunque in accordo con Y2K, l'anno è cambiato da due cifre a quattro (es. mm/DD/yyyy).

E' possibile eseguire l'override del pattern d'input in modo globale, usando la configurazione dei componenti. Per vedere alcuni esempi consultare la JavaDoc per questa classe.

org.jboss.seam.faces.facesMessages

Consentono ai messaggi faces di successo di essere propagati lungo i redirect del browser.

org.jboss.seam.faces.redirect

Un'API per l'esecuzione dei redirect con parametri (utile specialmente per le schermate con risultati di ricerca memorizzabili come segnalibro).

org.jboss.seam.faces.httpError

Un'API per l'invio di errori HTTP.

org.jboss.seam.ui.renderStampStore

Un componente (di default con scope sessione) responsabile del mantenimento della collezione di stampe da renderizzare. Una stampa da renderizzare è un indicatore che mostra se una form renderizzata è stata inviata. Questa memorizzazione è utile quando viene il metodo JSF di salvataggio dello stato lato client, poiché determina se la form è stata mandata sotto il controllo del server anziché nell'albero dei componenti che viene mantenuto sul client.

Per disassociare questo check dalla sessione (che è uno dei principali obiettivi di design del salvataggio di stato lato client) deve essere fornita un'implementazione che memorizzi le stampe da renderizzare nell'applicazione (valida a lungo quanto l'esecuzione dell'applicazione) o il database (valido fino al riavvio del server).

Tutti questi componenti vengono installati quando la classe javax.faces.context.FacesContext è disponibile nel classpath.

Il prossimo gruppo di componenti semplifica la costruzione di interfacce utente internazionalizzate usando Seam.

org.jboss.seam.core.locale

Il locale di Seam.

org.jboss.seam.international.timezone

La timezone di Seam. La timezone ha scope di sessione.

org.jboss.seam.core.resourceBundle

Il resource bundle di Seam. Il resource bundle è stateless. Il resource bundle di Seam esegue una ricerca delle chiavi in una lista di resource bundle di Java.

org.jboss.seam.core.resourceLoader

Il resource loader fornisce accesso alle risorce dell'applicazione ed ai resource bundle.

org.jboss.seam.international.localeSelector

Supporta la selezione del locale o a configuration time, o dall'utente a runtime.

org.jboss.seam.international.timezoneSelector

Supporta la selezione della timezone o a configuration time o da parte dell'utente a runtime.

org.jboss.seam.international.messages

Una mappa contenente messaggi internazionalizzati generati da template di messaggi definiti nel resource bundle di Seam.

org.jboss.seam.theme.themeSelector

Supporta la selezione di temi o a configuration time, o da parte dell'utente a runtime.

org.jboss.seam.theme.theme

Una mappa contenente delle entry di temi.

Tutti questi componenti vengono sempre installati.

Il prossimo gruppo di componenti consente il controllo delle conversazioni da parte dell'applicazione o dell'interfaccia utente.

org.jboss.seam.core.conversation

API per il controllo dell'applicazione degli attributi della conversazione di Seam.

org.jboss.seam.core.conversationList

Componente gestore per la lista delle conversazioni.

org.jboss.seam.core.conversationStack

Componente gestore per lo stack delle conversazioni (breadcrumbs).

org.jboss.seam.faces.switcher

Lo switcher di conversazione.

Tutti questi componenti vengono sempre installati.

Questi componenti sono usati con jBPM.

org.jboss.seam.pageflow.pageflow

Controllo API dei pageflow di Seam.

org.jboss.seam.bpm.actor

API per il controllo dell'applicazione degli attributi dell'attore jBPM associato alla sessione corrente.

org.jboss.seam.bpm.transition

API per il controllo dell'applicazione della transizione jBPM per il task corrente.

org.jboss.seam.bpm.businessProcess

API per il controllo programmatico dell'associazione tra la conversazione ed il processo di business.

org.jboss.seam.bpm.taskInstance

Componente gestore per la TaskInstance jBPM.

org.jboss.seam.bpm.processInstance

Componente gestore per la ProcessInstance jBPM.

org.jboss.seam.bpm.jbpmContext

Componente gestore per il JbpmContext con scope evento.

org.jboss.seam.bpm.taskInstanceList

Manager component for the jBPM task list.

org.jboss.seam.bpm.pooledTaskInstanceList

Componente gestore per la lista di task pooled jBPM.

org.jboss.seam.bpm.taskInstanceListForType

Componente gestore per la lista di task jBPM.

org.jboss.seam.bpm.pooledTask

Action handler per l'assegnamento del task pooled.

org.jboss.seam.bpm.processInstanceFinder

Manager per la lista di task delle istanze di processo.

org.jboss.seam.bpm.processInstanceList

La lista dei task delle istanze di processo.

Tutti questi componenti vengono installati quando viene installato il componente org.jboss.seam.bpm.jbpm.

Questi componenti forniscono un'infrastruttura critica di piattaforma. Si può installare un componente che non è installato di default impostando install="true" nel componente in components.xml.

org.jboss.seam.core.init

Impostazioni di inizializzazione per Seam. Sempre installato.

org.jboss.seam.core.manager

Il componente interno per la pagina Seam e la gestione del contesto di conversazione. Sempre installato.

org.jboss.seam.navigation.pages

Componente interno per la gestione del workspace di Seam. Sempre installato.

org.jboss.seam.bpm.jbpm

Avvia una JbpmConfiguration. Installa org.jboss.seam.bpm.Jbpm come classe.

org.jboss.seam.core.conversationEntries

Componente interno con scope sessione che registra le conversazioni long-running attive tra le richieste.

org.jboss.seam.faces.facesPage

Componente interno con scope pagina che registra il contesto di conversazione associato alla pagina.

org.jboss.seam.persistence.persistenceContexts

Componente interno che registra i contesti di persistenza usati nell'attuale conversazione.

org.jboss.seam.jms.queueConnection

Gestisce una QueueConnection JMS. Installata ogni volta che viene installata una QueueSender gestita.

org.jboss.seam.jms.topicConnection

Gestisce una TopicConnection JMS. Installata ogni volta che viene installata una TopicPublisher gestita.

org.jboss.seam.persistence.persistenceProvider

Layer d'astrazione per le funzionalità non standardizzate del provider JPA.

org.jboss.seam.core.validators

Mette in cache istanze di ClassValidator di Hibernate Validator.

org.jboss.seam.faces.validation

Consente all'applicazione di determinare se la validazione ha fallito o ha avuto successo.

org.jboss.seam.debug.introspector

Supporto per la pagina di debug di Seam.

org.jboss.seam.debug.contexts

Supporto per la pagina di debug di Seam.

org.jboss.seam.exception.exceptions

Componente interno per la gestione delle eccezioni.

org.jboss.seam.transaction.transaction

API per controllare le transazioni ed astrarre l'implementazione della gestione delle transazioni sottostante dietro all'interfaccia compatibile-JTA.

org.jboss.seam.faces.safeActions

Decide se un'espressione d'azione in un URL entrante è sicura. Questo viene fatto controllando che l'espressione d'azione esista nella vista.

Alcune classi speciali di componenti Seam sono installabili più volte sotto nomi specificati nella configurazione Seam. Per esempio, le seguenti linee in components.xml installano e configurano due componenti Seam:


<component name="bookingDatabase"
          class="org.jboss.seam.persistence.ManagedPersistenceContext">
    <property name="persistenceUnitJndiName"
>java:/comp/emf/bookingPersistence</property>
</component>

<component name="userDatabase"
          class="org.jboss.seam.persistence.ManagedPersistenceContext">
    <property name="persistenceUnitJndiName"
>java:/comp/emf/userPersistence</property>
</component
>

I nomi dei componenti Seam sono bookingDatabase e userDatabase.

<entityManager>, org.jboss.seam.persistence.ManagedPersistenceContext

Componente gestore per un EntityManager gestito con scope conversazione con un contesto di persistenza esteso.

<entityManagerFactory>, org.jboss.seam.persistence.EntityManagerFactory

Gestisce una EntityManagerFactory JPA. Questo è utile quando sia usa JPA fuori dall'ambiente EJB3.0.

Si vede la API JavaDoc per ulteriori proprietà di configurazione.

<session>, org.jboss.seam.persistence.ManagedSession

Componente gestore per una Session HIbernate gestita con scope di conversazione.

<sessionFactory>, org.jboss.seam.persistence.HibernateSessionFactory

Gestisce una SessionFactory di Hibernate.

Si vede la API JavaDoc per ulteriori proprietà di configurazione.

<managedQueueSender>, org.jboss.seam.jms.ManagedQueueSender

Componente gestore per un QueueSender JMS gestito con scope evento.

<managedTopicPublisher>, org.jboss.seam.jms.ManagedTopicPublisher

Componente gestore per un TopicPublisher JMS gestito con scope evento.

<managedWorkingMemory>, org.jboss.seam.drools.ManagedWorkingMemory

Componente gestore di una WorkingMemory di Drools gestita con scope di conversazione.

<ruleBase>, org.jboss.seam.drools.RuleBase

Componente gestore di una RuleBase di Drools con scope di applicazione. Si noti che questo non è inteso per l'uso in produzione, poiché non supporta l'installazione dinamica di nuove regole.

<entityHome>, org.jboss.seam.framework.EntityHome

<hibernateEntityHome>, org.jboss.seam.framework.HibernateEntityHome

<entityQuery>, org.jboss.seam.framework.EntityQuery

<hibernateEntityQuery>, org.jboss.seam.framework.HibernateEntityQuery

Seam include un numero di controlli JSF che sono utili per lavorare con Seam. Sono intesi comecomplemento ai controlli predefiniti JSF, e ai controlli di librerie di terze parti. Si raccomanda l'uso in Seam dei tag presenti nelle librerie di JBoss RichFaces, ICEsoft ICEfaces e Apache MyFaces Trinidad. Non si raccomanda l'uso dei tag della libreria Tomahawk.

Per usare questi tag si definisce il namespace "s" nella propria pagina come segue (solo facelets):


<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:s="http://jboss.com/products/seam/taglib"
>

L'esempio ui mostra l'uso di diversi tag.

Descrizione

Assegna un entity converter al componente corrente. Questo è utile per pulsanti radio e controlli dropdown.

Il converter funziona con qualsiasi entity gestita - sia semplice che composta. Il converter deve essere in grado di trovare gli item dichirati nei controlli JSF durante la sottomissione della form, altrimenti si riceverà un errore di validazione.

Attributi

Nessuno.

Configurazione

Si devono usare le transazioni di Seam gestite (vedere Sezione 9.2, «Transazioni gestite da Seam») con <s:convertEntity />.

Se il Managed Persistence Context non viene chiamato entityManager, allora occorre impostarlo in components.xml:

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:ui="http://jboss.com/products/seam/ui">
 
   <ui:jpa-entity-loader entity-manager="#{em}" />

Se si usa una Managed Hibernate Session allora occorre impostarla in components.xml:

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:ui="http://jboss.com/products/seam/ui">
 
   <ui:hibernate-entity-loader />

Se il Managed Hibernate Session non viene chiamato session, allora occorre impostarla in components.xml:

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:ui="http://jboss.com/products/seam/ui">
            
   <ui:hibernate-entity-loader session="#{hibernateSession}" />

Se si vuole usare più di un entity manager con l'entity converter, si può creare una copia dell'entity converter per ciascun entity manager in components.xml - si noti come l'entity converter deleghi all'entity loader l'esecuzione delle operazioni di persistenza:

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:ui="http://jboss.com/products/seam/ui">
 
   <ui:entity-converter name="standardEntityConverter" entity-loader="#{standardEntityLoader}" />
            
   <ui:jpa-entity-loader name="standardEntityLoader" entity-manager="#{standardEntityManager}" />
   
   <ui:entity-converter name="restrictedEntityConverter" entity-loader="#{restrictedEntityLoader}" />
            
   <ui:jpa-entity-loader name="restrictedEntityLoader" entity-manager="#{restrictedEntityManager}" />
<h:selectOneMenu value="#{person.continent}">
   <s:selectItems value="#{continents.resultList}" var="continent" 
                  label="#{continent.name}" />
    <f:converter converterId="standardEntityConverter" />
</h:selectOneMenu
>

Utilizzo


<h:selectOneMenu value="#{person.continent}" required="true">
   <s:selectItems value="#{continents.resultList}" var="continent" 
                  label="#{continent.name}" 
                  noSelectionLabel="Please Select..."/>
   <s:convertEntity />
</h:selectOneMenu
>

Descrizione

"Decora" un campo d'input JSF quando la validazione fallisce o quando è impostato required="true".

Attributi

#{invalid} e #{required} sono disponibili dentro s:decorate; #{required} valuta true se si è impostato il componente input da decorare come richiesto, e #{invalid} valuta true se avviene un errore di validazione.

Utilizzo


<s:decorate template="edit.xhtml">
   <ui:define name="label"
>Country:</ui:define>
      <h:inputText value="#{location.country}" required="true"/>
   </s:decorate
>

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:s="http://jboss.com/products/seam/taglib">
                  
   <div
>   
   
      <s:label styleClass="#{invalid?'error':''}">
         <ui:insert name="label"/>
         <s:span styleClass="required" rendered="#{required}"
>*</s:span>
      </s:label>
        
      <span class="#{invalid?'error':''}">
         <s:validateAll>
            <ui:insert/>
         </s:validateAll>
      </span>
        
      <s:message styleClass="error"/>     
      
   </div
>   
  
</ui:composition
>

Descrizione

Renderizza un controllo per l'upload del file. Questo controllo deve essere usato dentro la form con tipo di codifica multipart/form-data, cioè:


<h:form enctype="multipart/form-data"
>

Per richieste multiple, in web.xml deve essere configurato il filtro servlet Seam Multipart :


<filter>
  <filter-name
>Seam Filter</filter-name>
  <filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name
>Seam Filter</filter-name>
  <url-pattern
>/*</url-pattern>
</filter-mapping
>

Configurazione

Le seguenti opzioni di configurazioni per richieste multipart possono essere configurate in components.xml:

Ecco un esempio:


<component class="org.jboss.seam.web.MultipartFilter">
  <property name="createTempFiles"
>true</property>
  <property name="maxRequestSize"
>1000000</property>
</component
>

Attributi

Utilizzo


<s:fileUpload id="picture" data="#{register.picture}" 
              accept="image/png"
              contentType="#{register.pictureContentType}" />

Descrizione

Un <h:graphicImage> esteso che consente all'immagine di essere creata in un componente Seam; possono essere applicate all'immagine altre trasformazioni.

Tutti gli attributi per <h:graphicImage> sono supportati, così come:

Attributi

Trasformazioni

Per applicare una trasformazione all'immagine, occorre innestare un tag specificando la trasformazione da applicare. Seam attualmente supporta queste trasformazioni:

E' facile creare una trasformazione - si crei un UIComponent che implementi org.jboss.seam.ui.graphicImage.ImageTransform. Dentro il metodo applyTransform() si usi image.getBufferedImage() per recuperare l'immagine originale e image.setBufferedImage() per impostare l'immagine trasformata. Le trasformazioni sono applicare nell'ordine specificato nella vista.

Utilizzo


<s:graphicImage rendered="#{auction.image ne null}"
                value="#{auction.image.data}">
  <s:transformImageSize width="200" maintainRatio="true"/>
</s:graphicImage
>

Seam fornisce anche annotazioni che permettono di impiegare i componenti Seam come convertitori JSF e validatori:

@Converter
@Name("itemConverter") 

@BypassInterceptors 
@Converter
public class ItemConverter implements Converter {
   
   @Transactional
   public Object getAsObject(FacesContext context, UIComponent cmp, String value) {
      EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
      entityManager.joinTransaction();
      // Do the conversion
   }
  
   public String getAsString(FacesContext context, UIComponent cmp, Object value) {
      // Do the conversion
   }
  
}

<h:inputText value="#{shop.item}" converter="itemConverter" />

Registra il componente Seam come converter JSF. Qua è mostrato un converter capace di accedere all'EntityManager JPA dentro ad una transazione JTA, quando converte il valore legato alla rappresentazione del suo oggetto.

@Validator
@Name("itemValidator") 

@BypassInterceptors 
@org.jboss.seam.annotations.faces.Validator
public class ItemValidator implements javax.faces.validator.Validator {
      
   public void validate(FacesContext context, UIComponent cmp, Object value)
         throws ValidatorException {
      ItemController ItemController = (ItemController) Component.getInstance("itemController");
      boolean valid = itemController.validate(value);
      if (!valid) {
         throw ValidatorException("Invalid value " + value);
      }
   }
}

<h:inputText value="#{shop.item}" validator="itemValidator" />

Registra il componente Seam come validatore JSF. Qua è mostrato un validatore che inietta un altro componente Seam; il componente iniettato è usato per validare il valore.

Seam utilizza JBoss EL, il quale fornisce un'estensione allo standard Unified Expression Language (EL). JBoss EL apporta un numero di miglioramenti che incrementano l'espressività e la potenza delle espressioni EL.

Lo standard EL non consente di utilizzare un metodo con parametri definiti dall'utente — sicuramente metodi JSF listener (es. valueChangeListener) prende i parametri forniti da JSF.

JBoss EL rimuove questa restrizione. Per esempio:


<h:commandButton action="#{hotelBooking.bookHotel(hotel)}" value="Book Hotel"/>
@Name("hotelBooking")

public class HotelBooking {
   
   public String bookHotel(Hotel hotel) {
      // Book the hotel
   }
}

Come nelle chiamate ai metodi in Java, i parametri sono racchiusi tra parentesi e separati da virgole:


<h:commandButton action="#{hotelBooking.bookHotel(hotel, user)}" value="Book Hotel"/>

I parametri hotel e user verranno valutati come espressioni di valore e passati al metodo bookHotel() del componente.

Qualsiasi valore d'espressione può essere usato come parametro:


<h:commandButton 
   action="#{hotelBooking.bookHotel(hotel.id, user.username)}" 
   value="Book Hotel"/>

E' importante capire bene come funziona quest'estensione a EL. Quando la pagine viene generata, i nomi dei parametri vengono memorizzati (per esempio hotel.id e user.username) e valutati (come espressioni di valore) quando la pagina viene inviata. Non si possono passare oggetti come parametri!

Devi assicurarti che i parametri siano disponibili non solo quando la pagina viene generata, ma anche quando ne viene fatto il submit. Se gli argomenti non possono essere risolti quando la pagina viene inviata, il metodo d'azione verrà chiamato con argomenti null!

Si può passare stringe letterali usando virgolette singole:


<h:commandLink action="#{printer.println('Hello world!')}" value="Hello"/>

EL unificato supporta anche le espressioni di valore, usate per associare un campo ad un bean. Le espressioni di valore utilizzano le convenzioni dei nomi di JavaBean e richiedono get e set. Spesso JSP si attende un'espressione di valore dove solo un recupero (get) è richiesto (es. l'attributo rendered). Molti oggetti, comunque, non hanno nominato in modo appropriato i metodi accessor alle proprietà o i parametri richiesti.

JBoss EL rimuove questa restrizione permettendo che i valori vengano recuperati usando la sintassi del metodo. Per esempio:


<h:outputText value="#{person.name}" rendered="#{person.name.length() 
> 5}" />

Si può accedere alla dimensione di una collezione in maniera analoga:

<h:outputText value="#{person.name}" rendered="#{person.name.length() 
> 5}" />

In generale qualsiasi espressione nella forma #{obj.property} è identica all'espressione #{obj.getProperty()}.

Sono consentiti anche i parametri. Il seguente esempio chiama productsByColorMethod con un argomento stringa letterale:

#{controller.productsByColor('blue')}

Nell'uso di JBoss EL dovresti tenere presente i seguenti punti:

JBoss EL supporta una limitata sintassi di proiezione. Un'espressione di proiezione mappa una sotto-espressione attraverso un'espressione a valori multipli (lista, set, ecc...). Per esempio, l'espressione:

#{company.departments}

potrebbe restituire una lista di dipartimenti. Se occorresse una lista di nomi di dipartimento, l'unica opzione è quella di iterare sulla lista per recuperare i valori. JBoss EL permette questo con l'espressione di proiezione:

#{company.departments.{d|d.name}}

La sotto-espressione è racchiusa da parentesi. In quest'esempio l'espressione d.name viene valutata per ogni dipartimento, usando d come alias per l'oggetto dipartimento. Il risultato di quest'espressione sarà una lista di valori Stringa.

Qualsiasi espressione valida può essere usata in un'espressione, e quindi sarebbe perfettamente valido scrivere la seguente, assumendo che venga usata per le lunghezze di tutti i nomi di dipartimento in un'azienda:

#{company.departments.{d|d.size()}}

Le proiezioni possono essere annidate. La seguente espressione ritorna gli ultimi nomi di ciascun impiegato in ogni dipartimento:

#{company.departments.{d|d.employees.{emp|emp.lastName}}}

Le proiezioni annidate possono comunque rivelarsi un pò difficoltose. La seguente espressione sembra ritornare una lista di tutti gli impiegati in tutti i dipartimenti:

#{company.departments.{d|d.employees}}

Comunque, restituisce una lista contenente una lista di impiegati per ogni singolo dipartimento. Per combinare questi valori è necessario usare un'espressione leggermente più lunga:

#{company.departments.{d|d.employees.{e|e}}}

E' importante notare che questa sintassi non può essere analizzata da Facelets o JSP e quindi non può essere usata in file xhtml o jsp. Anticipiamo che la sintassi di proiezione cambierà nelle future versioni di JBoss EL.

Questo capitolo è un tentativo di documentare in un unico posto tutti i suggerimenti per ottenere migliori performance da un'applicazione Seam.

Per ripetitivi binding di valore come quelli in dataTable JSF o in altri controlli iterativi (come ui:repeat), l'intero stack di interceptor verrà chiamato ad ogni invocazione di un componente Seam referenziato. L'effetto di questo può essere una sostanziale degradazione di performance, specialmente se il componente viene chiamato molte volte. Un guadagno significativo di performance può essere ottenuto disabilitando lo stack degli interceptor per il componente Seam da invocare. Per disabilitare gli interceptor di un componente occorre aggiungere l'annotazione @BypassInterceptors alla classe del componente.

Il seguente listato di codice mostra come un componente Seam venga disabilitato con i suoi interceptor:

@Name("foo")
@Scope(EVENT)
@BypassInterceptors
public class Foo
{
   public String getRowActions()
   {
     // Role-based security check performed inline instead of using @Restrict or other security annotation
     Identity.instance().checkRole("user");
     
     // Inline code to lookup component instead of using @In
     Bar bar = (Bar) Component.getInstance("bar");
   
     String actions;   
     // some code here that does something     
     return actions;
   }
}

La maggior parte delle applicazioni Seam ha bisogno di almeno due tipi di test automatici: test di unità per testare un particolare componente Seam in isolamento, e test d'integrazione per provare tutti i layer java dell'applicazione (cioè tutto, tranne le pagine di vista).

Entrambi i tipi di test sono facili da scrivere.

Tutti i componenti Seam sono POJO. Questo è un buon punto per partire se si vogliono eseguire dei test di unità facili. E poiché Seam enfatizza l'uso della bujection per le interazioni tra componenti e l'accesso ad oggetti contestuali, è molto facile testare un componente Seam fuori dal suo normale ambiente di runtime.

Si consideri il seguente componente Seam che crea una dichiarazione di account per un cliente:

@Stateless

@Scope(EVENT)
@Name("statementOfAccount")
public class StatementOfAccount {
   
   @In(create=true) EntityManager entityManager
   
   private double statementTotal;
   
   @In
   private Customer customer;
   
   @Create
   public void create() {
      List<Invoice
> invoices = entityManager
         .createQuery("select invoice from Invoice invoice where invoice.customer = :customer")
         .setParameter("customer", customer)
         .getResultList();
      statementTotal = calculateTotal(invoices);
   }
   
   public double calculateTotal(List<Invoice
> invoices) {
      double total = 0.0;
      for (Invoice invoice: invoices)
      {
         double += invoice.getTotal();
      }
      return total;
   }
   
   // getter and setter for statementTotal
   
}

Si può scrivere un test d'unità per il metodo calculateTotal (che testa la business logic del componente) come segue:

public class StatementOfAccountTest {

    
    @Test
    public testCalculateTotal {
       List<Invoice
> invoices = generateTestInvoices(); // A test data generator
       double statementTotal = new StatementOfAccount().calculateTotal(invoices);
       assert statementTotal = 123.45;
    }   
}

Si vede che non si sta testando il recupero dei dati e la persistenza dei dati da/a database; e neppure si testa alcuna funzionalità fornita da Seam. Si sta solamente testanto la logica del POJO. I componenti Seam solitamente non dipendono direttamente dall'infrastruttura del container, e quindi la maggior parte dei test d'unità sono facili come quello mostrato!

Comunque se si vuole testare l'intera applicazione, si consiglia di continuare nella lettura.

Il test d'integrazione è leggermente più difficile. In questo caso non si può eliminare l'infrastruttura del container che invece fa parte di ciò che va testato! Allo stesso tempo non si vuole essere forzati ad eseguire un deploy dell'applicazione in un application server per fare girare i test. Occorre essere in grado di riprodurre l'infrastruttura essenziale del container dentro l'ambiente di test per poter provare l'intera applicazione senza nuocere troppo alle performance.

L'approccio preso da Seam è quello di consentire di scrivere test che possano provare i componenti mentre girano dentro un ambiente di container ridotto (Seam assieme al container JBoss Embedded; vedere Sezione 30.6.1, «Installare JBoss Embedded» per i dettagli di configurazione)

public class RegisterTest extends SeamTest

{
   
   @Test
   public void testRegisterComponent() throws Exception
   {
            
      new ComponentTest() {
         protected void testComponents() throws Exception
         {
            setValue("#{user.username}", "1ovthafew");
            setValue("#{user.name}", "Gavin King");
            setValue("#{user.password}", "secret");
            assert invokeMethod("#{register.register}").equals("success");
            assert getValue("#{user.username}").equals("1ovthafew");
            assert getValue("#{user.name}").equals("Gavin King");
            assert getValue("#{user.password}").equals("secret");
         }
         
      }.run();
      
   }
   ...
   
}

Un problema ancora più difficile è quello di emulare le interazioni utente. Un terzo problema si verifica quando vengono messe le asserzioni. Alcuni framewrok di test consentono di testare un'intera applicazione riproducendo le interazioni utente con il browser. Questi framework hanno un loro spazio d'uso, ma non sono adatti per l'uso in fase di sviluppo.

"SeamTest consente di scrivere test sotto forma di script (scripted), in un ambiente JSF simulato. Il ruolo di un test scripted è quello di riprodurre l'interazione tra la vista ed i componenti Seam. In altre parole si pretende di essere l'implementazione JSF!

Questo approccio testa ogni cosa tranne la vista.

Si consideri una vista JSP per il componente testato come unità visto sopra:


<html>
 <head>
  <title
>Register New User</title>
 </head>
 <body>
  <f:view>
   <h:form>
     <table border="0">
       <tr>
         <td
>Username</td>
         <td
><h:inputText value="#{user.username}"/></td>
       </tr>
       <tr>
         <td
>Real Name</td>
         <td
><h:inputText value="#{user.name}"/></td>
       </tr>
       <tr>
         <td
>Password</td>
         <td
><h:inputSecret value="#{user.password}"/></td>
       </tr>
     </table>
     <h:messages/>
     <h:commandButton type="submit" value="Register" action="#{register.register}"/>
   </h:form>
  </f:view>
 </body>
</html
>

Si vuole testare la funzionalità di registrazione dell'applicazione (la cosa che succede quando l'utente clicca il pulsante Registra). Si riprodurrà ilciclo di vita della richiesta JSF in un test TestNG automatizzato:

public class RegisterTest extends SeamTest

{
   
   @Test
   public void testRegister() throws Exception
   {
            
      new FacesRequest() {
         @Override
         protected void processValidations() throws Exception
         {
            validateValue("#{user.username}", "1ovthafew");
            validateValue("#{user.name}", "Gavin King");
            validateValue("#{user.password}", "secret");
            assert !isValidationFailure();
         }
         
         @Override
         protected void updateModelValues() throws Exception
         {
            setValue("#{user.username}", "1ovthafew");
            setValue("#{user.name}", "Gavin King");
            setValue("#{user.password}", "secret");
         }
         @Override
         protected void invokeApplication()
         {
            assert invokeMethod("#{register.register}").equals("success");
         }
         @Override
         protected void renderResponse()
         {
            assert getValue("#{user.username}").equals("1ovthafew");
            assert getValue("#{user.name}").equals("Gavin King");
            assert getValue("#{user.password}").equals("secret");
         }
         
      }.run();
      
   }
   ...
   
}

Si noti che si è esteso SeamTest, che fornisce un ambiente Seam ai componenti, e si è scritto uno script di test come classe anonima che estende SeamTest.FacesRequest, la quale fornisce un ciclo di vita emulato della richiesta JSF. (C'è anche SeamTest.NonFacesRequest per testare le richieste GET). Si è scritto codice in metodi che vengono chiamati per le varie fasi JSF, per emulare le chiamate che JSF farebbe ai componenti. Infine si sono scritte le asserzioni.

Nelle applicazioni d'esempio di Seam ci sono molti test d'integrazione che mostrano casi ancora più complicati. Ci sono istruzioni per eseguire questi test usando Ant o usando il plugin TestNG per Eclipse:

Se è stato usato seam-gen per creare il progetto si è già pronti per scrivere test. Altrimenti occorre configurare l'ambiente di test all'interno del proprio tool di build (es. ant, maven, eclipse).

Prima si guardi alle dipendenze necessarie:


E' molto importante non inserire nel classpath le dipendenze di JBoss AS a compile time da lib/ (es. jboss-system.jar), altrimenti questo causerà il non avvio di JBoss Embedded. Quindi si aggiungano solo le dipendenze necessarie per partire (es.Drools, jBPM).

Occorre includere nel classpath la directory bootstrap/; bootstrap/ contiene la configurazione per JBoss Embedded.

E sicuramente occorre mettere nel classpath il progetto ed i test così come i jar del framework di test. Non si dimentichi di mettere nel classpath anche tutti i file di configurazione corretti per JPA e Seam. Seam chiede a JBoss Embedded di deployare le risorse (jar o directory) che hanno seam.properties nella root. Quindi se non si assembla una struttura di directory che assomiglia ad un archivio deployabile contenente il progetto, occorre mettere seam.properties in ciascuna risorsa.

Di default un progetto generato userà per i test java:/DefaultDS (un datasource predefinito HSQL in JBoss Embedded). Se si vuole usare un altro datasource, si metta foo-ds.xml nella directory bootstrap/deploy.

Se occorre inserire o pulire i dati nel database prima di ogni test, si può usare l'integrazione di Seam con DBUnit. Per fare questo si estenda DBUnitSeamTest piuttosto che SeamTest.

Devi fornire un dataset per DBUnit.


<dataset>
   
   <ARTIST 
      id="1"
      dtype="Band"
      name="Pink Floyd" />
      
   <DISC
      id="1"
      name="Dark Side of the Moon"
      artist_id="1" />
      
</dataset
>

e comunicarlo a Seam facendo l'override di prepareDBUnitOperations():"

protected void prepareDBUnitOperations() {

    beforeTestOperations.add(
       new DataSetOperation("my/datasets/BaseData.xml")
    );
 }

DataSetOperation è impostato di default a DatabaseOperation.CLEAN_INSERT se non viene specificata qualchealtra operazione come argomento del costruttore. L'esempio di cui sopra pulisce tutte le tabelle definite in BaseData.xml e quindi inserisce tutte le richedichiarate in BaseData.xml prima che ogni metodo @Test venga invocato.

Se viene richiesta un'ulteriore pulizia prima dell'esecuzione di un metodo di test, si aggiungano operazioni alla lista afterTestOperations.

Occorre informare DBUnit del datasource usato impostando il parametro TestNG chiamato datasourceJndiName:


<parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>
         

DBUnitSeamTest supporta MySQL e HSQL- occorre dire quale database viene usato:

<parameter name="database" value="HSQL" />

Questo consente anche di inserire dati binari nel set dei dati di test (N.B. questo non è stato testato sotto Windows). Occorre dire dove si trovano queste risorse:

<parameter name="binaryDir" value="images/" />

Si devono specificare questi tre parametri in testng.xml.

Per usare DBUnitSeamTest con altri database occorre implementare alcuni metodi. Si legga javadoc di AbstractDBUnitSeamTest per saperne di più.

E' facilissimo eseguire il test d'integrazione con Seam Mail:

public class MailTest extends SeamTest {

    
   @Test
   public void testSimpleMessage() throws Exception {
        
      new FacesRequest() {
         @Override
         protected void updateModelValues() throws Exception {
            setValue("#{person.firstname}", "Pete");
            setValue("#{person.lastname}", "Muir");
            setValue("#{person.address}", "test@example.com");
         }
            
         @Override
         protected void invokeApplication() throws Exception {
            MimeMessage renderedMessage = getRenderedMailMessage("/simple.xhtml");
            assert renderedMessage.getAllRecipients().length == 1;
            InternetAddress to = (InternetAddress) renderedMessage.getAllRecipients()[0];
            assert to.getAddress().equals("test@example.com");
         }
            
      }.run();       
   }
}

Viene creata una nuova FacesRequest comenormale. Dentro la sezione invokeApplication mostriamo il messaggio usando getRenderedMailMessage(viewId);, passando il viewId del messaggio da generare. Il metodo restituisce il messaggio sul quale è possibile fare i test. Si può anche usare ogni altro metodo standard del ciclo di vita JSF.

Non c'è alcun supporto per il rendering dei componenti JSF standard, così non è possibile testare facilmente il corpo dei messaggi email.

Weblogic 10.3 is BEA's latest stable JEE5 server offering. Seam applications can be deployed and developed on Weblogic servers, and this chapter will show you how. There are some known issues with the Weblogic servers that will need to be worked around, and configuration changes that are needed specific to Weblogic.

First step is to get Weblogic downloaded, installed and running. Then we'll talk about Seam's JEE5 example and the hurdles to getting it running. After that, the JPA example will be deployed to the server. Then finally we will create a seam-gen application and get it up and running to provide a jump start to your own application.

First things first we need to get the server installed. There are some outstanding issues that were not addressed in 10.3, but it does solve some of the issues discussed below without the need for BEA patches. The previous release 10.0.MP1 is also available, but requires some BEA patches to function correctly.

Special jboss-seam.jar for Weblogic EJB Support

Starting with Seam 2.0.2.CR2 a special Weblogic specific jar has been created that does not contain the TimerServiceDispatcher . This is the EJB that uses varargs and exposes the second EJB issue. We will be using this jar for the jee5/booking example, as it avoids the known BEA issues.

Here are the quick steps to installing Weblogic 10.3. For more details or if you are having any issues please check with the BEA docs at the Weblogic 10.3 Doc Center . Here we install the RHEL 5 version using the graphical installer:

  1. Follow the link given above for 10.3 and download the correct version for your environment. You will need to sign up for an account with Oracle in order to do this.

  2. You may need to change the the server103_XX.bin file to be executable:

    chmod a+x server103_XX.bin
  3. Execute the install:

    ./server103_XX.bin
  4. When the graphical install loads, you need to set the BEA home location. This is where all BEA applications are installed. This location will be known as $BEA_HOME in this document e.g.:

    /jboss/apps/bea
  5. Select Complete as the installation type. You do not need all the extras of the complete install (such as struts and beehive libraries), but it will not hurt.

  6. You can leave the defaults for the component installation locations on the next page.

Now that the server is installed and the domain is created you need to know how to start and stop it, plus how to access its configuration console.

These are the instructions to deploy and configure Weblogic's JSF 1.2 libraries. Out of the box Weblogic does not come with its own JSF libraries active. For complete details see Weblogic 10.3 Configuring JSF and JSTL Libraries

  1. In the administration console navigate to the Deployments page using the left hand menu.

  2. Then select the Install button at the top of the deployments table

  3. Using the directory browser navigate to the $BEA_HOME/wlserver_10.3/common/deployable-libraries directory. Then select the jsf-1.2.war archive, and click the Next button.

  4. Make sure that the Install this deployment as a library is selected. Click the Next button on the Install Application Assistant page.

  5. Click the Next button on the Optional Settings page.

  6. Make sure that the Yes, take me to the deployment's configuration screen. is selected. Click the Finish button on the Review your choices and click Finish page.

  7. On the Settings for jsf(1.2,1.2.3.1) page set the Deployment Order to 99 so that it is deployed prior to auto deployed applications. Then click the Save button.

There is another step that is needed for this to work. For some reason, even with the steps above classes in the jsf-api.jar are not found during application deployment. The only way for this to work is to put the javax.jsf_1.2.0.0.jar (the jsf-api.jar) from jsf-1.2.war in the domains shared library. This requires a restart of the server.

Do you want to run Seam using EJB's on Weblogic? If so there are some obstacles that you will have to avoid, or some patches that are needed from BEA. This section describes those obstacles and what changes are needed to the jee5/booking example to get it deployed and functioning.

For several releases of Weblogic there has been an issue with how Weblogic generates stubs and compiles EJB's that use variable arguments in their methods. This is confirmed in the Weblogic 9.X and 10.0.MP1 versions. Unfortunately the 10.3 version only partially addresses the issue as detailed below.

The basic explanation of the issue is that the Weblogic EJB compiler mistakes methods that use varargs as having the transient modifier. When BEA generates its own stub class from those classes during deployment it fails and the deployment does not succeed. Seam uses variable arguments in one of its internal EJB's ( TimerServiceDispatcher). If you see exceptions like below during deployment you are running an unpatched version of 10.0.MP1.

java.io.IOException: Compiler failed executable.exec: 
/jboss/apps/bea/wlserver_10.0/user_projects/domains/seam_examples/servers/AdminServer
/cache/EJBCompilerCache/5yo5dk9ti3yo/org/jboss/seam/async/
TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java:194: modifier transient 
not allowed here
  public transient javax.ejb.Timer scheduleAsynchronousEvent(java.lang.String arg0,
  java.lang.Object[] arg1)
                                   ^
/jboss/apps/bea/wlserver_10.0/user_projects/domains/seam_examples/servers/AdminServer
/cache/EJBCompilerCache/5yo5dk9ti3yo/org/jboss/seam/async/
TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java:275: modifier transient
not allowed here
  public transient javax.ejb.Timer scheduleTimedEvent(java.lang.String arg0, 
  org.jboss.seam.async.TimerSchedule arg1, java.lang.Object[] arg2)

This issue has been fixed in Weblogic 10.3, and BEA has created a patch for Weblogic 10.0.MP1 ( CR327275 ) for this issue that can be requested from their support.

Unfortunately a second issue has been reported and verified by BEA.

This issue was only found once the CR327275 patch had been applied to 10.0.MP1. This new issue has been confirmed by BEA and they created a patch for 10.0.MP1 that addresses this issue. This patch has been referred to as both CR370259 and CR363182. As with the other patch this can be requested through the BEA support.

This issue causes certain EJB methods to be incorrectly left out of Weblogic's generated internal stub classes. This results in the following error messages during deployment.

<<Error
> <EJB
> <BEA-012036
> <Compiling generated EJB classes produced the following Java compiler error message:
<Compilation Error
> TimerServiceDispatcher_qzt5w2_Impl.java: The type TimerServiceDispatcher_qzt5w2_Impl must implement the inherited abstract method TimerServiceDispatcher_qzt5w2_Intf.scheduleTimedEvent(String, Schedule, Object[])
<Compilation Error
> TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java: Type mismatch: cannot convert from Object to Timer
<Compilation Error
> TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java: Type mismatch: cannot convert from Object to Timer
> 
<Error
> <Deployer
> <BEA-149265
> <Failure occurred in the execution of deployment request with ID '1223409267344' for task '0'. Error is: 'weblogic.application.ModuleException: Exception preparing module: EJBModule(jboss-seam.jar)

It appears that when Weblogic 10.3 was released the neglected to include this fix!! This means that Weblogic 10.0.MP1 with patches will function correctly, but 10.3 will still require the special Seam jar described below. Not all users have seen this and there may be certain combinations of OS/JRE that do not see this, however is has been seen many times. Hopefully Oracle/BEA will release an updated patch for this issue on 10.3. When they do we will update this reference guide as needed.

So that Seam's users can deploy an EJB application to Weblogic a special Weblogic specific jar has been created, starting with Seam 2.0.2.CR2. It is located in the $SEAM/lib/interop directory and is called jboss-seam-wls-compatible.jar . The only difference between this jar and the jboss-seam.jar is that it does not contain the TimerServiceDispatcher EJB. To use this jar simply rename the jboss-seam-wls-compatible.jar to jboss-seam.jar and replace the original in your applications EAR file. The jee5/booking example demonstrates this. Obviously with this jar you will not be able to use the TimerServiceDispatcher functionality.

In this section we will go over the steps needed to get the jee5/booking example to up and running.

This example uses the in memory hypersonic database, and the correct data source needs to be set up. The admin console uses a wizard like set of pages to configure it.

  1. Copy hsqldb.jar to the Weblogic domain's shared library directory: cp $SEAM_HOME/lib/hsqldb.jar $BEA_HOME/user_projects/domains/seam_examples/lib

  2. Start up the server and navigate to the administration console following Sezione 38.1.3, «How to Start/Stop/Access your domain»

  3. On the left side tree navigate seam_examples - Services- JDBC - Data Sources.

  4. Then select the New button at the top of the data source table

  5. Fill in the following:

    1. Name: seam-jee5-ds

    2. JNDI Name: seam-jee5-ds

    3. Database Type and Driver: other

    4. Select Next button

  6. Select Next button on the Transaction Options page

  7. Fill in the following on the Connection Properties page:

    1. Database Name: hsqldb

    2. Host Name: 127.0.0.1

    3. Port: 9001

    4. Username: sa saranno campi password vuoti.

    5. Password: lasciare vuoto.

    6. Select Next button

  8. Fill in the following on the Connection Properties page:

    1. Driver Class Name: org.hsqldb.jdbcDriver

    2. URL: jdbc:hsqldb:.

    3. Username: sa

    4. Password: lasciare vuoto.

    5. Leave the rest of the fields as is.

    6. Select Next button

  9. Choose the target domain for the data source in our case the only one AdminServer. Click Next.

OK - now we are ready to finally begin adjusting the seam application for deployment to the Weblogic server.

resources/META-INF/persistence.xml
resources/META-INF/weblogic-application.xml
resources/META-INF/ejb-jar.xml
resources/WEB-INF/weblogic.xml

There are some changes needed to the build script and the jboss-seam.jar then we can deploy the app.

Assuming that you choose the first option for handling the jboss-seam-wls-compatible.jar we can build the application by running ant archive at the base of the jee5/booking example directory.

Because we chose to create our Weblogic domain in development mode we can deploy the application by putting the EAR file in the domains autodeploy directory.

cp ./dist/jboss-seam-jee5.ear
                  $BEA_HOME/user_projects/domains/seam_examples/autodeploy
               

Check out the application at http://localhost:7001/seam-jee5/

This is the Hotel Booking example implemented with Seam POJOs and Hibernate JPA and does not require EJB3 support to run. The example already has a breakout of configurations and build scripts for many of the common containers including Weblogic 10.X

First we'll build the example for Weblogic 10.x and do the needed steps to deploy. Then we'll talk about what is different between the Weblogic versions, and with the JBoss AS version.

Note that this example assumes that Weblogic's JSF libraries have been configured as described in Sezione 38.1.4, «Setting up Weblogic's JSF Support».

seam-gen is a very useful tool for developers to quickly get an application up and running, and provides a foundation to add your own functionality. Out of box seam-gen will produce applications configured to run on JBoss AS. These instructions will show the steps needed to get it to run on Weblogic.

seam-gen was build for simplicity so, as you can imagine, deploying an application generated by seam-gen to Weblogic 10.x is not too hard. Basically it consists of updating or removing some configuration files, and adding dependent jars that Weblogic 10.x does not ship with.

This example will cover the basic seam-gen WAR deployment. This will demonstrate Seam POJO components, Hibernate JPA, Facelets, Drools security, RichFaces, and a configurable dataSource.

The first thing we need to do it tell seam-gen about the project we want to make. This is done by running ./seam setup in the base directory of the Seam distribution. Note the paths here are my own, feel free to change for you environment.

./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]
/home/jbalunas/workspace
    [input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA] 
[C:/Program Files/jboss-4.2.3.GA]
/jboss/apps/jboss-4.2.3.GA
    [input] Enter the project name [myproject] [myproject]
weblogic-example
     [echo] Accepted project name as: weblogic_example
    [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, )
war
    [input] Enter the Java package name for your session beans [org.jboss.seam.
tutorial.weblogic.action] [org.jboss.seam.tutorial.weblogic.action]
org.jboss.seam.tutorial.weblogic.action
    [input] Enter the Java package name for your entity beans [org.jboss.seam.
tutorial.weblogic.model] [org.jboss.seam.tutorial.weblogic.model]
org.jboss.seam.tutorial.weblogic.model
    [input] Enter the Java package name for your test cases [org.jboss.seam.
tutorial.weblogic.action.test] [org.jboss.seam.tutorial.weblogic.action.test]
org.jboss.seam.tutorial.weblogic.test
    [input] What kind of database are you using? [hsql]  ([hsql], mysql, oracle,
 postgres, mssql, db2, sybase, enterprisedb, h2)

    [input] Enter the Hibernate dialect for your database [org.hibernate.
dialect.HSQLDialect] [org.hibernate.dialect.HSQLDialect]

    [input] Enter the filesystem path to the JDBC driver jar [/tmp/seamlib/hsqldb.jar] 
[/tmp/seam/lib/hsqldb.jar]

    [input] Enter JDBC driver class for your database [org.hsqldb.jdbcDriver] 
 [org.hsqldb.jdbcDriver]

    [input] Enter the JDBC URL for your database [jdbc:hsqldb:.] [jdbc:hsqldb:.]

    [input] Enter database username [sa] [sa]

    [input] Enter database password [] []

    [input] Enter the database schema name (it is OK to leave this blank) [] []

    [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], )

    [input] Do you want to drop and recreate the database tables and data in 
import.sql each time you deploy? [n]  (y, [n], )

    [input] Enter your ICEfaces home directory (leave blank to omit ICEfaces) [] []

[propertyfile] Creating new property file: 
/rhdev/projects/jboss-seam/cvs-head/jboss-seam/seam-gen/build.properties
     [echo] Installing JDBC driver jar to JBoss server
     [copy] Copying 1 file to /jboss/apps/jboss-4.2.3.GA/server/default/lib
     [echo] Type 'seam create-project' to create the new project

BUILD SUCCESSFUL

Type ./seam new-project to create your project and cd /home/jbalunas/workspace/weblogic_example to see the newly created project.

First we change and delete some configuration files, then we update the libraries that are deployed with the application.

build.xml
resources/META-INF/persistence-dev.xml
resource/WEB-INF/weblogic.xml

You will need to create this file and populate it following description of WEB-INF/weblogic.xml.

resource/WEB-INF/components.xml

We want to use JPA transactions so we need to add the following to let Seam know.


<transaction:entity-transaction entity-manager="#{entityManager}"/>

You will also need to add the transaction namespace and schema location to the top of the document.


xmlns:transaction="http://jboss.com/products/seam/transaction"

http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.1.xsd
resource/WEB-INF/web.xml

WEB-INF/web.xml — Because the jsf-impl.jar is not in the WAR this listener need to be configured :



 <listener>
   <listener-class
>com.sun.faces.config.ConfigureListener</listener-class>
 </listener
>
resources/WEB-INF/jboss-web.xml

You can delete this file as we aren't deploying to JBoss AS ( jboss-app.xml is used to enable classloading isolation in JBoss AS)

resources/*-ds.xml

You can delete these files as we aren't deploying to JBoss AS. These files define datasources in JBoss AS, in Weblogic we will use the administration console.

The seam-gen application has very similar library dependencies as the jpa example above. See Sezione 38.3.2, «What's different with Weblogic 10.x». Below is the changes that are needed to get them in this application.

  • build.xml — Now we need to adjust the build.xml. Find the target war and add the following to the end of the target.

    
    
          <copy todir="${war.dir}/WEB-INF/lib">
             <fileset dir="${lib.dir}">
                <!-- Misc 3rd party -->
                <include name="commons-logging.jar" />
                <include name="dom4j.jar" />
                <include name="javassist.jar" />
                <include name="cglib.jar" />
                <include name="antlr.jar" />

                <!-- Hibernate -->
                <include name="hibernate.jar" />
                <include name="hibernate-commons-annotations.jar" />
                <include name="hibernate-annotations.jar" />
                <include name="hibernate-entitymanager.jar" />
                <include name="hibernate-validator.jar" />
                <include name="jboss-common-core.jar" />
                <include name="concurrent.jar" />
             </fileset>
          </copy
    >

All that's left is deploying the application. This involves setting up a data source, building the app, and deploying it.

Configuring the datasource is very similar to the jee5 Sezione 38.2.2.1, «Setting up the hsql datasource». Except for what is listed here follow that instruction from the link.

  • DataSource Name: seam-gen-ds

  • JNDI Name: seam-gen-ds

When we installed Weblogic following Sezione 38.1.2, «Creating your Weblogic domain» we chose to have the domain in development mode. This means to deploy the application all we need to do is copy it into the autodeploy directory.


cp  ./dist/weblogic_example.war /jboss/apps/bea/user_projects/domains/seam_examples/autodeploy

Check out the application at the following http://localhost:7001/weblogic_example/. .

Websphere AS V7 is IBM's application server offering. This release is fully Java EE 5 certified.

First we will go over some basic information about the Websphere AS environment that we used for these examples. We will go over the details of those steps with the JEE5 booking example. We will also deploy the JPA example application.

The jee5/booking example is based on the Hotel Booking example (which runs on JBoss AS). Out of the box it is designed to run on Glassfish, but with the steps below it can be deployed to Websphere. It is located in the $SEAM_DIST/examples/jee5/booking directory.

Below are the configuration file changes that are need to the base example.

resources/WEB-INF/components.xml

We need to change the way that we look up EJBs for WAS. We need to remove the /local from the end of the jndi-pattern attribute. It should look like this:



<core:init jndi-pattern="java:comp/env/jboss-seam-jee5/#{ejbName}" debug="true"/>
                  
resources/META-INF/ejb-jar.xml

We need to replace the /local string from ejb-ref-name. See at the following final code:



   <enterprise-beans>
      <!-- EJB reference required when one Seam EJB component references another Seam EJB component using @In -->
      <!-- Not required if you inject using @EJB, but then you lose state management and client-side interceptors (e.g., security) -->
      <session>
         <ejb-name
>RegisterAction</ejb-name>
         <ejb-local-ref>
            <ejb-ref-name
>jboss-seam-jee5/AuthenticatorAction</ejb-ref-name>
            <ejb-ref-type
>Session</ejb-ref-type>
            <local
>org.jboss.seam.example.booking.Authenticator</local>
         </ejb-local-ref>
      </session>
   </enterprise-beans
>
resources/WEB-INF/web.xml

We have to make some changes to the EJB references in the web.xml. These changes are what will allow WAS to bind automatically the EJB3 references in the web module to the the actual EJB3 beans in the EAR module. Replace all of the /local strings in ejb-local-refs when the values below.



  <!-- JEE5 EJB3 names -->
  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/AuthenticatorAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.Authenticator</local>
  </ejb-local-ref
>        

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/BookingListAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.BookingList</local>
  </ejb-local-ref>

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/RegisterAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.Register</local>
  </ejb-local-ref>

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/ChangePasswordAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.ChangePassword</local>
  </ejb-local-ref>

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/HotelBookingAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.HotelBooking</local>
  </ejb-local-ref>

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/HotelSearchingAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.example.booking.HotelSearching</local>
  </ejb-local-ref>

  <ejb-local-ref>
    <ejb-ref-name
>jboss-seam-jee5/EjbSynchronizations</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
  </ejb-local-ref
>

Note also that EjbSynchronizations is a built-in Seam EJB and not part of the Hotel Booking example. This means that if your application's components.xml specifies transaction:ejb-transaction, then you must include:



  <ejb-local-ref>
    <ejb-ref-name
>myapp/EjbSynchronizations</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local-home
></local-home>
    <local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
  </ejb-local-ref
>

in your web.xml. If you don't include it, you'll get the following error:

Name comp/env/myapp/EjbSynchronizations not found in context java:
resources/META-INF/persistence.xml

For this example we will be using the default datasource that comes with WAS. To do this change the jta-data-source element:



<jta-data-source
>DefaultDatasource</jta-data-source>
                  

Then we need to adjust some of the hibernate properties. First comment out the Glassfish properties. Next you need to add/change the properties:



<!--<property name="hibernate.transaction.flush_before_completion" value="true"/>-->
<property name="hibernate.cache.provider_class" 
                  value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.dialect" value="GlassfishDerbyDialect"/>
<property name="hibernate.transaction.manager_lookup_class" 
          value="org.hibernate.transaction.WebSphereExtendedJTATransactionLookup"/>
                  

src/GlassfishDerbyDialect.java

You will need to get the GlassfishDerbyDialect.java and copy it into the /src directory. The java class exists in the JPA example source directory and can be copied using the command below assuming you are in jee5/booking directory:

cp ../../jpa/src/GlassfishDerbyDialect.java
   ./src

This class will be put into the jboss-seam-jee5.jar file.

resources/import.sql

This file must also be copied from the JPA example because either the Derby DB or the dialect does not support changes to the ID column. The files are identical except for the column difference. Use the following command to make the copy

cp ../../jpa/resources-websphere7/import.sql ./resources

So now we have everything we need in place. All that is left is to deploy it - just a few steps more.

For this we will use Websphere's administration console. As before there are some tricks and tips that must be followed.

The steps below are for the WAS version stated above. The ports are default values, if you changed them substitute your values.

  1. Log in to the administration console

    https://localhost:9043/admin

    or

    http://localhost:9060/admin

  2. Access the Websphere enterprise applications menu option under the Applications --> Application Type left side menu.

  3. At the top of the Enterprise Applications table select Install. Below are installation wizard pages and what needs to done on each:

  4. Now that we have our application installed we need to make some adjustments to it before we can start it:

  5. To start the application return to the Enterprise Applications table and select our application in the list. Then choose the Start button at the top of the table.

  6. You can now access the application at http://localhost:9080/seam-jee5-booking/index.html .

Thankfully getting the jpa example to work is much easier than the jee5 example. This is the Hotel Booking example implemented in Seam POJOs and using Hibernate JPA with JPA transactions. It does not use EJB3.

The example already has a breakout of configurations and build scripts for many of the common containers including Websphere.

First thing we are going to do is build and deploy that example. Then we'll go over some key changes that we needed.

This is similar to the jee5 example at Sezione 39.2.3, «Deploying the application to Websphere», but without so many steps.

  • From the Enterprise Applications table select the Install button.

    • Preparing for the application installation

      • Browse to the examples/jpa/dist-websphere7/jboss-seam-jpa.war file using the file upload widget.

      • Select the Fast Path button.

      • Select the Next button.

    • Select the Next button for the next three pages, no changes are needed.

    • Map context roots for Web modules

      • In the Context root text box enter jboss-seam-jpa.

      • Select the Next button.

    • Summary page

      • Review the settings if you wish and select the Finish button to install the application. When installation finished select the Save link and you will be returned to the Enterprise Applications table.

  • As with the jee5 example there are some class loader changes needed before we start the application. Follow the instructions at installation adjustments for jee5 example but exchange jboss-seam-jpa_war for Seam Booking.

  • Finally start the application by selecting it in the Enterprise Applications table and clicking the Start button.

  • You can now access the application at the http://localhost:9080/jboss-seam-jpa/index.html.

The differences between the JPA examples that deploys to JBoss 4.2 and Websphere AS V7 are mostly expected; library and configuration file changes.

seam-gen is a very useful tool for developers to quickly get an application up and running, and provides a foundation to add your own functionality. Out of box seam-gen will produce applications configured to run on JBoss AS. These instructions will show the steps needed to get it to run on Websphere. As stated above in Sezione 39.2, «The jee5/booking example » there are some tricky changes needed to get an EJB3 application running. This section will take you through the exact steps.

The first step is setting up seam-gen to construct the base project. There are several choices made below, specifically the datasource and hibernate values that we will adjust once the project is created.

./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]
/home/jbalunas/workspace
    [input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA] 
[C:/Program Files/jboss-4.2.3.GA]
/home/jbalunas/jboss/jboss-4.2.3.GA
    [input] Enter the project name [myproject] [myproject]
websphere_example
     [echo] Accepted project name as: websphere_example
    [input] Do you want to use ICEFaces instead of RichFaces [n] (y, [n], )

    [input] skipping input as property icefaces.home.new has already been set.
    [input] Select a RichFaces skin [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 [org.jboss.seam.
tutorial.websphere.action] [org.jboss.seam.tutorial.websphere.action]
org.jboss.seam.tutorial.websphere.action 
    [input] Enter the Java package name for your entity beans [org.jboss.seam.
tutorial.websphere.model] [org.jboss.seam.tutorial.websphere.model]
org.jboss.seam.tutorial.websphere.model  
    [input] Enter the Java package name for your test cases [org.jboss.seam.
tutorial.websphere.action.test] [org.jboss.seam.tutorial.websphere.action.test]
org.jboss.seam.tutorial.websphere.test
    [input] What kind of database are you using? [hsql]  ([hsql], mysql, oracle,
 postgres, mssql, db2, sybase, enterprisedb, h2)

    [input] Enter the Hibernate dialect for your database [org.hibernate.
dialect.HSQLDialect] [org.hibernate.dialect.HSQLDialect]

    [input] Enter the filesystem path to the JDBC driver jar [/tmp/seam/lib/hsqldb.jar] 
[/tmp/seam/lib/hsqldb.jar]

    [input] Enter JDBC driver class for your database [org.hsqldb.jdbcDriver] 
[org.hsqldb.jdbcDriver]

    [input] Enter the JDBC URL for your database [jdbc:hsqldb:.] 
[jdbc:hsqldb:.]

    [input] Enter database username [sa] [sa]

    [input] Enter database password [] []

    [input] Enter the database schema name (it is OK to leave this blank) [] []

    [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], )

    [input] Do you want to drop and recreate the database tables and data in 
import.sql each time you deploy? [n]  (y, [n], )

[propertyfile] Creating new property file: 
/rhdev/projects/jboss-seam/svn-seam_2_0/jboss-seam-2_0/seam-gen/build.properties
     [echo] Installing JDBC driver jar to JBoss server
     [copy] Copying 1 file to /home/jbalunas/jboss/jboss-4.2.3.GA/server/default/lib
     [echo] Type 'seam create-project' to create the new project

BUILD SUCCESSFUL
Total time: 3 minutes 5 seconds

Type ./seam new-project to create your project and cd /home/jbalunas/workspace/websphere_example to the newly created structure.

We now need to make some changes to the generated project.

resources/META-INF/persistence-dev.xml
src/GlassfishDerbyDialect.java

As with other examples we need to include this java class for DB support. It can be copied from the jpa example into the websphere_example/src directory.

cp $SEAM/examples/jpa/src/GlassfishDerbyDialect.java
   ./src

resources/META-INF/jboss-app.xml

You can delete this file as we aren't deploying to JBoss AS ( jboss-app.xml is used to enable classloading isolation in JBoss AS)

resources/*-ds.xml

You can delete these file as we aren't deploying to JBoss AS (these files define datasources in JBoss AS, we are using Websphere's default datasource)

resources/WEB-INF/components.xml
  • Enable container managed transaction integration - add the <transaction:ejb-transaction /> component, and it's namespace declaration xmlns:transaction="http://jboss.com/products/seam/transaction"

  • Alter the jndi-pattern to java:comp/env/websphere_example/#{ejbName}

  • We do not need managed-persistence-context for this example and so can delete its entry.

    
    
    <persistence:managed-persistence-context name="entityManager"
                 auto-create="true"
                 persistence-unit-jndi-name="java:/websphere_exampleEntityManagerFactory"/> 
resources/WEB-INF/web.xml

As with the jee5/booking example we need to add EJB references to the web.xml. These references require replacing /local string in ejb-ref-name to flag them for Websphere to perform the proper binding.


  
  <ejb-local-ref>
    <ejb-ref-name
>websphere_example/AuthenticatorAction</ejb-ref-name>
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.tutorial.websphere.action.Authenticator</local>
  </ejb-local-ref
>        
   
  <ejb-local-ref>
    <ejb-ref-name
>websphere_example/EjbSynchronizations</ejb-ref-name
>  
    <ejb-ref-type
>Session</ejb-ref-type>
    <local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
  </ejb-local-ref
>

This application has similar requirements as the jee5/booking example.

GlassFish is an open source application server which fully implements Java EE 5. The latest stable release is v2 UR2.

First, we'll discuss the GlassFish environment. Then we will go over the how you deploy the jee5 example. Next, we will deploy the JPA example application. Finally we show how to get a seam-gen's generated application running on GlassFish.

All of the examples and information in this chapter are based on the the latest version of GlassFish at the time of this writing.

After downloading GlassFish, install it:

$ java -Xmx256m -jar glassfish-installer-v2ur2-b04-linux.jar

After installing, setup GlassFish:

$ cd glassfish; ant -f setup.xml

The created domain's name is domain1.

Next, we start the embedded JavaDB server:

$ bin/asadmin start-database

Nota

JavaDB is an embedded database that is included with GlassFish, just as HSQLDB is included in JBoss AS.

Now, start the GlassFish server:

$ bin/asadmin start-domain domain1

The web administration console is available at http://localhost:4848/. You can access the web admin console with the default username (admin) and password (adminadmin). We will be using the the admin console to deploy our examples. You can also copy EAR/WAR files to the glassfish/domains/domain1/autodeploy directory to deploy them, although we are not going to cover that.

You can stop the server and database using:

$ bin/asadmin stop-domain domain1; bin/asadmin stop-database

This is the Hotel Booking example implemented in Seam POJOs and using Hibernate JPA with JPA transactions. It does not require EJB3 support to run on application server.

The example already has a break-out of configurations and build scripts for many of the common containers including GlassFish.

This is very similar to the jee5 example at Sezione 40.2.2, «Deploying the application to GlassFish» except that this is a war and not an ear.

  • Log in to the administration console:

    http://localhost:4848
  • Access the Web Applications in the menu option under the Applications left side menu.

    • Preparing for the application installation

      • Browse to examples/jpa/dist-glassfish/jboss-seam-jpa.war.

      • Selezionare il pulsante OK.

    • You can now access the application at http://localhost:8081/jboss-seam-jpa/.

Using Derby instead of Hypersonic SQL DB

In order for the app to work out of the box with GlassFish, we have used the Derby (aka JavaDB) database in GlassFish. However, we strongly recommend that you use another database (e.g. HSQL). examples/jpa/resources-glassfish/WEB-INF/classes/GlassfishDerbyDialect.class is a hack to get around a Derby bug in GlassFish server. You must use it as your Hibernate dialect if you use Derby with GlassFish.

seam-gen is a very useful tool for developers to quickly get an application up and running, and provides a foundation to add your own functionality. Out of box seam-gen will produce applications configured to run on JBoss AS. These instructions will show the steps needed to get it to run on GlassFish.

The first step is setting up seam-gen to construct the base project. There are several choices made below, specifically the datasource and hibernate values that we will adjust once the project is created.

$ ./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]
/projects
[input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.3.GA]
[C:/Program Files/jboss-4.2.3.GA]

[input] Enter the project name [myproject] [myproject]
seamgen_example
[echo] Accepted project name as: seamgen_example
[input] Do you want to use ICEfaces instead of RichFaces [n] (y, [n])

[input] skipping input as property icefaces.home.new has already
been set.
[input] Select a RichFaces skin [blueSky] ([blueSky], classic, ruby, wine,
deepMarine, emeraldTown, japanCherry, 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.seamgen_example] [com.mydomain.seamgen_example]
org.jboss.seam.tutorial.glassfish.action
[input] Enter the Java package name for your entity beans
[org.jboss.seam.tutorial.glassfish.action]
[org.jboss.seam.tutorial.glassfish.action]
org.jboss.seam.tutorial.glassfish.model
[input] Enter the Java package name for your test cases
[org.jboss.seam.tutorial.glassfish.action.test]
[org.jboss.seam.tutorial.glassfish.action.test]
org.jboss.seam.tutorial.glassfish.test
[input] What kind of database are you using? [hsql] ([hsql], mysql, oracle,
postgres, mssql, db2, sybase, enterprisedb, h2)

[input] Enter the Hibernate dialect for your database
[org.hibernate.dialect.HSQLDialect]
[org.hibernate.dialect.HSQLDialect]

[input] Enter the filesystem path to the JDBC driver jar
[/tmp/seam/lib/hsqldb.jar] [/tmp/seam/lib/hsqldb.jar]

[input] Enter JDBC driver class for your database [org.hsqldb.jdbcDriver]
[org.hsqldb.jdbcDriver]

[input] Enter the JDBC URL for your database [jdbc:hsqldb:.]
[jdbc:hsqldb:.]

[input] Enter database username [sa] [sa]

[input] Enter database password [] []

[input] Enter the database schema name (it is OK to leave this blank) [] []

[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])

[input] Do you want to drop and recreate the database tables and data in
import.sql each time you deploy? [n] (y, [n])

[propertyfile] Creating new property file:
/home/mnovotny/workspaces/jboss/jboss-seam/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss server
[copy] Copying 1 file to
/home/mnovotny/workspaces/jboss/jboss-seam/seam-gen/C:/Program
Files/jboss-4.2.3.GA/server/default/lib
[echo] Type 'seam create-project' to create the new project

BUILD SUCCESSFUL
Total time: 4 minutes 5 seconds

Type $ ./seam new-project to create your project and then cd /projects/seamgen_example to the newly created structure.

We now need to make some changes to the generated project.

resources/META-INF/persistence-dev.xml
resources/GlassfishDerbyDialect.class

As with other examples we need to include this class for DB support. It can be copied from the jpa example into the seamgen_example/resources directory.

$ cp \ 
$SEAM_DIST/examples/jpa/resources-glassfish/WEB-INF/classes/GlassfishDerbyDialect.class \ 
./resources
                     
resources/META-INF/jboss-app.xml

You can delete this file as we aren't deploying to JBoss AS (jboss-app.xml is used to enable classloading isolation in JBoss AS)

resources/*-ds.xml

You can delete these file as we aren't deploying to JBoss AS (these files define data sources in JBoss AS, we are using GlassFish's default data source)

resources/WEB-INF/components.xml
  • Enable container managed transaction integration - add the <transaction:ejb-transaction/> component, and it's namespace declaration xmlns:transaction="http://jboss.com/products/seam/transaction"

  • Alter the jndi-pattern to java:comp/env/seamgen_example/#{ejbName}

resources/WEB-INF/web.xml

As with the jee5/booking example, we need to add EJB references to web.xml. Technically, the reference type is not required, but we add it here for good measure. Note that these references require the presence of an empty local-home element to retain compatibility with a JBoss AS 4.x deployment.


<ejb-local-ref
>              
    <ejb-ref-name
>seamgen_example/AuthenticatorAction</ejb-ref-name
>                
    <ejb-ref-type
>Session</ejb-ref-type
>     
    <local-home/>
    <local
>org.jboss.seam.tutorial.glassfish.action.Authenticator</local
>  
  </ejb-local-ref>
   
  <ejb-local-ref>
    <ejb-ref-name
>seamgen_example/EjbSynchronizations</ejb-ref-name
>  
    <ejb-ref-type
>Session</ejb-ref-type>
    <local-home/>
    <local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
  </ejb-local-ref
>

Keep in mind that if you are deploying to JBoss AS 4.x, and have defined the EJB references shown above in your web.xml, you will need to also define local JNDI names for each of them in jboss-web.xml, as shown below. This step is not required when deploying to GlassFish, but it's mentioned here in case you are also deploying the application to JBoss AS 4.x (not required for JBoss AS 5).


<ejb-local-ref
>              
    <ejb-ref-name
>seamgen_example/AuthenticatorAction</ejb-ref-name
>                
    <local-jndi-name
>AuthenticatorAction</local-jndi-name
>  
  </ejb-local-ref>
   
  <ejb-local-ref>
    <ejb-ref-name
>seamgen_example/EjbSynchronizations</ejb-ref-name
>  
    <local-jndi-name
>EjbSynchronizations</local-jndi-name>
  </ejb-local-ref
>

This application has similar requirements as the jee5/booking example.

Questa sezione elenca le dipendenze di Seam sia a compile-time sia a runtime. Laddove il tipo viene elencato come ear, la libreria deve essere inclusa nella directory /lib del proprio ear dell'applicazione. Laddove il tipo viene elencato come war, la libreria deve essere collocata nella directory /WEB-INF/lib del proprio file war. Lo scope della dipendenze è tutto, runtime o provided (da JBoss AS 4.2 o 5.0).

Le informazioni sulla versione e sulle dipendenze non sono incluse nella documentazione, ma sono fornite in /dependency-report.txt che viene generato dai POM di Maven memorizzati in /build. E' possibile generare questo file eseguendo ant dependencyReport.

Maven offre un supporto per la gestione transitiva delle dipendenze e può essere usato per gestire le dipendenze nei progetti Seam. Maven Ant Tasks intergra Maven nel build di Ant, e Maven può essereimpiegato per fare ilbuild ed il deploy dei propri progetti.

Qui non si discute l'uso di Maven, ma soltanto un utilizzo base del POM.

Le versioni rilasciate di Seam sono disponibili all'indirizzo http://repository.jboss.org/maven2 e gli snapshot notturni sono disponibili all'indirizzo http://snapshots.jboss.org/maven2.

Tutti gli artifact di Seam sono disponibili in Maven:


<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam</artifactId>
</dependency
>

<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam-ui</artifactId>
</dependency
>

<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam-pdf</artifactId>
</dependency
>

<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam-remoting</artifactId>
</dependency
>

<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam-ioc</artifactId>
</dependency
>

<dependency>
  <groupId
>org.jboss.seam</groupId>
  <artifactId
>jboss-seam-ioc</artifactId>
</dependency
>

Questo POM d'esempio fornirà Seam, JPA (tramite Hibernate) e Hibernate Validator:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion
>4.0.0</modelVersion>
  <groupId
>org.jboss.seam.example/groupId>
  <artifactId
>my-project</artifactId>
  <version
>1.0</version>
  <name
>My Seam Project</name>
  <packaging
>jar</packaging>
  <repositories>
    <repository>
      <id
>repository.jboss.org</id>
      <name
>JBoss Repository</name>
      <url
>http://repository.jboss.org/maven2</url>
    </repository>
  </repositories>

  <dependencies>

    <dependency>
      <groupId
>org.hibernate</groupId>
      <artifactId
>hibernate-validator</artifactId>
      <version
>3.0.0.GA</version>
    </dependency>

    <dependency>
      <groupId
>org.hibernate</groupId>
      <artifactId
>hibernate-annotations</artifactId>
      <version
>3.3.0.ga</version>
    </dependency>

    <dependency>
      <groupId
>org.hibernate</groupId>
      <artifactId
>hibernate-entitymanager</artifactId>
      <version
>3.3.1.ga</version>
    </dependency>

    <dependency>
      <groupId
>org.jboss.seam</groupId>
      <artifactId
>jboss-seam</artifactId>
      <version
>2.0.0.GA</version>
    </dependency>
    
  </dependencies>

</project
>