SeamFramework.orgCommunity Documentation
La configurazione è un argomento molto noioso ed un passatempo estremamente tedioso. Sfortunatamente sono richieste parecchie linee di XML per integrare Seam con l'implementazione JSF ed il servlet container. Non c'è bisogno di soffermarsi sulle seguenti sezioni; non si dovrà mai scrivere queste cose a mano, poiché basta usare seam-gen per creare ed avviare l'applicazione oppure basta copiare ed incollare il codice dagli esempi di applicazione!
In primo luogo si cominci col guardare alla configurazione base che serve ogni volta che si usa Seam con JSF.
Certamente occorre un servlet faces!
<servlet>
<servlet-name
>Faces Servlet</servlet-name>
<servlet-class
>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup
>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name
>Faces Servlet</servlet-name>
<url-pattern
>*.seam</url-pattern>
</servlet-mapping
>
(Si può sistemare il pattern dell'URL a proprio piacimento.)
In aggiunta, Seam richiede la seguente voce nel file web.xml
:
<listener>
<listener-class
>org.jboss.seam.servlet.SeamListener</listener-class>
</listener
>
Questo listener è responsabile dell'avvio di Seam e della distruzione dei contesti di sessione e di applicazione.
Alcune implementazioni JSF hanno un'implementazione erronea della conservazione dello stato lato server, che interferisce con la propagazione della conversazione di Seam. Se si hanno problemi con la propagazione della conversazione durante l'invio di form, si provi a cambiare impostanto la conservazione lato client. Occorre impostare questo in web.xml
:
<context-param>
<param-name
>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value
>client</param-value>
</context-param
>
C'è una piccola parte grigia nella specifica JSF riguardante la mutabilità dei valore dello stato della vista. Poiché Seam usa lo stato della vista JSF per tornare allo scope PAGE, questo in alcuni casi può diventare un problema. Se si usa la conservazione dello stato lato server con JSF-RI e si vuole che il bean con scope PAGE mantenga l'esatto valore per una data vista di pagina, occorre specificare il seguente parametro di contesto. Altrimenti se un utente usa il pulsante "indietro", un componente con scope PAGE avrà l'ultimo valore se questo non è cambiato nella pagina "indietro". (si veda Spec Issue ). Quest'impostazione non è abilitata di default a causa della performance nella serializzazione della vista JSF ad ogni richiesta.
<context-param>
<param-name
>com.sun.faces.serializeServerState</param-name>
<param-value
>true</param-value>
</context-param
>
Se si vuole seguire il consiglio ed usare Facelets invece di JSP, si aggiungano le seguenti linee a faces-config.xml
:
<application>
<view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
</application
>
E le seguenti linee a web.xml
:
<context-param>
<param-name
>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value
>.xhtml</param-value>
</context-param
>
Se si usa facelets in JBoss AS, si noterà che il logging di Facelets è guasto (i messaggi di log non appaiono nel log del server). Seam fornisce un modo per sistemare questo problema, per usarlo si copi lib/interop/jboss-seam-jul.jar
in $JBOSS_HOME/server/default/deploy/jboss-web.deployer/jsf-libs/
e si includa jboss-seam-ui.jar
nel WEB-INF/lib
dell'applicazione. Le categorie di logging di Facelets sono elencate nella Documentazione per lo sviluppatore Facelets.
Seam Resource Servlet fornisce le risorse usate da Seam Remoting, captchas (si veda il capitolo sulla sicurezza) ed altri controlli JSF UI. Configurare Seam Resource Servlet richiede la seguente riga in web.xml
:
<servlet>
<servlet-name
>Seam Resource Servlet</servlet-name>
<servlet-class
>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name
>Seam Resource Servlet</servlet-name>
<url-pattern
>/seam/resource/*</url-pattern>
</servlet-mapping
>
Seam non ha bisogno di filtri servlet per le operazioni base. Comunque ci sono parecchie caratteristiche che dipendono dall'uso dei filtri. Per facilitare le cose, Seam lascia aggiungere e configurare i filtri servlet così come si configurano gli altri componenti predefiniti di Seam. Per sfruttare questa caratteristica occorre installa un filtro master in web.xml
:
<filter>
<filter-name
>Seam Filter</filter-name>
<filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name
>Seam Filter</filter-name>
<url-pattern
>/*</url-pattern>
</filter-mapping
>
Il filtro master di Seam deve essere il primo filtro specificato in web.xml
. Questo assicura che venga eseguito per primo.
I filtri Seam condividono un numero di attributi comuni, si possono impostare questi in components.xml
in aggiunto ai parametri discussi sotto:
url-pattern
— Usato per specificare quali richieste vengono filtrate, il default è tutte le richieste. url-pattern
è un pattern di stile di Tomcat che consente un suffisso wildcard.
regex-url-pattern
— Usato per specificare quali richieste vengono filtrate, il default è tutte le richieste. regex-url-pattern
è una vera espressione regolare per il percorso di richiesta.
disabled
— Usato per disabilitare il filtro predefinito.
Si noti che i pattern corrispondono al percorso URI della richiesta (si veda HttpServletRequest.getURIPath()
) e che il nome del contesto servlet viene rimosso prima del matching.
L'aggiunta del filtro master abilita i seguenti filtri predefiniti.
Questo filtro fornisce la funzionalità di mappaturadelle eccezioni in pages.xml
(quasi tutte le applicazioni ne hanno bisogno). Inoltre il filtro si preoccupa del rolling back delle transazioni non eseguite quando avviene un'eccezione non catturata. (In accordo alle specifiche Java EE, il web container dovrebbe fare questo in modo automatico, ma noi abbiamo visto che questo comportamento non può essere dato per scontato su tutti gli application server. E certamente non è richiesto sugli engine servlet come Tomcat.)
Di default il filtro di gestione eccezioni processerà tutte le richieste, comunque questo comportamento può essere sistemato aggiungendo una riga <web:exception-filter>
a components.xml
, come mostrato in quest'esempio:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:web="http://jboss.com/products/seam/web">
<web:exception-filter url-pattern="*.seam"/>
</components
>
Questo filtro consente a Seam di propagare il contesto conversazione attraverso i redirect del browser. Intercetta qualsiasi redirect ed aggiunge un parametro di richiesta che specifica l'identificatore della conversazione di Seam.
Il filtro redirect processerà tutte le richieste di default, ma questo comportamento può essere aggiustato in components.xml
:
<web:redirect-filter url-pattern="*.seam"/>
Questo filtro consente aSeam di applicare la riscrittura URL per le viste basate sulla configurazione nel file pages.xml
. Questo filtro non è attivato di default, ma può essere attivato aggiungendo la configurazione in components.xml
:
<web:rewrite-filter view-mapping="*.seam"/>
Il parametro view-mapping
deve corrispondere alla mappatura del servlet definita per Faces Servlet nel file web.xml
. Se omesso, il filtro rewrite assume il pattern *.seam
.
Questa caratteristica è necessaria per usare il controllo JSF di upload dei file. Esso rileva le richieste form multipart e le processa in accordo con la specifica multipart/form-data (RFC-2388). Per sovrascrivere queste impostazioni di default si aggiunga la seguente riga a components.xml
:
<web:multipart-filter create-temp-files="true"
max-request-size="1000000"
url-pattern="*.seam"/>
create-temp-files
— Se impostato a true
, i file caricati vengono scritti in un file temporaneo (invece di essere mantenuti in memoria). Questa può essere una considerazione importante se ci si aspettano upload di file grandi. L'impostazione di default è false
.
max-request-size
— Se la dimensione della richiesta di file upload (determinata leggendo l'intestazione Content-Length
nella richiesta) eccede questo valore, la richiesta verrà annullata. L'impostazione di default è 0 (nessun limite di dimensioni).
Imposta la codifica caratteri dei dati della form inviata.
Questo filtro non è installato di default e richiede una riga in components.xml
per essere abilitato:
<web:character-encoding-filter encoding="UTF-16"
override-client="true"
url-pattern="*.seam"/>
encoding
— La codifica da usare.
override-client
— Se questo è impostato a true
, la codifica della richiesta sarà impostata a ciò che è specificato da encoding
non importa se la richiesta già specifica una codifica o no. Se impostato a false
, la codifica della richiesta sarà impostata solo se la richiesta non specifica già una codifica. L'impostazione di default è false
.
Se RichFaces viene usato nel progetto, Seam installerà il filtro RichFaces Ajax, assicurandosi di installarlo prima di tutti gli altri filtri predefiniti. Non occorre che voi installiate il filtro RichFaces Ajax in web.xml
.
Il filtro RichFaces Ajax è installato solo se i jar di RichFaces sono presenti nel progetto.
Per sovrascrivere le impostazioni di default aggiungere la seguente riga a components.xml
. Le opzioni sono le stesse di quelle specificate nella guida RichFaces Developer:
<web:ajax4jsf-filter force-parser="true"
enable-cache="true"
log4j-init-file="custom-log4j.xml"
url-pattern="*.seam"/>
force-parser
— forza tutte le pagine JSF ad essere validate dal controllore di sintassi XML di RichFaces. Se false
, solo le risposte AJAX vengono validate e convertite in XML ben-formato. Impostando force-parser
a false
si migliorano le performance, ma può portare ad imperfezioni visuali durante gli aggiornamenti AJAX.
enable-cache
— abilita il caching delle risorse generate dal framework (es. javascript, CSS, immagini, ecc). Sviluppando javascript o CSS personalizzati, impostare a true previene il browser dal caching delle risorse.
log4j-init-file
— è usato per impostare il logging per applicazione. Deve essere fornito un path al file di configurazione log4j.xml, relativo al contesto dell'applicazione web.
Questo filtro aggiunge il nome dell'utente autenticato al contesto diagnostico mappato di loj4j affinché possa essere incluso nell'output formattato del log se desiderato, aggiungendo %X{username} al pattern.
Di default il filtro di logging processerà tutte le richieste, comunque questo comportamento può essere sistemato aggiungendo l'entry <web:logging-filter>
a components.xml
, come mostrato in quest'esempio:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:web="http://jboss.com/products/seam/web">
<web:logging-filter url-pattern="*.seam"/>
</components
>
Le richieste inviate direttamente a servlet diversi dal servlet JSF non vengono processate attraverso il ciclo di vita JSF, in questo modo Seam fornisce un filtro servlet che può essere applicato ad altri servlet che necessitano dell'accesso ai componenti Seam.
Questo filtro consente ai servlet personalizzati di interagire con i contesti Seam. Questo imposta i contesti Seam all'inizio di ogni richiesta e li elimina alla fine della richiesta. Occorre assicurarsi che il filtro non venga mai applicato al FacesServlet
JSF.
Questo filtro non è installato di default e richiede una riga in components.xml
per essere abilitato:
<web:context-filter url-pattern="/media/*"/>
Il filtro del contesto si aspetta di trovare l'id della conversazione di ogni contesto di conversazione in un parametro di richiesta nominato conversationId
. Occorre assicurarsi che questo venga inviato nella richiesta.
Si è responsabili della propagazione di ogni nuovo id della conversazione attraverso il client. Seam espone l'id di conversazione come proprietà del componente predefinito conversation
.
Seam può installare i filtri per voi, consentendo di specificare dove debba essere collocato il filtro nella catena (la specifica servlet non fornisce un ordine ben definito se si specificano i filtri in web.xml
). Si aggiunga l'annotazione @Filter
al componente Seam (che deve implementare javax.servlet.Filter
):
@Startup
@Scope(APPLICATION)
@Name("org.jboss.seam.web.multipartFilter")
@BypassInterceptors
@Filter(within="org.jboss.seam.web.ajax4jsfFilter")
public class MultipartFilter extends AbstractFilter {
Aggiungere l'annotazione @Startup
significa che il componente è disponibile durante lo startup di Seam; la bijection non è disponibile qua (@BypassInterceptors
); ed il filtro dovrebbe essere più in fondo alla catena rispetto al filtro RichFaces (@Filter(within="org.jboss.seam.web.ajax4jsfFilter")
).
Nelle applicazioni Seam i componenti EJB hanno una certa dualità, poiché sono gestiti da entrambi: il container EJB e Seam. In verità Seam risolve i riferimenti ai componenti EJB, gestisce il ciclo di vita dei componenti bean con sessione stateful, e partecipa anche ad ogni chiamata di metodo via interceptor. Iniziamo con la configurazione della catena interceptor di Seam.
Occorre applicare SeamInterceptor
ai componenti EJB di Seam. Quest'interceptor delega ad un set di interceptor predefiniti lato server che gestiscono i concern quali la bijection, la demarcazione delle conversazioni ed i segnali del processo di business. Il modo più semplice per fare questo in tutta l'applicazione è aggiungere la seguente configurazione per l'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>
Seam necessita di sapere dove trovare i session bean in JNDI. Un modo per farlo è specificare l'annotazione @JndiName
su ogni componente session bean. Comunque questa modalità è abbastanza noiosa. Un approccio migliore è specificare un pattern che Seam usa per calcolare il nome JNDI dal nome EJB. Sfortunatamente nella specifica EJB3 non è definita una mappatura standard al JNDI globale, quindi questa mappatura è specificata dal venditore (e può dipendere anche dalle proprie convenzioni di nome). Solitamente si specifica quest'opzione in components.xml
.
Per JBoss AS ilseguente pattern è corretto:
<core:init jndi-name="earName/#{ejbName}/local" />
In questo caso earName
è il nome dell'EAR in cui viene deployato il bean, Seam sostituisce #{ejbName}
con il nome dell'EJB ed il segmento finale rappresenta il tipo di interfaccia (locale o remota).
Fuori dal contesto di un EAR (quando si usa il container JBoss Embeddable EJB3) il primo segmento viene ignorato poiché non c'è alcun EAR, rimanendo quindi con il seguente pattern:
<core:init jndi-name="#{ejbName}/local" />
Come questi nomi JNDI vengono risolti e come localizzare un componente EJB potrebbe apparire a questo punto un pò una questione di magia, quindi indaghiamo meglio i dettagli. Innanzitutto vediamo come i componenti EJB entrano in JNDI.
Le persone di JBoss non si preoccupano molto di XML. Quindi quando hanno progettato JBoss AS, hanno deciso che i componenti EJB avrebbero visto assegnarsi automaticamente un nome JNDI globale, usando il pattern appena descritto (cioè nome EAR/nome EJB/tipo interfaccia). Il nome EJB è il primo valore non vuoto della seguente lista:
Il valore dell'elemento <ejb-name>
in ejb-jar.xml
Il valore dell'attributo name
nell'annotazione @Stateless o @Stateful
Il semplice nome della classe bean
Guardiamo un esempio. Si assuma di avere definiti il seguente bean EJB ed un'interfaccia.
package com.example.myapp;
import javax.ejb.Local;
@Local
public class Authenticator
{
boolean authenticate();
}
package com.example.myapp;
import javax.ejb.Stateless;
@Stateless
@Name("authenticator")
public class AuthenticatorBean implements Authenticator
{
public boolean authenticate() { ... }
}
Assumendo che la classe del bean EJB sia deployata in un'applicazione EAR chiamata myapp, il nome JNDI globale myapp/AuthenticatorBean/local verrà assegnato a lei in JBoss AS. Come visto, si può fare riferimento a questo componente EJB come componente Seam con il nome authenticator
e Seam si preoccuperà di trovarlo in JNDI secondo il pattern JNDI (o l'annotazione @JndiName
).
Ma cosa dire rispetto ai restanti application server? In accordo alla specifica Java EE, alla quale la maggior parte dei venditori cerca di aderire in modo religioso, si deve dichiarare un riferimento EJB per il proprio EJB affinché gli venga assegnato un nome JNDI. Questo richiede un pò di XML. Significa che sta a voi stabilire una convenzione di nomi JNDI per poter sfruttare il pattern JNDI di Seam. Si potrebbe ritenere buona e usare la convenzione JBoss.
Ci sono due posti dove poter definire il riferimento EJB usando Seam su application server non-JBoss. Se si cercheranno i componenti EJB di Seam attraverso JSF (in una vista JSF o in un action listener JSF) od un componente JavaBean di Seam, allora occorre dichiarare il riferimento EJB in web.xml. Ecco qua il riferimento EJB per il componente d'esempio appena mostrato:
<ejb-local-ref>
<ejb-ref-name
>myapp/AuthenticatorBean/local</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local
>org.example.vehicles.action.Authenticator</local>
</ejb-local-ref>
Questo riferimento coprirà la maggior parte degli usi dei componenti in un'applicazione Seam. Comunque se si vuole essere in grado di iniettare un componente EJB di Seam in un altro componente usando @In
, occorre definire questo riferimento EJB in un'altra posizione. Questa volta deve essere definito in ejb-jar.xml, ed è un pò più complicato.
Dentro il contesto di una chiamata di metodo EJB, occorre avere a che fare con un contesto JNDI protetto. Quando Seam tenta di trovare un altro componente EJB Seam per soddisfare un punto d'iniezione definito con @In
, il fatto che Seam lo trovi oppure no dipende dal fatto che esista un riferimento EJB in JNDI. In senso letterale non si può semplicemente risolvere i nomi JNDI a proprio piacimento. Occorre definire esplicitamente i riferimenti. Fortunatamente JBoss ha capito come questo sarebbe aggravante per lo sviluppatore e quindi tutte le versioni di JBoss registrano automaticamente gli EJB cosicché siano sempre disponibili in JNDI, sia nel web container sia nell'EJB container. In definitiva se si usa JBoss, si possono saltare i prossimi paragrafi. Comunque, se si usa GlassFish, si presti molta attenzione.
Per gli application server che aderiscono testardamente alla specifica EJB, i riferimenti EJB devono sempre essere definititi esplicitamente. Ma diversamente dal contesto web, dove un singolo riferimento di una risorsa copre tutti gli usi di EJB, non si possono dichiarare i riferimenti EJB in modo globale nel container EJB. Invece si devono specificare le risorse JNDI per un dato componente EJB una per una.
Si assuma di avere un EJB chiamato RegisterAction (nome che viene risolto usando i tre passi menzionati prima). Questo EJB ha la seguente injection Seam:
@In(create = true)
Authenticator authenticator;
Per fare funzionare quest'injection, il link deve essere messo nel file ejb-jar.xml come mostrato:
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name
>RegisterAction</ejb-name>
<ejb-local-ref>
<ejb-ref-name
>myapp/AuthenticatorAction/local</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local
>com.example.myapp.Authenticator</local>
</ejb-local-ref>
</session>
</enterprise-beans>
...
</ejb-jar>
Si noti che i contenuti di <ejb-local-ref>
sono identici a ciò che viene definito in web.xml. Ciò che viene fatto è portare il riferimento nel contesto EJB dove può essere usato dal bean RegisterAction. Occorre aggiungere uno di questi riferimenti per qualsiasi injection di componente EJB Seam in un altro componente EJB Seam con @In
. (Vedere l'esempio di setup di jee5/booking).
E riguardo @EJB
? E' vero che si può iniettare un EJB in un altro usando @EJB
. Comunque, facendo così, si sta iniettando il riferimento EJB piuttosto che l'istanza del componente EJB Seam. In questo caso, alcune funzionalità di Seam funzioneranno, mentre altre no. Questo perchè l'interceptor di Seam viene invocato ad ogni chiamata di metodo in un componente EJB. Ma questo invoca solo la catena dell'interceptor Seam lato server. Ciò che viene persa è la gestione dello stato di Seam e la catena dell'interceptor lato client. Gli interceptor lato client gestiscono i concern quali sicurezza e concorrenza. Inoltre, quando si inietta un SFSB, non c'è garanzia che il SFSB venga associato alla conversazione o sessione attiva, qualunque sia il caso. Quindi si inietterà il componente EJB Seam usando @In
.
Vediamo ora come vengono definiti ed usati i nomi JNDI. La lezione riguarda alcuni application server, come GlassFish, con cui occorre specificare i nomi JNDI esplicitamente per tutti i componenti EJB, ed alcune volte in modo doppio! Ed anche se si segue la stessa convenzione di nomi di JBoss AS, il pattern JNDI in Seam deve essere alterato. Per esempio in GlasshFish ai nomi JNDI viene automaticamente aggiunto il prefisso java:comp/env, e quindi occorre definire il pattern JNDI come segue:
<core:init jndi-name="java:comp/env/earName/#{ejbName}/local" />
Infine parliamo di transazioni. In un ambiente EJB3 si raccomanda l'uso di un componente speciale predefinito per la gestione delle transazioni, che sia pienamente consapevole delle transazioni del container e possa correttamente processare gli eventi di successo legati alle transazioni registrato con il componente Events
. Se non viene aggiunta questa linea al file components.xml
, Seam non saprà quando finiscono le transazioni gestite dal container:
<transaction:ejb-transaction/>
C'è un ulteriore punto finale da sapere. Occorre mettere un file seam.properties
, META-INF/seam.properties
o META-INF/components.xml
in qualsiasi archivio in cui vengono deployati componenti Seam (anche un file vuoto). All'avvio Seam scansionerà ogni archivio con il file seam.properties
per cercare componenti seam.
Nel file WAR occorre mettere il file seam.properties
nella directory WEB-INF/classes
qualora si abbiano componenti Seam da includere.
E' questo il motivo per cui tutti gli esempi Seam hanno un file seam.properties
vuoto. Non si può cancellare questo file ed attendersi che tutto funzioni!
Si potrebbe ritenere che ciò sia stupido e chiedersi quale stupida razza di designer di framework farebbe influenzare il proprio software da un file vuoto? Questo è un workaroundper una limitazione della JVM — se non venisse usato questo meccanismo la migliore opzione sarebbe quella di obbligarvi ad elencare esplicitamente in components.xml
ciascun componente, proprio come fanno gli altri framework! Riflettete su quale sia il modo migliore.
Seam viene assemblato e configurato con Hibernate in qualità di provider JPA. Se si vuole usare un diverso provider JPA occorre dirlo a seam
.
La configurazione del provider JPA sarà più semplice in futuro e non richiederà cambiamenti nella configurazione, amenoché non si aggiunga un'implementazione personalizzata del provider di persistenza.
Comunicare a Seam di usare un altro provider JPA può essere realizzato in uno dei due modi:
Si aggiorni il components.xml
della propria applicazione affinché PersistenceProvider
abbia la precedenza sulla versione Hibernate. Semplicemente si aggiunga al file:
<component name="org.jboss.seam.persistence.persistenceProvider"
class="org.jboss.seam.persistence.PersistenceProvider"
scope="stateless">
</component
>
Se si vogliono sfruttare le caratteristiche nonstandard del proprio provider JPA occorre scrivere la propria implementazione di PersistenceProvider
. Si usa HibernatePersistenceProvider
come punto di partenza (si ricordi di dare qualcosa alla comunità :). Quindi occorrerà dire a seam
di usarlo come prima.
<component name="org.jboss.seam.persistence.persistenceProvider"
class="org.your.package.YourPersistenceProvider">
</component
>
Ciò che rimane da fare è aggiornare il file persistence.xml
come la giusta classe del provider e quelle proprietà che il provider richiede. Non dimenticare di impacchettare nell'applicazione i file jar del provider se richiesti.
Se si esegue il software in ambiente Java EE 5, questa è tutta la configurazione richiesta per usare Seam!
Una volta impacchettato assieme tutte queste cose in un EAR, la struttura dell'archivio apparirà così:
my-application.ear/ jboss-seam.jar lib/ jboss-el.jar META-INF/ MANIFEST.MF application.xml my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jsf-facelets.jar jboss-seam-ui.jar login.jsp register.jsp ... my-application.jar/ META-INF/ MANIFEST.MF persistence.xml seam.properties org/ jboss/ myapplication/ User.class Login.class LoginBean.class Register.class RegisterBean.class ...
Si dovrebbe dichiarare jboss-seam.jar
come modulo ejb in META-INF/application.xml
; jboss-el.jar
dovrebbe essere collocato nella directory lib dell'EAR (mettendolo nel classpath dell'EAR).
Se si vuole usare jBPM o Drools, occorre includere i jar necessari nella directory lib di EAR.
Se si vuole usare facelets (consigliato), occorre includere jsf-facelets.jar
nella directory WEB-INF/lib
del WAR.
Se si vuole usare la libreria dei tag di Seam (come per la maggior parte delle applicazioni), occorre includere jboss-seam-ui.jar
nella directory WEB-INF/lib
del WAR. Se si vuole usare la libreria PDF o quella email, occorre mettere jboss-seam-pdf.jar
o jboss-seam-mail.jar
in WEB-INF/lib
.
Se si vuole usare la pagina di debug di Seam (funziona solo per applicazioni con facelets), occorre includere jboss-seam-debug.jar
nella directory WEB-INF/lib
del WAR.
Seam porta con sé parecchi esempi di applicazioni che sono deployate in un container Java EE che supporta EJB 3.0.
Ci piacerebbe aver terminato l'argomento configurazione, ma sfortunatamente siamo solo ad un terzo. Se si è troppo sopraffatti da questo tedioso argomento, si può saltare alla prossima sezione e tornare qua più tardi.
Seam è utile anche se non si è ancora pronti per il grande salto in EJB3.0. In questo caso si usa Hibernate3 o JPA invece della persistenza EJB3.0, ed i JavaBean invece dei bean di sessione. Non si avranno a disposizione alcune funzionalità carine per i session bean, ma sarà molto semplice migrare a EJB3.0 quando si sarà pronti ed allora si potrà sfruttare l'architettura unica di Seam per la gestione dichiarativa dello stato.
I componenti JavaBean di Seam non forniscono la demarcazione dichiarativa delle transazioni come fanno i session bean. Si possono gestire manualmente le transazioni usando UserTransaction
di JTA od in modo dichiarativo usando l'annotazione @Transactional
di Seam. Ma la maggior parte delle applicazioni userà solo le transazioni gestite da Seam con Hibernate ed i JavaBean.
La distribuzione Seam include una versione dell'esempio booking che usa Hibernate3 e JavaBean invece di EJB3, ed un'altra versione che usa JPA e JavaBean. Queste applicazioni d'esempio sono pronte per essere deployate in ogni application server J2EE.
Seam avvierà un SessionFactory
di Hibernate dal file hibernate.cfg.xml
se si installa un componente predefinito:
<persistence:hibernate-session-factory name="hibernateSessionFactory"/>
Occorre anche configurare una sessione gestita se si vuole disponibile via injection una Session
di Hibernate gestita da Seam.
<persistence:managed-hibernate-session name="hibernateSession"
session-factory="#{hibernateSessionFactory}"/>
Seam avvierà un EntityManagerFactory
JPA dal file persistence.xml
se è stato installato il seguente componente predefinito:
<persistence:entity-manager-factory name="entityManagerFactory"/>
Occorre anche configurare un contesto di persistenza gestito se si vuole avere disponibile via injection un EntityManager
JPA gestito da Seam.
<persistence:managed-persistence-context name="entityManager"
entity-manager-factory="#{entityManagerFactory}"/>
Si può impacchettare l'applicazione come WAR nella seguente struttura:
my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jboss-seam.jar jboss-seam-ui.jar jboss-el.jar jsf-facelets.jar hibernate3.jar hibernate-annotations.jar hibernate-validator.jar ... my-application.jar/ META-INF/ MANIFEST.MF seam.properties hibernate.cfg.xml org/ jboss/ myapplication/ User.class Login.class Register.class ... login.jsp register.jsp ...
Se si vuole fare ildeploy di Hibernate in un ambiente non EE come Tomcat o TestNG, occorre un pò di lavoro.
E' possibile usare Seam completamente fuori dall'ambiente EE. In questo caso serve istruire Seam come gestire le transazioni gestire, poiché non sarà disponibile JTA. Se si usa JTA, si può dire a Seam di usare le transazioni resource-local di JTA, cioè EntityTransaction
, in questo modo:
<transaction:entity-transaction entity-manager="#{entityManager}"/>
Se si usa Hibernate, si può dire a Seam di usare l'API transaction di Hibernate in questo modo:
<transaction:hibernate-transaction session="#{session}"/>
Certamente occorre definire un datasource.
Un'alternativa migliore è usare JBoss Embedded per accedere alle API EE.
JBoss Embedded consente di eseguire i componenti EJB3 fuori dal contesto dell'application server Java EE 5. Questo è utile specialmente, ma non solo, per il testing.
L'applicazione d'esempio booking include la suite d'integrazione per TestNG che esegue JBoss Embedded via SeamTest
.
L'applicazione d'esempio booking può essere deployata via Tomcat.
Embedded JBoss deve essere installato in Tomcat per le eseguire correttamente le applicazioni Seam. Embedded JBoss gira con JDK 5 o JDK 6 (si veda Sezione 42.1, «Dipendenze JDK» per i dettagli con JDK 6). Embedded JBoss può essere scaricato qua. Il processo di installazione di Embedded JBoss in Tomcat 6 è abbastanza semplice. Innanzitutto bisogna copiare i jar di Embedded JBoss ed i file di configurazione in Tomcat.
Copiare tutti i file e le directory di Embedded JBoss bootstrap
e la directory lib
, tranne il file jndi.properties
, nella directory di Tomcat lib
.
Rimuovere il file annotations-api.jar
dalla directory Tomcat lib
.
Poi devono essere aggiornati due file di configurazione per poter aggiungere funzionalità specifiche di JBoss Embedded.
Aggiungere il listener Embedded JBoss EmbeddedJBossBootstrapListener
a conf/server.xml
. Deve apparire dopo tutti gli altri listener nel file:
<Server port="8005" shutdown="SHUTDOWN">
<!-- Comment these entries out to disable JMX MBeans support used for the
administration web application -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener" />
<!-- Add this listener -->
<Listener className="org.jboss.embedded.tomcat.EmbeddedJBossBootstrapListener" />
La scansione del file WAR può essere abilitata aggiungendo il listener WebinfScanner
in conf/context.xml
:
<Context>
<!-- Default set of monitored resources -->
<WatchedResource
>WEB-INF/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<!-- Add this listener -->
<Listener className="org.jboss.embedded.tomcat.WebinfScanner" />
</Context
>
Se si usa Sun JDK 6, occorre impostare l'opzione Java sun.lang.ClassLoader.allowArraySyntax
a true
nella variabile d'ambiente JAVA_OPTS usata dallo script di avvio Catalina (catalina.bat in Windows o catalina.sh in Unix).
Aprire in un editor lo script appropriato per il proprio sistema operativo. Aggiungere una nuova linea immediatamente sotto i commenti in cima al file dove verrà definita la variabile d'ambiente JAVA_OPTS. Su Windows, usare la seguente sintassi:
set JAVA_OPTS=%JAVA_OPTS% -Dsun.lang.ClassLoader.allowArraySyntax=true
In Unix, usare invece questa sintassi:
JAVA_OPTS="$JAVA_OPTS -Dsun.lang.ClassLoader.allowArraySyntax=true"
Per ulteriorio opzioni di configurazione, si prega di vedere l'integrazione con Embedded JBoss Tomcat wiki entry.
La struttura dell'archivio di un deploy basato su WAR per un servlet engine come Tomcat appare in questo modo:
my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jboss-seam.jar jboss-seam-ui.jar jboss-el.jar jsf-facelets.jar jsf-api.jar jsf-impl.jar ... my-application.jar/ META-INF/ MANIFEST.MF persistence.xml seam.properties org/ jboss/ myapplication/ User.class Login.class LoginBean.class Register.class RegisterBean.class ... login.jsp register.jsp ...
La maggior parte delle applicazioni d'esempio può essere deployata in Tomcat eseguendo ant deploy.tomcat
.
L'integrazione jBPM di Seam non è installata di default, quindi occorre abilitare jBPM installando il componente predefinito. Serve anche elencare esplicitamente le definizioni di processo ed i pageflow. In components.xml
:
<bpm:jbpm>
<bpm:pageflow-definitions>
<value
>createDocument.jpdl.xml</value>
<value
>editDocument.jpdl.xml</value>
<value
>approveDocument.jpdl.xml</value>
</bpm:pageflow-definitions>
<bpm:process-definitions>
<value
>documentLifecycle.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
Non servono ulteriori configurazioni se si usano solo i pageflow. Se si hanno definizioni di processi di business, bisogna fornire una configurazione jBPM ed una configurazione Hibernate per jBPM. La demo Seam DVD Store include i file d'esempio jbpm.cfg.xml
e hibernate.cfg.xml
che funzionano con Seam:
<jbpm-configuration>
<jbpm-context>
<service name="persistence">
<factory>
<bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled"
><false/></field>
</bean>
</factory>
</service>
<service name="tx" factory="org.jbpm.tx.TxServiceFactory" />
<service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
<service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
<service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
<service name="authentication"
factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" />
</jbpm-context>
</jbpm-configuration
>
La cosa più importante da notare è che il controllo della transazione jBPM è disabilitato. Seam o EJB3 dovrebbero controllare le transazioni JTA.
Non c'è ancora un formato di impacchettamento ben-definito per la configurazione jBPM ed i file di definizione processo/pageflow. Negli esempi Seam si è deciso di impacchettare semplicemente tutti questi file nella directory radice dell'EAR. In future, si progetterà probabilmente un formato standard di impacchettamento. Quindi l'EAR appare così:
my-application.ear/ jboss-seam.jar lib/ jboss-el.jar jbpm-3.1.jar META-INF/ MANIFEST.MF application.xml my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jsf-facelets.jar jboss-seam-ui.jar login.jsp register.jsp ... my-application.jar/ META-INF/ MANIFEST.MF persistence.xml seam.properties org/ jboss/ myapplication/ User.class Login.class LoginBean.class Register.class RegisterBean.class ... jbpm.cfg.xml hibernate.cfg.xml createDocument.jpdl.xml editDocument.jpdl.xml approveDocument.jpdl.xml documentLifecycle.jpdl.xml
E' molto importante che il timeout per i bean di sessione stateful sia impostato ad un tempo maggiore del timeout per le sessioni HTTP, altrimenti SFSB può andare in timeout primache la sessione HTTP sia terminata. JBoss AS ha un timeout di default di 30 minuti, che è impostato nel file server/default/conf/standardjboss.xml
(sostituire defaultcon la propria configurazione).
Il timeout di default di SFSB può essere impostato modificando il valore di max-bean-life
nella configurazione della cache LRUStatefulContextCachePolicy
:
<container-cache-conf>
<cache-policy
>org.jboss.ejb.plugins.LRUStatefulContextCachePolicy</cache-policy>
<cache-policy-conf>
<min-capacity
>50</min-capacity>
<max-capacity
>1000000</max-capacity>
<remover-period
>1800</remover-period>
<!-- SFSB timeout in seconds; 1800 seconds == 30 minutes -->
<max-bean-life
>1800</max-bean-life
>
<overager-period
>300</overager-period>
<max-bean-age
>600</max-bean-age>
<resizer-period
>400</resizer-period>
<max-cache-miss-period
>60</max-cache-miss-period>
<min-cache-miss-period
>1</min-cache-miss-period>
<cache-load-factor
>0.75</cache-load-factor>
</cache-policy-conf>
</container-cache-conf
>
Il timeout di default per la sessione HTTP può essere modificato in server/default/deploy/jbossweb-tomcat55.sar/conf/web.xml
per JBoss 4.0.x, o in server/default/deploy/jboss-web.deployer/conf/web.xml
per JBoss 4.2.x o successivi. La seguente riga in questo file controlla il timeout di default per la sessione di tutte le applicazioni web:
<session-config>
<!-- HTTP Session timeout, in minutes -->
<session-timeout
>30</session-timeout>
</session-config
>
Per sovrascrivere questo valore per la propria applicazione, si includa semplicemente questa riga nel proprio web.xml
.
Se si vogliono eseguire le applicazioni in un portlet, si guardi JBoss Portlet Bridge, un'implementazione di JSR-301 che supporta JSF con i portlet, con estensioni per Seam e RichFaces. Si veda http://labs.jboss.com/portletbridge.
Seam scansiona all'avvio tutti i jar contenenti /seam.properties
, /META-INF/components.xml
o /META-INF/seam.properties
. Per esempio, tutte le classi annotate con @Name
vengono registrate da Seam come componenti Seam.
Si potrebbe volere fare gestire a Seam delle risorse personalizzate. Un comune caso d'uso è gestire una specifica annotazione, Seam fornisce un supporto specifico per questo. Innanzitutto, dire a Seam quali annotazioni gestire in /META-INF/seam-deployment.properties
:
# A colon-separated list of annotation types to handle org.jboss.seam.deployment.annotationTypes=com.acme.Foo:com.acme.Bar
Poi durante l'avvio dell'applicazione si può comunicare con tutte le classi annotate con @Foo
:
@Name("fooStartup") @Scope(APPLICATION) @Startup public class FooStartup { @In("#{deploymentStrategy.annotatedClasses['com.acme.Foo']}") private Set<Class<Object > > fooClasses; @In("#{hotDeploymentStrategy.annotatedClasses['com.acme.Foo']}") private Set<Class<Object > > hotFooClasses; @Create public void create() { for (Class clazz: fooClasses) { handleClass(clazz); } for (Class clazz: hotFooClasses) { handleClass(clazz); } } public void handleClass(Class clazz) { // ... } }
Si può gestire qualsiasi risorsa. Per esempio, si processano i file con estensione .foo.xml
. Per fare questo occorre scrivere un deployment handler personalizzato:
public class FooDeploymentHandler implements DeploymentHandler { private static DeploymentMetadata FOO_METADATA = new DeploymentMetadata() { public String getFileNameSuffix() { return ".foo.xml"; } }; public String getName() { return "fooDeploymentHandler"; } public DeploymentMetadata getMetadata() { return FOO_METADATA; } }
Qua viene costruita una lista di file con il suffisso .foo.xml
.
Poi occorre registrare il deployment handler con Seam in /META-INF/seam-deployment.properties
. Si possono registrare più deployment handler usando una lista separata da virgola.
# For standard deployment org.jboss.seam.deployment.deploymentHandlers=com.acme.FooDeploymentHandler # For hot deployment org.jboss.seam.deployment.hotDeploymentHandlers=com.acme.FooDeploymentHandler
Seam utilizza internamente dei deployment handler per installare componenti e namespace. Si può facilmente accedere ad un deployment handler durante l'avvio di un componente con scope APPLICATION
:
@Name("fooStartup") @Scope(APPLICATION) @Startup public class FooStartup { @In("#{deploymentStrategy.deploymentHandlers['fooDeploymentHandler']}") private FooDeploymentHandler myDeploymentHandler; @In("#{hotDeploymentStrategy.deploymentHandlers['fooDeploymentHandler']}") private FooDeploymentHandler myHotDeploymentHandler; @Create public void create() { for (FileDescriptor fd: myDeploymentHandler.getResources()) { handleFooXml(fd); } for (FileDescriptor f: myHotDeploymentHandler.getResources()) { handleFooXml(fd); } } public void handleFooXml(FileDescriptor fd) { // ... } }