SeamFramework.orgCommunity Documentation

Capitolo 6. Eventi, interceptor e gestione delle eccezioni

6.1. Eventi di Seam
6.2. Azioni di pagina
6.3. Parametri di pagina
6.3.1. Mappatura dei parametri di richiesta sul modello
6.4. Parametri di richiesta che si propagano
6.5. Riscrittura URL con parametri di pagina
6.6. Conversione e validazione
6.7. Navigazione
6.8. File granulari per la definizione della navigazione, azioni di pagina e parametri
6.9. Eventi guidati da componenti
6.10. Eventi contestuali
6.11. Interceptor Seam
6.12. Gestione delle eccezioni
6.12.1. Eccezioni e transazioni
6.12.2. Abilitare la gestione delle eccezioni di Seam
6.12.3. Uso delle annotazioni per la gestione delle eccezioni
6.12.4. Uso di XML per la gestione delle eccezioni
6.12.5. Alcune eccezioni comuni

A complemento del modello contestuale a componenti, ci sono due ulteriori concetti base che facilitano il disaccoppiamento estremo, caratteristica distintiva delle applicazioni Seam. Il primo è un forte modello a eventi in cui gli eventi possono essere mappati su degli event listener attraverso espressioni di method binding stile JSF. Il secondo è l'uso pervasivo di annotazioni ed interceptor per applicare concern incrociati ai componenti che implementano la business logic.

Il modello a componenti di Seam è stato sviluppato per l'uso con applicazioni event-driven, specificatamente per consentire lo sviluppo di componenti a granularità fine, disaccoppiati in un modello a eventi a granularità fine. Gli eventi in Seam avvengono in parecchi modi, la maggior parte dei quali è già stata vista:

Tutti questi vari tipi di eventi sono mappati sui componenti Seam tramite espressioni di method binding JSF EL. Per un evento JSF, questo è definito nel template JSF:


<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/>

Per un evento di transizione jBPM, è specificato nella definizione di processo o nella definizione di pageflow:


<start-page name="hello" view-id="/hello.jsp">
    <transition to="hello">
        <action expression="#{helloWorld.sayHello}"/>
    </transition>
</start-page
>

In altri luoghi si possono trovare maggiori informazioni sugli eventi JSF e sugli eventi jBPM. Concentriamoci ora sui due tipi di evento aggiuntivi definiti da Seam.

Un'azione di pagina Seam è un evento che avviene appena prima che la pagina venga renderizzata. Si dichiarano le azioni di pagina in WEB-INF/pages.xml. Si può definire un'azione di pagina anche per un particolare view-id JSF:


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

Oppure si può usare il wildcard * come suffisso a view-id per specificare un'azione che si applica a tutti i view-id che corrispondono al pattern:


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

Si tenga presente che se l'elemento <page> è definito in un descrittore di pagina a granularità fine, l'attributo view-id può essere tralasciato poiché implicito.

Se più azioni di pagina con wildcard corrispondono al corrente view-id, Seam chiamerà tutte le azioni, nell'ordine dal meno al più specifico.

Il metodo di azione di pagine può restituire un esito JSF. Se l'esito è non-null, Seam userà le regole di navigazione definite per andare verso una vista.

Inoltre l'id della vista menzionato nell'elemento <page> non occorre che corrisponda alla vera pagina JSP o Facelets! Quindi si può riprodurre la funzionalità di un tradizionale framework action-oriented come Struts o WebWork usando le azioni di pagina. Questo è abbastanza utile se si vogliono fare cose complesse in risposta a richieste non-faces (per esempio, richieste HTTP GET).

Azioni di pagina multiple o condizionali possono essere specificate usando il tag <action>:


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

Le azioni di pagina vengono eseguite sia su una richiesta iniziale (non-faces) sia su una richiesta di postback (faces). Se si usa l'azione di pagina per caricare i dati, quest'operazione può confliggere con le azioni standard JSF che vengono eseguite su un postback. Un modo per disabilitare l'azione di pagina è di impostare una condizione che risolva a true solo su una richiesta iniziale.


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

Questa condizione consulta ResponseStateManager#isPostback(FacesContext)per determinare se la richiesta è un postback. Il ResponseStateManager viene acceduto usando FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager().

