<s:link> e <s:button>Seam è un'application framework per Java Enterprise. Si ispira ai seguenti principi:
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
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.
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.
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.
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.
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.
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!
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 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.
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.
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.
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:
La directory view contiene i file relativi alla vista come template di pagine web, immagini e fogli di stile.
La directory resources contiene i descrittori per il deploy ed altri file di configurazione.
La directory src contiene il codice sorgente dell'applicazione.
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.
Gli esempi sono configurati per usare JBoss 4.2. Dovrai impostare la variabile jboss.home affinché punti alla locazione dell'installazione di JBoss AS. Questa variabile si trova nel file condiviso build.properties nella cartella radice dell'installazione di Seam.
Una volta impostata la locazione di JBoss AS ed avviato il server, si può eseguire il build ed il deploy degli esempi semplicemente scrivendo ant explode nella directory dell'esempio. Ogni esempio che viene impacchettato come EAR viene messo in un URL del tipo /seam-, dove exampleexample è il nome della cartella dell'esempio, con una eccezione. Se la cartella d'esempio inizia con seam, il prefisso "seam" viene omesso. Per esempio, se JBoss AS gira sulla porta 8080, l'URL per l'esempio registrazione è http://localhost:8080/seam-registration/, mentre l'URL per l'esempio seamspace è http://localhost:8080/seam-space/.
Se, dall'altro lato, l'esempio viene impacchettato come WAR, allora viene messo in un URL del tipo /jboss-seam-. La maggior parte degli esempi può essere messa come WAR in Tomcat con JBoss Embedded digitando exampleant tomcat.deploy. Diversi esempi possono venire deployati solo come WAR. Questi esempi sono groovybooking, hibernate, jpa, and spring.
Questi esempi sono configurati anche per essere usati in Tomcat 6.0. Occorreràseguire le istruzioni in Sezione 29.6.1, «Installare JBoss Embedded» per installare JBoss Embedded in Tomcat 6.0. JBoss Embedded è richiesto per eseguire le demo di Seam che usano componenti EJB3 in Tomcat. Ci sono anche esempio di applicazioni non-EJB3 che possono funzionare in Tomcat senza JBoss Embedded.
Occorrerà impostare al percorso di Tomcat la variabile tomcat.home, la quale si trova nel file condiviso build.properties della cartella padre nell'installazione di Seam.
Dovrai usare un diverso target Ant per utilizzare Tomcat. Usa ant tomcat.deploy nella sotto-directory d'esempio per il build ed il deploy in Tomcat.
Con Tomcat gli esempi vengono deployati con URL del tipo /jboss-seam-, così per l'esempio di registrazione, l'URL sarebbe examplehttp://localhost:8080/jboss-seam-registration/. Lo stesso vale per gli esempi che vengono deployati come WAR, come già detto nella precedente sezione.
La maggior parte degli esempi è fornita di una suite di test d'integrazione TestNG. Il modo più semplice per eseguire i test è ant test. E' anche possibile eseguire i test all'interno del proprio IDE usandi il plugin di TestNG. Per ulteriori informazioni consultare il file readme.txt nella directory degli esempi nella distribuzione di Seam.
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
@Entity
@Name("user")
@Scope(SESSION)
@Table(name="users")
public class User implements Serializable
{
private static final long serialVersionUID = 1881413500711441951L;
private String username;
private String password;
private String name;
public User(String name, String password, String username)
{
this.name = name;
this.password = password;
this.username = username;
}
public User() {}
@NotNull @Length(min=5, max=15)
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
@NotNull
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Id @NotNull @Length(min=5, max=15)
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
![]() | L'annotazione EJB3 standard |
![]() | Un componente Seam ha bisogno di un nome componente specificato dall'annotazione |
![]() | 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 |
![]() | L'annotazione standard EJB |
![]() |
|
![]() | Un costruttore vuoto è richiesto sia dalla specifica EJB sia da Seam. |
![]() | Le annotazioni |
![]() | L'annotazione standard EJB |
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@Name("register") public class RegisterAction implements Register { @In private Use
r user; @PersistenceContext private Ent
ityManager em; @Logger private Log
log; public String register() {
List existing = em.createQuery( "select username from User where username = #{user.username}") .getR
esultList(); if (existing.size()==0) { em.persist(user); log.info("Registered new user #{user.username}"); retur
n "/registered.xhtml"; }
else { FacesMessages.instance().add("User #{user.username} already exists"); retur
n null; } } }
![]() | L'annotazione EJB |
![]() | L'annotazione |
![]() | L'annotazione EJB standard |
![]() | L'annotazione |
![]() | Il metodo action listener utilizza l'API EJB3 standard |
![]() | Si noti che Seam consente di utilizzare espressioni JSF EL dentro EJB-QL. Sotto il coperchio, questo proviene da un'ordinaria chiamata JPA |
![]() | L'API |
![]() | 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. |
![]() | Seam fornisce un numero di componenti predefiniti per aiutare a risolvere problemi comuni. Il componente |
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.
Certamente il nostro session bean richiede un'interfaccia locale.
Questa è la fine del codice Java. Vediamo ora la vista.
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.
Esempio 1.4. register.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title
>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Username: <h:inputText value="#{user.username}" required="true"/>
Real Name: <h:inputText value="#{user.name}" required="true"/>
Password: <h:inputSecret value="#{user.password}" required="true"/>
</h:panelGrid>
</s:validateAll>
<h:messages/>
<h:commandButton value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html
>
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.
Esempio 1.5. registered.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title
>Successfully Registered New User</title>
</head>
<body>
<f:view>
Welcome, #{user.name}, you are successfully registered as #{user.username}.
</f:view>
</body>
</html>
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:
Esempio 1.6. components.xml
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.com/products/seam/core
http://jboss.com/products/seam/core-2.1.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.1.xsd">
<core:init jndi-pattern="@jndiPattern@"/>
</components
>
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.
Esempio 1.7. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<listener>
<listener-class
>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<context-param>
<param-name
>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value
>.xhtml</param-value>
</context-param>
<servlet>
<servlet-name
>Faces Servlet</servlet-name>
<servlet-class
>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup
>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name
>Faces Servlet</servlet-name>
<url-pattern
>*.seam</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout
>10</session-timeout>
</session-config>
</web-app
>
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
Esempio 1.8. faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<application>
<view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config
>
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.
Il file ejb-jar.xml integra Seam con EJB3, attaccando SeamInterceptor a tutti i bean di sessione nell'archivio.
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name
>*</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar
>
Il file persistence.xml dice all'EJB persistence provider dove trovare il datasource, e contiene alcune impostazioni vendor-specific. In questo caso abilita automaticamente l'esportazione dello schema all'avvio.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="userDatabase">
<provider
>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source
>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence
>
Infine poiché l'applicazione viene deployata come EAR, occorre anche un descrittore di deploy.
Esempio 1.9. applicazione di registrazione
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/application_5.xsd"
version="5">
<display-name
>Seam Registration</display-name>
<module>
<web>
<web-uri
>jboss-seam-registration.war</web-uri>
<context-root
>/seam-registration</context-root>
</web>
</module>
<module>
<ejb
>jboss-seam-registration.jar</ejb>
</module>
<module>
<ejb
>jboss-seam.jar</ejb>
</module>
<module>
<java
>jboss-el.jar</java>
</module>
</application
>
Questo descrittore di deploy punta a moduli nell'archivio enterprise ed associa l'applicazione web al contesto radice /seam-registration.
Adesso sono stati analizzati tutti i file dell'intera applicazione!
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.
L'entity Message definisce il titolo, il testo, la data e l'orario del messaggio e un flag indica se il messaggio è stato letto:
Esempio 1.10. Message.java
@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
private Long id;
private String title;
private String text;
private boolean read;
private Date datetime;
@Id @GeneratedValue
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
@NotNull @Length(max=100)
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
@NotNull @Lob
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
@NotNull
public boolean isRead()
{
return read;
}
public void setRead(boolean read)
{
this.read = read;
}
@NotNull
@Basic @Temporal(TemporalType.TIMESTAMP)
public Date getDatetime()
{
return datetime;
}
public void setDatetime(Date datetime)
{
this.datetime = datetime;
}
}
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
t<Message
> messageList;
@DataModelS
election
@Out(requir
ed=false)
private Message message;
@Persistenc
eContext(type=EXTENDED)
private EntityManager em;
@Factory("m
essageList")
public void findMessages()
{
messageList = em.createQuery("select msg from Message msg order by msg.datetime desc")
.getResultList();
}
public void
select()
{
message.setRead(true);
}
public void
delete()
{
messageList.remove(message);
em.remove(message);
message=null;
}
@Remove
public void destroy() {}
}![]() | L'annotazione |
![]() | L'annotazione |
![]() | L'annotazione |
![]() | 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' |
![]() | La prima volta che si naviga in un pagina JSP, non c'è alcun valore nella variabile di contesto |
![]() | Il metodo action listener |
![]() | Il metodo action listener |
![]() | Tutti i componenti Seam bean di sessione stateful devono avere un metodo senza parametri marcato |
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.)
Tutti i session bean hanno un'interfaccia di business, naturalmente.
Esempio 1.12. MessageManager.java
@Local
public interface MessageManager
{
public void findMessages();
public void select();
public void delete();
public void destroy();
}
D'ora in poi non verranno mostrate interfacce locali nei codici d'esempio.
Saltiamo i file components.xml, persistence.xml, web.xml, ejb-jar.xml, faces-config.xml e application.xml poiché sono praticamente uguali all'esempio precedente e andiamo dritti alla pagina JSP.
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:
Esempio 1.14. todo.jpdl.xml
<process-definition name="todo"> <start-state name="start"> <transition to="todo"/> </start-state> <task-node
name="todo"> <task na
me="todo" description="#{todoList.description}"> <assi
gnment actor-id="#{actor.id}"/> </task> <transition to="done"/> </task-node> <end-state
name="done"/> </process-definition >
![]() | Il nodo |
![]() | Il nodo |
![]() | L'elemento |
![]() | I task devono essere assegnati ad un utente od un gruppo di utenti quando vengono creati. In questo caso il task viene assegnato all'utente corrente che viene recuperato dal componente Seam predefinito chiamato |
![]() | Il nodo |
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.
Esempio 1.15. Login.java
@Name("login")
public class Login
{
@In
private Actor actor;
private String user;
public String getUser()
{
return user;
}
public void setUser(String user)
{
this.user = user;
}
public String login()
{
actor.setId(user);
return "/todo.jsp";
}
}
Qua si vede l'uso di @In per iniettare il componente predefinito Actor.
Lo stesso JSP è banale:
Esempio 1.16. login.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title
>Login</title>
</head>
<body>
<h1
>Login</h1>
<f:view>
<h:form>
<div>
<h:inputText value="#{login.user}"/>
<h:commandButton value="Login" action="#{login.login}"/>
</div>
</h:form>
</f:view>
</body>
</html
>
Il secondo JavaBean è responsabile per l'avvio delle istanze del processo di business e della fine dei task.
Esempio 1.17. TodoList.java
@Name("todoList")
public class TodoList
{
private String description;
public Stri
ng getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
@CreateProcess(definition="todo")
public void createTodo() {}
@StartTask @EndTask
public void done() {}
}![]() | La proprietà descrizione accetta l'input utente dalla pagina JSP e lo espone alla definizione del processo, consentendo che venga impostata la descrizione del task. |
![]() | L'annotazione Seam |
![]() | L'annotazione Seam |
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.
Esempio 1.19. todo.jsp
<h:form id="list">
<div>
<h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task"
rendered="#{not empty taskInstanceList}">
...
</h:dataTable>
</div>
</h:form
>
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
>
#{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
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:directi
ve.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
>![]() | L'elemento |
![]() | L'elemento |
![]() | Una transizione |
![]() | Un nodo |
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:
Esempio 1.22. win.jspx
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns="http://www.w3.org/1999/xhtml"
version="2.0">
<jsp:output doctype-root-element="html"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<jsp:directive.page contentType="text/html"/>
<html>
<head>
<title
>You won!</title>
<link href="niceforms.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1
>You won!</h1>
<f:view>
Yes, the answer was <h:outputText value="#{numberGuess.currentGuess}" />.
It took you <h:outputText value="#{numberGuess.guessCount}" /> guesses.
<h:outputText value="But you cheated, so it doesn't count!"
rendered="#{numberGuess.cheat}"/>
Would you like to <a href="numberGuess.seam"
>play again</a
>?
</f:view>
</body>
</html>
</jsp:root>
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
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;
}
}
![]() | La prima volta che una pagina JSP richiede un componente |
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.
Esempio 1.24. pages.xml
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
<page view-id="/numberGuess.jspx">
<begin-conversation join="true" pageflow="numberGuess"/>
</page>
</pages
>
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.
L'applicazione booking (prenotazione) è un sistema completo per la prenotazione di camere d'hotel, ed incorpora le seguenti funzionalità:
Registrazione utente
Login
Logout
Impostazione password
Ricerca hotel
Scelta hotel
Prenotazione stanza
Conferma prenotazione
Lista di prenotazioni esistenti

