SeamFramework.orgCommunity Documentation

Capitolo 7. Conversazioni e gestione del workspace

7.1. Il modello di conversazioni di Seam
7.2. Conversazioni innestate
7.3. Avvio di conversazioni con richieste GET
7.4. Richiedere una conversazione long-running
7.5. Usando <s:link> e <s:button>
7.6. Messaggi di successo
7.7. Id di una conversazione naturale
7.8. Creazione di una conversazione naturale
7.9. Redirezione alla conversazione naturale
7.10. Gestione del workspace
7.10.1. Gestione del workspace e navigazione JSF
7.10.2. Gestione del workspace e pageflow jPDL
7.10.3. Lo switcher delle conversazioni
7.10.4. La lista delle conversazioni
7.10.5. Breadcrumbs
7.11. Componenti conversazionali ed associazione ai componenti JSF
7.12. Chiamare concorrenti ai componenti conversazionali
7.12.1. Come si può progettare la nostra applicazione AJAX conversazionale?
7.12.2. Gestione degli errori
7.12.3. RichFaces (Ajax4jsf)

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

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

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

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

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

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


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

O in stile più JSF:


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

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


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

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


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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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


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

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


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

Per risolvere il primo problema, si hanno cinque opzioni:

Certe pagine sono rilevanti solo nel contesto di conversazione long-running. Un modo per "proteggere" tale pagina è richiedere una conversazione long-running come prerequisito per renderizzare la pagina. Fortunatamente, Seam ha un meccanismo predefinito per forzare questa richiesta.

Nel descrittore di pagina di Seam si può indicare che la conversazione corrente sia long-running (o innestata) come requisito per poter renderizzare la pagina, usando l'attributo conversation-required come mostrato:


<page view-id="/book.xhtml" conversation-required="true"/>

Quando Seam determina che questa pagina è richiesta fuori da una conversazione long-running, vengono intraprese le seguenti azioni:

La pagina alternativa è definita nell'attributo no-conversation-view-id in un elemento <pages> nel descrittore di pagina Seam come mostrato:


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

Al momento si può solo definire una sola pagina per l'intera applicazione.

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

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

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

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


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

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


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

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


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

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


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

Si può lasciare lo scope di una conversazione esistente:


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

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


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

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


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

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


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

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

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


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

E' abbastanza comune visualizzare all'utente un messaggio indicante il successo od il fallimento di un'azione. E' conveniente usare FacesMessage di JSF per questo scopo. Sfortunatamente un'azione di successo spesso richiede un redirect del browser, e JSF non propaga i messaggi faces lungo i redirect. Questo rende difficile visualizzare i messaggi nel semplice JSF.

Il componente Seam predefinito con scope conversazione chiamato facesMessages risolve questo problema. (Occorre avere installato il filtro redirect di Seam.)

@Name("editDocumentAction")

@Stateless
public class EditDocumentBean implements EditDocument {
    @In EntityManager em;
    @In Document document;
    @In FacesMessages facesMessages;
    
    public String update() {
        em.merge(document);
        facesMessages.add("Document updated");
    }
}

Qualsiasi messaggio aggiunto a facesMessages viene usato nella fase render response più prossima per la conversazione corrente. Questo funziona anche quando non ci sono conversazioni long-running poiché Seam preserva anche i contesti di conversazioni temporanee lungo i redirect.

Si possono anche includere espressioni JSF EL in un sommario di messaggi faces:

facesMessages.add("Document #{document.title} was updated");

Si possono visualizzare i messaggi nella solita maniera, per esempio:


<h:messages globalOnly="true"/>

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

Redirect facile verso conversazioni esistenti

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

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

URL user friendly

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

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

Molto meglio!

Le conversazioni naturali sono definite in pages.xml:


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

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

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

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

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


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

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


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

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

   @Begin(join = true)

   public void placeBid()

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


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

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


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

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

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


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

Si immagini di voler personalizzare questo per la propria applicazione.

Solo le conversazioni con una descrizione verranno incluse nella lista.

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

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

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

@Name("grid")

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

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

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

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



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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

int total;


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

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


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

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


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

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

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

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

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

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

AJAX_VIEW_EXPIRED=View expired. Please reload the page.

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