Per risparmiare dalla verbosità dell'API di JSF, Seam offre una condizione predefinita che consente di raggiungere lo stesso risultato con molto meno codice scritto. Si può disabilitare l'azione di pagina su un postback semplicemente impostando il on-postback a false:


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

Per ragioni di retro-compatibilità, il valore di default dell'attributo on-postback è true, sebbene la maggior parte delle volte si usa l'impostazione contraria.

Una richiesta JSF faces (sottomissione di una form) incapsula sia un'"azione" (un metodo di binding) sia "parametri" (valore di binding di input). Un'azione di pagina può anche avere bisogno di parametri!

Poiché le richieste GET sono memorizzabili come segnalibro, i parametri di pagine vengono passati come parametri di richiesta human-readable. (A differenza degli input di form JSF!)

Si possono usare i parametri di pagina con o senza metodo d'azione.

Seam lascia fornire un valore di binding che mappa un parametro di richiesta su un attributo di un oggetto del modello.


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

La dichiarazione <param> è bidirezionale, proprio come un valore di binding per un input JSF:

L'idea essenziale dietro a tutto questo è che comunque si giunge da qualsiasi altra pagina a /hello.jsp (o da /hello.jsp indietro a /hello.jsp), il valore dell'attributo del modello riferito al valore di binding viene "ricordato" senza il bisogno di una conversazione (o altro stato lato server).

Se viene specificato solo l'attributo name allora il parametro di richiesta viene propagato usando il contesto PAGE (non viene mappato alla proprietà del modello).


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

La propagazione dei parametri di pagina è utile in particolare se si vuole costruire pagine CRUD con più livelli master-detail. Si può usare per "ricordare" quale vista c'era precedentemente (es. quando si preme il pulsante Salva) e quale entity si stava modificando.

Tutto questo sembra abbastanza complesso e probabilmente ci si starà chiedendo se un tale costrutto esotico ne valga lo sforzo. In verità l'idea è molto naturale appena ci si abitua. Vale la pena impiegare del tempo per capire questo concetto. I parametri di pagina sono il modo più elegante per propagare lo stato lungo richieste non-faces. In particolare sono comodi per problemi quali schermate di ricerca con pagine di risultato memorizzabili, dove si vuole essere in grado di scrivere il codice applicativo per gestire sia richieste POST sia GET con lo stesso codice. I parametri di pagina eliminano la lista ripetitiva dei parametri di richiesta nella definizione della vista e rendono molto facile la codifica dei redirect.

La riscrittura avviene in base ai pattern di riscrittura trovati per le viste in pages.xml. La riscrittura degli URL di Seam esegue sia la riscrittura dell'URL in entrata sia in uscita basandosi sullo stesso pattern. Ecco un esempio di pattern:



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

In questo caso, qualsiasi richiesta in ingresso per /home verrà inviata a /home.xhtml. Più interessante, qualsiasi link generato che normalmente punterebbe a /home.seam verrà invece riscritto come /home. I pattern di riscrittura corrispondono solo alla porzione di URL prima dei parametri di interrogazione. Quindi, /home.seam?conversationId=13 e /home.seam?color=red corrispondono entrambi in questa regola di riscrittura.

Le regole di riscrittura possono prendere in considerazione dei parametri di interrogazione, come mostrato con le seguenti regole.



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

In questo caso, una richiesta in ingresso per /home/red verrà servita come se fosse una richiesta per /home.seam?color=red. In modo analogo se il colore è un parametro di pagina, un URL in uscita che normalmente sarebbe mostrato come /home.seam?color=blue verrebbe invece mostrato come /home/blue. Le regole vengono processate in ordine, quindi è importante elencare le regole più specifiche prima delle regole generali.

I parametri di default di ricerca di Seam possono essere mappati usando la riscrittura d'URL, consentendo ad un'altra opzione di nascondere il fingerprint di Seam. In quest'esempio, /search.seam?conversationId=13 verrebbe riscritto come /search-13.



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

La riscrittura dell'URL di Seam fornisce una riscrittura semplice e bidirezionale su una base per-vista. Per regole di riscrittura più complesse che coprano componenti non-seam, le applicazioni Seam possono continuare ad usare il org.tuckey URLRewriteFilter o applicare regole di riscrittura nel server web.

La riscrittura dell'URL richiede che il filtro di riscrittura Seam sia abilitato. La configurazione del filtro è discussa in Sezione 30.1.4.3, «Riscrittura dell'URL».

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


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