L'applicazione booking utilizza JSF, EJB 3.0 e Seam, assieme a Facelets per la vista. C'è anche un port di quest'applicazione con JSF, Facelets, Seam, JavaBeans e Hibernate3.
Una delle cose che si noteranno utilizzando quest'applicazione per qualche tempo è che questa risulta essere estremamente robusta. Si può giocare con il pulsante indietro ed aggiornare le pagine, aprire più finestre ed inserire dati senza senso quanto si vuole, ma si vedrà che è molto difficile mettere in difficoltà l'applicazione. Si può pensare che siano occorse settimane per testare e risolvere bug prima di raggiungere questo risultato. In verità non è così. Seam è stato progettato per rendere semplice la costruzione di applicazioni web robuste, e molta della robustezza che si è soliti doversi codificare da soli, con Seam viene naturale e automatica.
Quando si sfoglia il codice sorgente delle applicazioni e si impara come questa funziona, si osservi come sono stati usati la gestione dichiarativa dello stato e la validazione integrata per ottenere questa robustezza.
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@Name("hotelSearch") @Scope(ScopeType.SESSION) @Restrict("#{i
dentity.loggedIn}") public class HotelSearchingAction implements HotelSearching { @PersistenceContext private EntityManager em; private String searchString; private int pageSize = 10; private int page; @DataModel
private List<Hotel > hotels; public void find() { page = 0; queryHotels(); } public void nextPage() { page++; queryHotels(); } private void queryHotels() { hotels = em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} " + "or lower(h.city) like #{pattern} " + "or lower(h.zip) like #{pattern} " + "or lower(h.address) like #{pattern}") .setMaxResults(pageSize) .setFirstResult( page * pageSize ) .getResultList(); } public boolean isNextPageAvailable() { return hotels!=null && hotels.size()==pageSize; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } @Factory(value="pattern", scope=ScopeType.EVENT) public String getSearchPattern() { return searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%'; } public String getSearchString() { return searchString; } public void setSearchString(String searchString) { this.searchString = searchString; }
@Remove public void destroy() {} }
![]() | L'annotazione EJB standard |
![]() | L'annotazione |
![]() | L'annotazione |
![]() | L'annotazione standard EJB |
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}"
style="width: 165px;">
<a:support event="onkeyup" actionListener="#{hotelSearch.find}"
reRender="searchResults" />
</h:inputText>
 
<a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}"
reRender="searchResults"/>
 
<a:status>
<f:facet name="start">
<h:graphicImage value="/img/spinner.gif"/>
</f:facet>
</a:status>
<br/>
<h:outputLabel for="pageSize"
>Maximum results:</h:outputLabel
> 
<h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
</fieldset>
</h:form>
</div>
<a:outputPanel id="searchResults">
<div class="section">
<h:outputText value="No Hotels Found"
rendered="#{hotels != null and hotels.rowCount==0}"/>
<h:dataTable id="hotels" value="#{hotels}" var="hot"
rendered="#{hotels.rowCount
>0}">
<h:column>
<f:facet name="header"
>Name</f:facet>
#{hot.name}
</h:column>
<h:column>
<f:facet name="header"
>Address</f:facet>
#{hot.address}
</h:column>
<h:column>
<f:facet name="header"
>City, State</f:facet>
#{
hot.city}, #{hot.state}, #{hot.country}
</h:column
>
<h:column>
<f:facet name="header"
>Zip</f:facet>
#{hot.zip}
</h:column>
<h:column>
<f:facet name="header"
>Action</f:facet>
<s:link id="viewHotel" value="View Hotel"
action="#{hotelBooking.selectHotel(hot)}"/>
</h:column>
</h:dataTable>
<s:link value="More results" action="#{hotelSearch.nextPage}"
rendered="#{hotelSearch.nextPageAvailable}"/>
</div>
</a:outputPanel
> ![]() | Il tag RichFaces Ajax |
![]() | Il tag RichFaces Ajax |
![]() | Il tag RichFaces Ajax |
![]() | Il tag Seam Se ci si chiede come avvenga la navigazione, si possono trovare tutte le regole in |
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
eContext(type=EXTENDED)
private EntityManager em;
@In
private User user;
@In(required=false) @Out
private Hotel hotel;
@In(required=false)
@Out(requir
ed=false)
private Booking booking;
@In
private FacesMessages facesMessages;
@In
private Events events;
@Logger
private Log log;
private boolean bookingValid;
@Begin
public void selectHotel(Hotel selectedHotel)
{
hotel = em.merge(selectedHotel);
}
public void bookHotel()
{
booking = new Booking(hotel, user);
Calendar calendar = Calendar.getInstance();
booking.setCheckinDate( calendar.getTime() );
calendar.add(Calendar.DAY_OF_MONTH, 1);
booking.setCheckoutDate( calendar.getTime() );
}
public void setBookingDetails()
{
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -1);
if ( booking.getCheckinDate().before( calendar.getTime() ) )
{
facesMessages.addToControl("checkinDate", "Check in date must be a future date");
bookingValid=false;
}
else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
{
facesMessages.addToControl("checkoutDate",
"Check out date must be later than check in date");
bookingValid=false;
}
else
{
bookingValid=true;
}
}
public boolean isBookingValid()
{
return bookingValid;
}
@End
public void confirm()
{
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number " +
" for #{hotel.name} is #{booki g.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End
public void cancel() {}
@Remove
public void destroy() {}
![]() | 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. |
![]() | L'annotazione |
![]() | L'annotazione |
![]() | L'annotazione |
![]() | 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.
Il WAR include anche seam-debug.jar. La pagina di debug di Seam sarà disponibile se questo jar è deployato in WEB-INF/lib, assieme a Facelets e se è stata impostata la proprietà di debug nel componente init:
<core:init jndi-pattern="@jndiPattern@" debug="true"/>
Questa pagina consentirà di sfogliare ed ispezionare i componenti Seam in ogni contesto Seam associato alla sessione di login corrente. Si punti il browser su http://localhost:8080/seam-booking/debug.seam .

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
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);
}
@Begin(nested=true)
public String selectPreference()
{
log.info("Room selected");
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
Redirect=true)
public String cancel()
{
log.info("ending conversation");
return "cancel";
}
@Destroy @Remove
public void destroy() {}
}
![]() | L'istanza |
![]() | Quando si incontra |
![]() |
|
![]() | L'annotazione |
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}"/>
<br/><br/>
<h:dataTable value="#{availableRooms}" var="room"
rendered="#{availableRooms.rowCount
> 0}">
<h:column>
<f:facet name="header"
>Name</f:facet>
#{room.name}
</h:column>
<h:column>
<f:facet name="header"
>Description</f:facet>
#{room.description}
</h:column>
<h:column>
<f:facet name="header"
>Per Night</f:facet>
<h:outputText value="#{room.price}">
<f:convertNumber type="currency" currencySymbol="$"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header"
>Action</f:facet>
<h:commandLink id="selectRoomPreference"
action="#{roomPreference.selectPreference}"
>Select</h:commandLink>
</h:column>
</h:dataTable>
</div>
<div class="entry">
<div class="label"
> </div>
<div class="input">
<s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
</div>
</div
>
</h:form>
</div>
![]() | Quando richiesto da EL, |
![]() | L'invocazione dell'azione |
![]() | Un cambiamento alle date semplicemente riporta a |
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
confirm()
{
// on confirmation we set the room preference in the booking. the room preference
// will be injected based on the nested conversation we are in.
booking.setRoomPreference(roomSelection);
em.persist(booking);
facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");
log.info("New booking: #{booking.id} for #{user.username}");
events.raiseTransactionSuccessEvent("bookingConfirmed");
}
@End(root=t
rue, beforeRedirect=true)
public void cancel() {}
@Destroy @Remove
public void destroy() {}
}
![]() | Annotare un'azione con |
![]() |
|
![]() | Annotando semplicemente l'azione di cancellazione con |
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.
L'applicazione demo Negozio DVD mostra un utilizzo pratico di jBPM sia per la gestione task sia per il pageflow.
Le schermate utente sfruttano il pageflow jPDL per implementare la ricerca e la funzionalità di carrello della spesa.

Le schermate di amministrazione utilizzano jBPM per gestire il ciclo di approvazione e di spedizione degli ordini. Il processo di business può anche essere cambiato dinamicamente, selezionando una diversa definizione di processo!

La demo Negozio DVD può essere eseguita dalla directory dvdstore, così come le altre applicazioni.
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:
Esempio 1.31.
<h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3">
<h:column>
<div class="blogEntry">
<h3
>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt}"/>
</div>
<p>
<s:link view="/entry.xhtml" rendered="#{blogEntry.excerpt!=null}" propagation="none"
value="Read more...">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
<p>
[Posted on 
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText
>]
 