In alternativa:


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

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


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

In alternativa:


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

Ancora meglio, le annotazioni di Hibernate validator basate sul modello vengono automaticamente riconosciute e validate. Seam fornisce anche un converter di data di default per convertire un valore di parametro stringa in una data e viceversa.

Quando fallisce la conversione del tipo o la validazione, un FacesMessage globale viene aggiunto a FacesContext.

Si possono usare le regole di navigazione standard di JSF definite in faces-config.xml in un'applicazione Seam. Comunque le regole di navigazione hanno un certo numero di limitazioni spiacevoli:

Un altro problema è che la logica di "orchestrazione" viene sparsa tra pages.xml e faces-config.xml. E' meglio unificare questa logica in pages.xml.

Questa regola di navigazione JSF:


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

Può essere riscritto come segue:


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

Ma sarebbe ancora meglio non dover inquinare il componente DocumentEditor con valori di ritorno stringa (gli esiti JSF). Quindi Seam consente di scrivere:


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

Od anche:


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

La prima form valuta un valore di binding per determinare il valore d'esito da impiegare nelle regole. Il secondo approccio ignora l'esito e valuta un valore di binding per ogni possibile regola.

Certamente quando un aggiornamento ha successo si vorrebbe terminare la conversazione corrente. Si può fare ciò in questo modo:


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

Appena terminata la conversazione ogni ulteriore richiesta non saprebbe quale sia il documento di interesse. Si può passare l'id documento come parametro di richiesta che renderebbe la vista memorizzabile come segnalibro:


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

Esiti null sono un caso speciale in JSF. L'esito null viene interpretato come "rivisualizza la pagina". La seguente regola di navigazione cerca esiti non-null, ma non l'esito null:


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

Se si vuole eseguire la navigazione quando avviene un esito null, si usi la seguente form:


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

Il view-id può essere assegnato come espressione JSF EL:


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

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

Se sono presenti molte differenti azioni e parametri di pagina, od anche solo molte regole di navigazione, si vuole quasi sicuramente separare le dichiarazioni in molti file. Si possono definire azioni e parametri per una pagina con id vista /calc/calculator.jsp in una risorsa chiamata calc/calculator.page.xml. L'elemento radice in questo caso è l'elemento <page>, e l'id vista è implicito:


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

I componenti Seam possono interagire semplicemente chiamando gli uni i metodi degli altri. I componenti stateful possono anche implementare il pattern observer/observable. Ma per abilitare i componenti per interagire in un modo più disaccoppiato rispetto a quando i componenti chiamano direttamente i metodi, Seam fornisce eventi component-driven.

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


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

Dove il tipo di evento è solo una stringa arbitraria.

Quando avviene un evento, le azioni registrate per quest'evento verrà chiamato nell'ordine in cui appare in components.xml. Come un componente genera un evento? Seam fornisce un componente predefinito per questo.

@Name("helloWorld")

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

Oppure si può usare un'annotazione.

@Name("helloWorld")

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

Si noti che questo produttore di eventi non ha dipendenza sui consumatori di eventi. L'event listener può adesso essere implementato con nessuna dipendenza sul produttore:

@Name("helloListener")

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

Il binding di metodo definito sopra in components.xml si preoccupare di mappare l'evento al consumatore. Se non si vuole metter mano al file components.xml, si possono usare le annotazioni:

@Name("helloListener")

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

Ci si potrebbe chiedere perché in questa discussione non si è menzionato niente riguardo gli oggetti evento. In Seam non c'è bisogno di un oggetto evento per propagare lo stato tra produttore evento e listener. Lo stato viene mantenuto nei contesti Seam e viene condiviso tra i componenti. Comunque se si vuole passare un oggetto evento, si può:

@Name("helloWorld")

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

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

Seam definisce un numero di eventi predefiniti che l'applicazione può usare per eseguire l'integrazione col framework. Questi eventi sono:

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

EJB 3.0 ha introdotto un modello standard di interceptor per componenti session bean. Per aggiungere un interceptor ad un bean, occorre scrivere una classe con un metodo annotato con @AroundInvoke ed annotare il bean con l'annotazione @Interceptors che specifica il nome della classe interceptor. Per esempio, il seguente interceptor controlla che l'utente sia loggato prima di consentire l'invocazione di un metodo action listener:

public class LoggedInInterceptor {


   @AroundInvoke
   public Object checkLoggedIn(InvocationContext invocation) throws Exception {
   
      boolean isLoggedIn = Contexts.getSessionContext().get("loggedIn")!=null;
      if (isLoggedIn) {
         //l'utente � gi� loggato
         return invocation.proceed();
      }
      else {
         //l'utente non � loggato, prosegui alla pagina di login
         return "login";
      }
   }
}

Per applicare quest'interceptor ad un bean di sessione che agisce come action listener, si deve annotare il bean con @Interceptors(LoggedInInterceptor.class). E' un'annotazione un pò brutta. Seam è basato sul framework interceptor di EJB3 e consente di usare @Interceptors come meta-annotazione per gli interceptor di livello classe (quelli annotati con @Target(TYPE)). Nell'esempio si vuole creare un'annotazione @LoggedIn, come segue:

@Target(TYPE)

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

Ora si può semplicemente annotare il bean action listener con @LoggedIn per applicare l'interceptor.

@Stateless

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

Se l'ordine degli interceptor è importante (solitamente lo è), si possono aggiungere le annotazioni @Interceptor alle classi interceptor per specificare un ordine parziale di interceptor.

@Interceptor(around={BijectionInterceptor.class,

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

Si può anche avere un interceptor "lato client", che giri attorno ad ogni funzionalità predefinita di EJB3:

@Interceptor(type=CLIENT)

public class LoggedInInterceptor
{
    ...
}

Gli interceptor EJB sono stateful, con un ciclo di vita che è lo stesso dei componenti che intercettano. Per gli interceptor che non hanno bisogno di mantenere uno stato, Seam consente di ottenere un'ottimizzazione di performance specificando @Interceptor(stateless=true).

Molte delle funzionalità di Seam sono implementate come set di interceptor predefiniti, includendo gli interceptor chiamati nel precedente esempio. Non è necessario specificare esplicitamente questi interceptor annotando i componenti; esistono per tutti i componenti Seam intercettabili.

Si possono usare gli interceptor Seam anche con i componenti JavaBean, non solo bean EJB3!

EJB definisce l'interception non solo per i metodi di business (usando @AroundInvoke), ma anche per i metodi del ciclo di vita @PostConstruct, @PreDestroy, @PrePassivate e @PostActive. Seam supporta tutti questi metodi del ciclo di vita sia per i componenti sia per gli interceptor, non solo per bean EJB3, ma anche per componenti JavaBean (tranne @PreDestroy che non è significativo per i componenti JavaBean).

JSF è soprendentemente limitato quando si tratta di gestione delle eccezioni. Come parziale soluzione a questo problema, Seam consente di definire come una particolare classe di eccezioni debba essere trattata annotando la classe eccezione o dichiarando l'eccezione in un file XML. Quest'opzione ha il significato di essere combinata con l'annotazione standard EJB3.0 @ApplicationException che specifica se l'eccezione debba causare un rollback della transazione.

Poiché non si possono aggiungere annotazioni a tutte le classi d'eccezione a cui si è interessati, Seam consente di specificare questa funzionalità in pages.xml.


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

L'ultima dichiarazione <exception> non specifica una classe, ed è un cattura-tutto per qualsiasi eccezione per cui la gestione non è altrimenti specifica tramite annotazioni o in pages.xml.

Si può anche usare EL per specificare la view-id a cui reindirizzare.

Si può anche accedere all'istanza dell'eccezione gestita attraverso EL, Seam la mette nel contesto conversazione, es. per accedere al messaggio dell'eccezione:


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

<pages>

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

</pages
>

org.jboss.seam.handledException mantiene l'eccezione annidata che è stata gestita dall'exception handler. L'eccezione più esterna (wrapper) è disponibile, come org.jboss.seam.caughtException.

Se si usa JPA:


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

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

Se si usa il Seam Application Framework:


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

Se si usa Seam Security:


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

E per JSF:


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

Avviene una ViewExpiredException se l'utente invia una pagina quando la sessione è scaduta. Le impostazioni di conversation-required e no-conversation-view-id nel descrittore di pagina Seam, discusse in Sezione 7.4, «Richiedere una conversazione long-running», consentono un controllo più fine sulla scadenza della sessione se si è all'interno di una conversazione.