<s:link view="/entry.xhtml" propagation="none" value="[Link]">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
</div>
</h:column>
</h:dataTable
>
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.
Esempio 1.32.
@Name("blog")
@Scope(ScopeType.STATELESS)
@AutoCreate
public class BlogService
{
@In EntityM
anager entityManager;
@Unwrap
public Blog getBlog()
{
return (Blog) entityManager.createQuery("select distinct b from Blog b left join fetch b.blogEntries")
.setHint("org.hibernate.cacheable", true)
.getSingleResult();
}
}![]() | Questo componente utilizza un contesto di persistenza gestito da Seam. A differenza deglialtri esempi visti, questo contesto di persistenza è gestito da Seam, invece che dal container EJB3. Il contesto di persistenza estende l'intera richiesta web, consentendo di evitare le eccezioni che avvengono quando nella vista si accede ad associazioni non recuperate (unfetched). |
![]() | L'annotazione |
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:
Esempio 1.33.
<div id="search">
<h:form>
<h:inputText value="#{searchAction.searchPattern}"/>
<h:commandButton value="Search" action="/search.xhtml"/>
</h:form>
</div
>
Per implementare una pagina di risultati di ricerca memorizzabili come segnalibro, occorre eseguire un redirect del browser dopo aver elaborato la form di ricerca inviata. Poiché si è impiegato come esito d'azione l'id della vista JSF, Seam redirige automaticamente all'id vista quando la form viene inviata. In alternativa, si può definire una regola di navigazione come questa:
<navigation-rule>
<navigation-case>
<from-outcome
>searchResults</from-outcome>
<to-view-id
>/search.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
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:
Esempio 1.34.
<pages>
<page view-id="/search.xhtml">
<rewrite pattern="/search/{searchPattern}"/>
<rewrite pattern="/search"/>
<param name="searchPattern" value="#{searchService.searchPattern}"/>
</page>
...
</pages
>
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 
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText
>]
</p>
</div
>
Se l'entry del blog non viene trovata nel database, viene lanciata l'eccezione EntryNotFoundException. Si vuole che quest'eccezione venga evidenziata come errore 404, non 505, e quindi viene annotata la classe dell'eccezione:
@ApplicationException(rollback=true)
@HttpError(errorCode=HttpServletResponse.SC_NOT_FOUND)
public class EntryNotFoundException extends Exception
{
EntryNotFoundException(String id)
{
super("entry not found: " + id);
}
}
Un'implementazione alternativa dell'esempio non utilizza il parametro nel method binding:
@Name("entryAction")
@Scope(STATELESS)
public class EntryAction
{
@In(create=true)
private Blog blog;
@In @Out
private BlogEntry blogEntry;
public void loadBlogEntry() throws EntryNotFoundException
{
blogEntry = blog.getBlogEntry( blogEntry.getId() );
if (blogEntry==null) throw new EntryNotFoundException(id);
}
}
<pages>
...
<page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry}">
<param name="blogEntryId" value="#{blogEntry.id}"/>
</page>
...
</pages
>
E' una questione di gusti su quale implementazione tu preferisca.
La demo del blog mostra anche una semplice autenticazione di password, un invio di un post al blog, un esempio di caching frammentato della pagina e la generazione di atom feed.
La distribuzione Seam comprende una utility da linea di comando che facilita la configurazione di un progetto eclipse, la generazione di un semplice codice skeleton Seam, ed il reverse engineer di un'applicazione da un database esistente.
Questo è il modo più semplice di sporcarti le mani con Seam e di preparare il colpo in canna per la prossima volta che ti troverai intrappolato in ascensore con uno di quei noiosi tipi di Ruby-on-Rail che farneticano quanto magnifico e meraviglioso sia l'ultimo giochino che hanno scoperto per realizzare applicazioni completamente banali che schiaffano delle cose nel database.
In questa relase, seam-gen funziona meglio per coloro che hanno JBoss AS. Si può usare il progetto generato con altri server J2EE o Java EE 5 facendo alcuni cambiamenti alla configurazione del progetto.
Si può usare seam-gen senza Eclipse, ma in questo tutorial, si vuole mostrare l'uso assieme ad Eclipse per il debugging ed i test. Se non si vuole installare Eclipse, si può seguire comunque questo tutorial - tutti i passi possono essere eseguiti da linea di comando.
Seam-gen è essenzialmente uno script Ant avvolto attorno a Hibernate Tools, assieme a qualche template. Questo facilita la sua personalizzazione nel caso ce ne sia bisogno.
Assicurarsi di avere JDK 5 o JDK 6 (vedere Sezione 40.1, «Dipendenze JDK» per maggiori dettagli), JBoss AS 4.2 e Ant 1.6, con una versione recente di Eclipse, il plugin JBoss IDE di Eclipse ed il plugin TestNG per Eclipse correttamente installati prima di avviare. Aggiungere l'installazione di JBoss alla vista di JBoss Server in Eclipse. Avviare JBoss in modalità debug. Infine, avviare da comando all'interno della directory dove si è scompattato la distribuzione Seam.
JBoss ha un supporto sofisticato per l'hot re-deploy di WAR e EAR. Sfortunatamente, a causa di bug alla JVM, ripetuti redeploy di un EAR—situazione frequente durante lo sviluppo—causano un perm gen space della JVM. Per questa ragione, in fase di sviluppo si raccomanda di eseguire JBoss in una JVM avente parecchio perm gen space. Se si esegue JBoss da JBoss IDE, si può configurare questo nella configurazione di lancio del server, sotto "VM arguments". Si suggeriscono i seguenti valori:
-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512m
Se non si ha molta memoria disponibile, si raccomanda come minimo:
-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m
Se si esegue JBoss da linea di comando, si possono configurare le opzioni JVM in bin/run.conf.
Se non si vuole avere a che fare con queste cose adesso, si lasci stare—ce ne si occuperà quando capiterà la prima OutOfMemoryException.
La prima cosa da fare è configurare seam-gen per il proprio ambiente: la directory di installazione JBoss AS, il workspace di Eclipse, e la connessione del database. E' facile, si digiti:
cd jboss-seam-2.0.x seam setup
E verranno richieste le informazioni necessarie:
~/workspace/jboss-seam$ ./seam setup
Buildfile: build.xml
init:
setup:
[echo] Welcome to seam-gen :-)
[input] Enter your Java project workspace (the directory that contains your Seam projects) [C:/Projects] [C:/Projects]
/Users/pmuir/workspace
[input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.2.GA] [C:/Program Files/jboss-4.2.2.GA]
/Applications/jboss-4.2.2.GA
[input] Enter the project name [myproject] [myproject]
helloworld
[echo] Accepted project name as: helloworld
[input] Select a RichFaces skin (not applicable if using ICEFaces) [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)
[input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear] ([ear], war, )
[input] Enter the Java package name for your session beans [com.mydomain.helloworld] [com.mydomain.helloworld]
org.jboss.helloworld
[input] Enter the Java package name for your entity beans [org.jboss.helloworld] [org.jboss.helloworld]
[input] Enter the Java package name for your test cases [org.jboss.helloworld.test] [org.jboss.helloworld.test]
[input] What kind of database are you using? [hsql] ([hsql], mysql, oracle, postgres, mssql, db2, sybase, enterprisedb, h2)
mysql
[input] Enter the Hibernate dialect for your database [org.hibernate.dialect.MySQLDialect] [org.hibernate.dialect.MySQLDialect]
[input] Enter the filesystem path to the JDBC driver jar [lib/hsqldb.jar] [lib/hsqldb.jar]
/Users/pmuir/java/mysql.jar
[input] Enter JDBC driver class for your database [com.mysql.jdbc.Driver] [com.mysql.jdbc.Driver]
[input] Enter the JDBC URL for your database [jdbc:mysql:///test] [jdbc:mysql:///test]
jdbc:mysql:///helloworld
[input] Enter database username [sa] [sa]
pmuir
[input] Enter database password [] []
[input] skipping input as property hibernate.default_schema.new has already been set.
[input] Enter the database catalog name (it is OK to leave this blank) [] []
[input] Are you working with tables that already exist in the database? [n] (y, [n], )
y
[input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y, [n], )
n
[input] Enter your ICEfaces home directory (leave blank to omit ICEfaces) [] []
[propertyfile] Creating new property file: /Users/pmuir/workspace/jboss-seam/seam-gen/build.properties
[echo] Installing JDBC driver jar to JBoss server
[echo] Type 'seam create-project' to create the new project
BUILD SUCCESSFUL
Total time: 1 minute 32 seconds
~/workspace/jboss-seam $ Il tool fornisce dei valori di default, che possono essere accettati semplicemente premendo Invio alla richiesta.
La scelta più importante da fare è tra il deploy EAR e il deploy WAR del progetto. I progetti EAR supportano EJB 3.0 e richiedono Java EE 5. I progetti WAR non supportano EJB 3.0, ma possono essere deployati in ambienti J2EE. L'impacchettamento di un WAR è più semplice da capire. Se si installa un application server predisposto per EJB3, come JBoss, si scelga ear. Altrimenti si scelga war. Assumeremo per il resto del tutorial che la scelta sia il deploy EAR, ma si potranno compiere gli stessi passi per il deploy WAR.
Se si sta lavorando con un modello di dati esistente, ci si assicuri di dire a seam-ger che le tabelle esistono già nel database.
Le impostazioni vengono memorizzate in seam-gen/build.properties, ma si possono anche modificare eseguendo semplicemente una seconda volta seam setup.
Ora è possibile creare un nuovo progetto nella directory di workspace di Eclipse, digitando:
seam new-project
C:\Projects\jboss-seam>seam new-project
Buildfile: build.xml
...
new-project:
[echo] A new Seam project named 'helloworld' was created in the C:\Projects directory
[echo] Type 'seam explode' and go to http://localhost:8080/helloworld
[echo] Eclipse Users: Add the project into Eclipse using File > New > Project and select General > Project (not Java Project)
[echo] NetBeans Users: Open the project in NetBeans
BUILD SUCCESSFUL
Total time: 7 seconds
C:\Projects\jboss-seam>Questo copia i jar Seam, i jar dipendenti ed il driver JDBC nel nuovo progetto Eclipse, e genera tutte le risorse necessarie ed il file di configurazione, i file template di facelets ed i fogli di stile, assieme ai metadati di Eclipse e allo script per il build di Ant. Il progetto Eclipse verrà automaticamente deployato in una struttura di directory esplosa in JBoss AS non appena si aggiungerà il progetto usando New -> Project... -> General -> Project -> Next, digitando Project name (helloworld in questo caso), e poi cliccando Finish. Non selezionare Java Project dallo wizard New Project.
Se in Eclipse la JDK di default non è Java SE 5 o Java SE 6 JDK, occorre selezionare un JDK compatibile Java SE 5 usando Project -> Properties -> Java Compiler.
in alternativa, si può eseguire il deploy del progetto dal di fuori di Eclipse digitando seam explode.
Si vada in http://localhost:8080/helloworld per vedere la pagina di benvenuto. Questa è una pagina facelets, view/home.xhtml, che utilizza il template view/layout/template.xhtml. In Eclipse si può modificare questa pagina, oppure il template, e vedere immediatamente i risultati, cliccando il pulsante aggiorna del browser.
Non si abbia paura dell'XML, i documenti di configurazione generati nella directory di progetto. Per la maggiore parte delle volte sono standard per Java EE, parti che servono per creare la prima volta e poi non si guarderanno più, ed al 90% sono sempre le stesse per ogni progetto Seam. (Sono così facili da scrivere che anche seam-gen può farlo.)
Il progetto generato include tre database e le configurazioni per la persistenza. I file persistence-test.xml e import-test.sql vengono usati quando di eseguono i test di unità TestNG con HSQLDB. Lo schema del database ed i dati di test in import-test.sql vengono sempre esportati nel database prima dell'esecuzione dei test. I file myproject-dev-ds.xml, persistence-dev.xml e import-dev.sql sono usati per il deploy dell'applicazione nel database di sviluppo. Lo schema può essere esportato automaticamente durante il deploy, a seconda che si sia detto a seam-gen che si sta lavorando con un database esistente. I file myproject-prod-ds.xml, persistence-prod.xml e import-prod.sql sono usati per il deploy dell'applicazione nel database di produzione. Lo schema non viene esportato automaticamente durante il deploy.
Se si è abituati ad usare un framework web action-style, ci si domanderà come in Java sia possibile creare una semplice pagina web con un metodo d'azione stateless. Se si digita:
seam new-action
Seam chiederà alcune informazioni, e genererà per il progetto una nuova pagina facelets ed i componenti Seam.
C:\Projects\jboss-seam>seam new-action
Buildfile: build.xml
validate-workspace:
validate-project:
action-input:
[input] Enter the Seam component name
ping
[input] Enter the local interface name [Ping]
[input] Enter the bean class name [PingBean]
[input] Enter the action method name [ping]
[input] Enter the page name [ping]
setup-filters:
new-action:
[echo] Creating a new stateless session bean component with an action method
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
[copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
[copy] Copying 1 file to C:\Projects\helloworld\view
[echo] Type 'seam restart' and go to http://localhost:8080/helloworld/ping.seam
BUILD SUCCESSFUL
Total time: 13 seconds
C:\Projects\jboss-seam>Poiché è stato aggiunto un nuovo componente Seam, occorre riavviare il deploy della directory esplosa. E' possibile farlo digitando seam restart, od eseguendo il target restart nel file build.xml del progetto generato all'interno di Eclipse. Un altro modo per forzare il riavvio è editare in Eclipse il file resources/META-INF/application.xml. Si noti che non occorre riavviare JBoss ogni volta che cambia l'applicazione.
Adesso si vada in http://localhost:8080/helloworld/ping.seam e si clicchi il pulsante. Si può vedere il codice sottostante l'azione guardando il progetto nella directory src. Si metta un breakpoint nel metodo ping(), e si clicchi nuovamente il pulsante.
Infine si cerchi il file PingTest.xml nel pacchetto dei test e si eseguano i test d'integrazione usando il plugin TestNG di Eclipse. In alternativa, si eseguano i test usando seam test od il target test del build generato.
Il prossimo passo è creare una form. Si digiti:
seam new-form
C:\Projects\jboss-seam>seam new-form
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml
validate-workspace:
validate-project:
action-input:
[input] Enter the Seam component name
hello
[input] Enter the local interface name [Hello]
[input] Enter the bean class name [HelloBean]
[input] Enter the action method name [hello]
[input] Enter the page name [hello]
setup-filters:
new-form:
[echo] Creating a new stateful session bean component with an action method
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[copy] Copying 1 file to C:\Projects\hello\view
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[echo] Type 'seam restart' and go to http://localhost:8080/hello/hello.seam
BUILD SUCCESSFUL
Total time: 5 seconds
C:\Projects\jboss-seam>Si riavvii di nuovo l'applicazione e si vada in http://localhost:8080/helloworld/hello.seam. Quindi si guardi il codice generato. Avviare i test. Si aggiungano nuovi campi alla form e al componente Seam (ricordarsi di riavviare il deploy ad ogni cambiamento del codice Java).
Si creino manualmente le tabelle nel database. (Se occorre passare ad un database diverso, si esegua di nuovo seam setup.) Adesso si digiti:
seam generate-entities
Riavviare il deploy ed andare in http://localhost:8080/helloworld. Si può sfogliare il database, modificare gli oggetti esistenti e creare nuovi oggetti. Se si guarda al codice generato, probabilmente ci si meraviglierà di quanto è semplice! Seam è stato progettato affiché sia semplice scrivere a mano il codice d'accesso ai dati, anche per persone che non vogliono barare usando seam-gen.
Si mettano le classi entity esistenti dentro src/main. Ora si digiti:
seam generate-ui
Si avvi il deploy, e si vada alla pagina http://localhost:8080/helloworld.
Infine si vuole essere in grado di eseguire il deploy dell'applicazione usando l'impacchettamento standard di Java EE 5. In primo luogo occorre rimuovere la directory esplosa eseguendo seam unexplode. Per fare il deploy dell'EAR, si può digitare da linea di comando seam deploy, od eseguire il target deploy dello script di build del progetto generato. L'undeploy può essere eseguito usando seam undeploy od il target undeploy.
Di default l'applicazione verrà deployata con il profile dev. L'EAR includerà i file persistence-dev.xml e import-dev.sql, e verrà deployato il file myproject-dev-ds.xml. Si può cambiare il profilo ed usare il profile prod, digitando:
seam -Dprofile=prod deploy
Si possono anche definire nuovi profili di deploy per l'applicazione. Basta aggiungere file al progetto—per esempio, persistence-staging.xml, import-staging.sql e myproject-staging-ds.xml—e selezionare il nome del profile usando -Dprofile=staging.
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:
qualsiasi pagina facelets
qualsiasi file pages.xml
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:
i componenti devono essere componenti JavaBean, non possono essere bean EJB3 (stiamo lavorando per sistemare questa limitazione)
gli entity non possono mai essere deployati a caldo (hot deployment)
i componenti deployati via components.xml non possono essere deployati a caldo
i componenti deployabili a caldo non saranno visibili alle classi deployate fuori da WEB-INF/dev
La modalità di degub di Seam deve essere abilitata e jboss-seam-debug.jar deve trovarsi in WEB-INF/lib
Occorre avere installato in web.xml il filtro Seam.
Si possono vedere errori se il sistema è messo sotto carico ed è abilitato il debug.
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.
Seam 2.0 è stato sviluppato per JavaServer Faces 1.2. Usando JBoss AS, si raccomanda di usare JBoss 4.2, che incorpora l'implementazione di riferimento JSF 1.2. Comunque è possibile usare Seam 2.0 su piattaforma JBoss 4.0. Ci sono due passi base richiesti per farlo: installare la versione JBoss 4.0 con EJB3 abilitato e sostituire MyFaces con l'implementazione di riferimento JSF 1.2. Una volta completati questi passi, le applicazioni Seam 2.0 possono essere deployate in JBoss 4.0.
JBoss 4.0 non porta con sé una configurazione di default compatibile con Seam. Per eseguire Seam, occorre installare JBoss 4.0.5 usando l'installer JEMS 1.2 con il profile ejb3 selezionato. Seam non funzionerà con un'installazione che non include il supporto EJB3. L'installer JEMS può essere scaricato da http://labs.jboss.com/jemsinstaller/downloads.
La configurazione web per JBoss 4.0 può essere trovata in server/default/deploy/jbossweb-tomcat55.sar. Occorre cancellare myfaces-api.jar e myfaces-impl.jar dalla directory jsf-libs. Poi occorre copiare jsf-api.jar, jsf-impl.jar, el-api.jar, e el-ri.jar in questa directory. I JAR JSF possono essere trovati nella directory lib di Seam. I JAR EL possono essere ottenuti dalla release Seam 1.2.
Occorre modificare il conf/web.xml, sostituendo myfaces-impl.jar con jsf-impl.jar.
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.
Assicurarsi di avere JDK 5, JBoss AS 4.2, Eclipse 3.3, i plugin di JBoss Tool (almeno Seam Tools, Visual Page Editor, jBPM Tools e JBoss AS Tools) ed il plugin TestNG per Eclipse correttamente installati prima di partire.
TODO - dettaglio su dove si trovano i siti per l'update.
Avviare Eclipse e selezionare la prospettiva Seam.
Si vada in File -> New -> Seam Web Project.

Primo, inserire un nome per il nuovo progetto. Per questo tutorial si userà helloworld .
Ora, occorre dire a JBoss Tools dell'esistenza di JBoss AS. Questo è un processo in due fasi, primo occorre definire un runtime, assicurarsi di selezionare JBoss AS 4.2:

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

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

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

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

Le prossime 3 schermate consentono di personalizzare ulteriormente il proprio progetto, ma per noi i valori di default vanno bene. Si prema Next fino ad arrivare alla schermata finale.
Il primo passo è dire a JBoss Tools quale download di Seam si vuole usare. Aggiungere un nuovo Seam Runtime - assicurarsi di dare un nome, e selezionare 2.0 come versione:

La scelta più importante da fare è tra deploy EAR e deploy WAR del proprio progetto. I progetti EAR supportano EJB 3.0 e richiede Java EE 5. I progetti WAR non supportano EJB 3.0, ma possono essere deployati in ambiente J2EE. Anche l'impacchettamento di un WAR è semplice da capire. Se si è installato un application server pronto per EJB3 come JBoss, si scelga EAR. Altrimenti, si scelga WAR. Assumeremo per il resto del tutorial che si sia scelto il deploy WAR, ma si possono seguire gli stessi passi per un deploy EAR.
Poi, si selezioni il tipo di database. Assumeremo di avere installato MySQL, con uno schema esistente. Occorrerà dire a JBoss Tools del database, selezionare MySQL come database, e creare un nuovo profilo di connessione. Selezionare Generic JDBC Connection:

Si metta un nome:

JBoss Tools non è fornito assieme ai driver dei database, quindi occorre specificare dove è collocato il driver MySQL JDBC. Si prema il pulsante ....
Trovare MySQL 5 e premere Add...:

Scegliere il template MySQL JDBC Driver:

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

Riguardare lo username e la password usate per la connessione, e se corretti, premere Ok.
Infine scegliere il driver appena creato:

Se si sta lavorando con un modello di dati esistenti, assicurarsi di segnalare a JBoss Tools che le tabelle esistono già nel database.
Riguardare lo username e la password usate per la connessione, testare la connessione usando il pulsante Test Connection, e se funzionante, premere Finish:
Infine, rivedere i nomi dei pacchetti per i bean generati, e se si è soddisfatti, cliccare su Finish:

JBoss ha un supporto molto sofisticato per il redeploy a caldo (hot deploy) di WAR e EAR. Sfortunatamente, a causa di alcuni bug nella JVM, ripetuti redeploy di un EAR - comuni durante lo sviluppo - possono portare eventualmente ad errori di perm gen space nella JVM. Per questa ragione, raccomandiamo di eseguire JBoss in fase di sviluppo dentro una JVM con un ampio perm gen space. Suggeriamo i seguenti valori:
-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512
Se la memoria disponibile è poca, questa è la quantità minima suggerita:
-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256
Trovare il server in JBoss Server View, cliccare col tasto destro sul server e selezionare Edit Launch Configuration:

Poi si cambino gli argomenti VM:

Se non si vuole avere a che fare con queste configurazioni, non occorre farlo - si ritorni in questa sezione quando si vedrà la prima OutOfMemoryException.
Per avviare JBoss, e fare il deploy del progetto, cliccare col destro sul server creato e cliccare quindi Start, (o Debug per avviare in modalità Debug):

Non si abbia paura dei documenti di configurazione in XML che sono stati creati nella directory di progetto. La maggior parte riguardano Java EE, e sono creati la prima volta e poi non vengono più toccati. Al 90% sono sempre gli stessi nei vari progetti Seam.
Se si è abituati ai tradizionali framework web action-style, probabilmente ci si sta domandando come si crea una semplice pagina web con un metodo d'azione stateless in Java.
Innanzitutto selezionare New -> Seam Action:

Ora, si inserisca il nome del componente Seam. JBoss Tools seleziona i valori di default sensibili per gli altri campi:

Infine di prema Finish.
Ora si vada in http://localhost:8080/helloworld/ping.seam e si clicchi il pulsante. Si può vedere il codice dietro l'azione guardando nel progetto alla directory src. Si metta un breakpoint nel metodo ping(), e si clicchi di nuovo il pulsante.
Infine, si apra il progetto helloworld-test, si cerchi la classe PingTest, si clicchi su di essa col tasto destro e si scelga Run As -> TestNG Test:

Il primo passo è creare una form. Selezionare New -> Seam Form:

Ora, si inserisca il nome del componente Seam. JBoss Tools seleziona i valori di default sensibili per gli altri campi:

Si vada in http://localhost:8080/helloworld/hello.seam. Quindi si guardi il codice generato. Si esegua il test. Provare ad aggiungere alcuni nuovi campi nella form e nel componente Seam (si noti che non serve riavviare l'application server ogni volta che cambia il codice in src/action poiché Seam ricarica a caldo il componente per voi, vedere Sezione 3.6, «Seam ed l'hot deploy incrementale con JBoss Tools»).
Si creino manualmente alcune tabelle nel database. (Se serve passare ad un database differente, si crei un nuovo progetto e si selezioni il database corretto). Poi, selezionare New -> Seam Generate Entities:

JBoss Tools fornisce l'opzione per eseguire il reverse engineering di entità, componenti, e viste da uno schema di database o da entità JPA esistenti. Ora eseguiremo un Reverse engineering da database.
Riavviare il deploy:

Andare in http://localhost:8080/helloworld. Si può sfogliare il database, modificare gli oggetti e creane di nuovi. Se si guarda il codice generato, probabilmente ci si meraviglierà della sua semplicità! Seam è stato progettato per rendere semplice la scrittura del codice, anche per persone che non vogliono imbrogliare usando il reverse engineering.
JBoss Tools supporta il deploy a caldo incrementale di:
qualsiasi pagina facelets
qualsiasi file pages.xml
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:
i componenti devono essere componenti JavaBean, non possono essere EJB3 (stiamo lavorando per risolvere questa limitazione)
gli entity non possono mai essere deployati a caldo
i componenti deployati via components.xml non possono essere deployati a caldo
i componenti hot-deployabili non saranno visibili alle classi deployate fuori da WEB-INF/dev
La modalità debug di Seam deve essere abilitata e jboss-seam-debug.jar deve essere in WEB-INF/lib
Occorre avere il filtro Seam installato in web.xml
Si potrebbero vedere errori se il sistema è sotto carico ed è abilitato il debug.
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:
contesto Stateless
contesto Evento (cioè, richiesta)
contesto Pagina
contesto Conversazione
contesto Sessione
contesto processo di Business
contesto Applicazione
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.
I componenti che sono stateless (in primo luogo bean di sessione stateless) vivono sempre nel contesto stateless (che è sostanzialmente l'assenza di un contesto poiché l'istanza che Seam risolve non è memorizzata). I componenti stateless non sono molto interessanti e sono non molto object-oriented. Tuttavia, essi vengono sviluppati e usati e sono quindi una parte importante di un'applicazione Seam.
Il contesto evento è il contesto stateful "più ristretto" ed è una generalizzazione della nozione del contesto di richiesta web per coprire gli altri tipi di eventi. Tuttavia, il contesto evento associato al ciclo di vita di una richiesta JSF è l'esempio più importante di un contesto evento ed è quello con cui si lavorerà più spesso. I componenti associati al contesto evento vengono distrutti alla fine della richiesta, ma il loro stato è disponibile e ben-definito per almeno il ciclo di vita della richiesta.
Quando si invoca un componente Seam via RMI, o Seam Remoting, il contesto evento viene creato e distrutto solo per l'invocazione.
Il contesto pagina consente di associare lo stato con una particolare istanza di una pagina renderizzata. Si può inizializzare lo stato nell'event listener, o durante il rendering della pagina, e poi avere ad esso accesso da qualsiasi evento che ha origine dalla pagina. Questo è utile per funzionalità quali le liste cliccabili, dove dietro alla lista sono associati dati che cambiamo lato server. Lo stato è in verità serializzato al client, e quindi questo costrutto è estremamente robusto rispetto alle operazioni multi-finestra e al pulsante indietro.
Il contesto conversazione è un concetto fondamentale in Seam. Una conversazione è una unità di lavoro dal punto di vista dell'utente. Può dar vita a diverse interazioni con l'utente, diverse richieste, e diverse transazioni di database. Ma per l'utente, una conversazione risolve un singolo problema. Per esempio, "Prenota hotel", "Approva contratto", "Crea ordine" sono tutte conversazioni. Si può pensare alla conversazione come all'implementazione di un singolo "caso d'uso" o "user story", ma la relazione non è esattamente uguale.
A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.
It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!
Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.
Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.
Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.
Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.
Seam serializza il processo delle richieste concorrenti che prendono posto nello stesso contesto di conversazione long-running, nello stesso processo.
Alternatively, Seam may be configured to keep conversational state in the client browser.
A session context holds state associated with the user login session. While there are some cases where it is useful to share state between several conversations, we generally frown on the use of session context for holding components other than global information about the logged in user.
In a JSR-168 portal environment, the session context represents the portlet session.
The business process context holds state associated with the long running business process. This state is managed and made persistent by the BPM engine (JBoss jBPM). The business process spans multiple interactions with multiple users, so this state is shared between multiple users, but in a well-defined manner. The current task determines the current business process instance, and the lifecycle of the business process is defined externally using a process definition language, so there are no special annotations for business process demarcation.
The application context is the familiar servlet context from the servlet spec. Application context is mainly useful for holding static information such as configuration data, reference data or metamodels. For example, Seam stores its own configuration and metamodel in the application context.
A context defines a namespace, a set of context variables. These work much the same as session or request attributes in the servlet spec. You may bind any value you like to a context variable, but usually we bind Seam component instances to context variables.
So, within a context, a component instance is identified by the context variable name (this is usually, but not always, the same as the component name). You may programatically access a named component instance in a particular scope via the Contexts class, which provides access to several thread-bound instances of the Context interface:
User user = (User) Contexts.getSessionContext().get("user");
You may also set or change the value associated with a name:
Contexts.getSessionContext().set("user", user);
Usually, however, we obtain components from a context via injection, and put component instances into a context via outjection.
Sometimes, as above, component instances are obtained from a particular known scope. Other times, all stateful scopes are searched, in priority order. The order is as follows:
Contesto Evento
contesto Pagina
contesto Conversazione
contesto Sessione
contesto processo di Business
contesto Applicazione
You can perform a priority search by calling Contexts.lookupInStatefulContexts(). Whenever you access a component by name from a JSF page, a priority search occurs.
Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing threadsafeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.
This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.
The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.
Since the session context is multithreaded, and often contains volatile state, session scope components are always protected by Seam from concurrent access so long as the Seam interceptors are not disabled for that component. If interceptors are disabled, then any thread-safety that is required must be implemented by the component itself. Seam serializes requests to session scope session beans and JavaBeans by default (and detects and breaks any deadlocks that occur). This is not the default behaviour for application scoped components however, since application scoped components do not usually hold volatile state and because synchronization at the global level is extremely expensive. However, you can force a serialized threading model on any session bean or JavaBean component by adding the @Synchronized annotation.
This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.
Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.
Bean di sessione stateless EJB 3.0
Bean di sessione stateful EJB 3.0
Bean entity EJB 3.0 (cioè classi entity JPA)
JavaBeans
EJB 3.0 message-driven beans
Bean Spring (see Capitolo 27, Spring Framework integration)
Stateless session bean components are not able to hold state across multiple invocations. Therefore, they usually work by operating upon the state of other components in the various Seam contexts. They may be used as JSF action listeners, but cannot provide properties to JSF components for display.
Stateless session beans always live in the stateless context.
Stateless session beans can be accessed concurrently as a new instance is used for each request. Assigning the instance to the request is the responsibility of the EJB3 container (normally instances will be allocated from a reusable pool meaning that you may find any instance variables contain data from previous uses of the bean).
Stateless session beans are the least interesting kind of Seam component.
Seam stateless session bean components may be instantiated using Component.getInstance() or @In(create=true). They should not be directly instantiated via JNDI lookup or the new operator.
Stateful session bean components are able to hold state not only across multiple invocations of the bean, but also across multiple requests. Application state that does not belong in the database should usually be held by stateful session beans. This is a major difference between Seam and many other web application frameworks. Instead of sticking information about the current conversation directly in the HttpSession, you should keep it in instance variables of a stateful session bean that is bound to the conversation context. This allows Seam to manage the lifecycle of this state for you, and ensure that there are no collisions between state relating to different concurrent conversations.
Stateful session beans are often used as JSF action listener, and as backing beans that provide properties to JSF components for display or form submission.
By default, stateful session beans are bound to the conversation context. They may never be bound to the page or stateless contexts.
Concurrent requests to session-scoped stateful session beans are always serialized by Seam as long as the Seam interceptors are not disabled for the bean.
Seam stateful session bean components may be instantiated using Component.getInstance() or @In(create=true). They should not be directly instantiated via JNDI lookup or the new operator.
Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.
Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.
Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.
By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.
Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.
Seam entity bean components may be instantiated using Component.getInstance(), @In(create=true) or directly using the new operator.
Javabeans may be used just like a stateless or stateful session bean. However, they do not provide the functionality of a session bean (declarative transaction demarcation, declarative security, efficient clustered state replication, EJB 3.0 persistence, timeout methods, etc).
In a later chapter, we show you how to use Seam and Hibernate without an EJB container. In this use case, components are JavaBeans instead of session beans. Note, however, that in many application servers it is somewhat less efficient to cluster conversation or session scoped Seam JavaBean components than it is to cluster stateful session bean components.
By default, JavaBeans are bound to the event context.
Concurrent requests to session-scoped JavaBeans are always serialized by Seam.
Seam JavaBean components may be instantiated using Component.getInstance() or @In(create=true). They should not be directly instantiated using the new operator.
Message-driven beans may function as a seam component. However, message-driven beans are called quite differently to other Seam components - instead of invoking them via the context variable, they listen for messages sent to a JMS queue or topic.
Message-driven beans may not be bound to a Seam context. Nor do they have access to the session or conversation state of their "caller". However, they do support bijection and some other Seam functionality.
Message-driven beans are never instantiated by the application. They are instantiated by the EJB container when a message is received.
In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed. For entity beans, interception is not required since bijection and context demarcation are not defined. For session beans, we must register an EJB interceptor for the session bean component. We could use an annotation, as follows:
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
But a much better way is to define the interceptor 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
>
All seam components need a name. We can assign a name to a component using the @Name annotation:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.
@Name is not the only way to define a component name, but we always need to specify the name somewhere. If we don't, then none of the other Seam annotations will function.
Whenever Seam instantiates a component, it binds the new instance to a variable in the scope configured for the component that matches the component name. This behavior is identical to how JSF managed beans work, except that Seam allows you to configure this mapping using annotations rather than XML. You can also programmatically bind a component to a context variable. This is useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable. Be careful, though, because through a programmatic assignment, it's possible to overwrite a context variable that has a reference to a Seam component, potentially confusing matters.
For very large applications, and for built-in seam components, qualified component names are often used to avoid naming conflicts.
@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}
We may use the qualified component name both in Java code and in JSF's expression language:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml file:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
All of the built-in Seam components have qualified names but can be accessed through their unqualified names due to the namespace import feature of Seam. The components.xml file included in the Seam JAR defines the following namespaces.
<components xmlns="http://jboss.com/products/seam/components">
<import>org.jboss.seam.core</import>
<import>org.jboss.seam.cache</import>
<import>org.jboss.seam.transaction</import>
<import>org.jboss.seam.framework</import>
<import>org.jboss.seam.web</import>
<import>org.jboss.seam.faces</import>
<import>org.jboss.seam.international</import>
<import>org.jboss.seam.theme</import>
<import>org.jboss.seam.pageflow</import>
<import>org.jboss.seam.bpm</import>
<import>org.jboss.seam.jms</import>
<import>org.jboss.seam.mail</import>
<import>org.jboss.seam.security</import>
<import>org.jboss.seam.security.management</import>
<import>org.jboss.seam.security.permission</import>
<import>org.jboss.seam.captcha</import>
<import>org.jboss.seam.excel.exporter</import>
<!-- ... --->
</components>
When attempting to resolve an unqualified name, Seam will check each of those namespaces, in order. You can include additional namespaces in your application's components.xml file for application-specific namespaces.
We can override the default scope (context) of a component using the @Scope annotation. This lets us define what context a component instance is bound to, when it is instantiated by Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType defines an enumeration of possible scopes.
Some Seam component classes can fulfill more than one role in the system. For example, we often have a User class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role annotation lets us define an additional named role for a component, with a different scope — it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public