SeamFramework.orgCommunity Documentation

第6章 イベント、インタセプタ、例外処理

6.1. Seamイベント
6.2. ページアクション
6.3. ページパラメータ
6.3.1. 要求パラメータからモデルへのマッピング
6.4. 要求パラメータの伝播
6.5. ページパラメータを伴うURL書き換え
6.6. 変換と妥当性検証
6.7. ナビゲーション
6.8. ナビゲーション、ページアクション、パラメータの定義用に細分化したファイル
6.9. コンポーネント駆動イベント
6.10. コンテキスト依存イベント
6.11. Seamインタセプタ
6.12. 例外を管理する
6.12.1. 例外およびトランザクション
6.12.2. Seam 例外処理を有効にする
6.12.3. 例外処理に対してアノテーションを使用する
6.12.4. 例外処理に XML を使用する
6.12.5. 共通の例外

コンテキスト依存コンポーネントモデルを補完するものとして、Seamアプリケーションの特徴となっている極度の疎結合を促進させる二つの基本概念が存在します。 最初のものは、イベントがJSFライクなメソッドバインディング式(method binding expression) を通じてイベントリスナーへマップできるような強力なイベントモデルです。 二番目のものは、ビジネスロジックを実装するコンポーネントに対して横断的関心事 (cross-cutting concerns) を適用するためにアノテーションやインタセプタを広範囲に使用しているということです。

Seamコンポーネントモデルはイベント駆動アプリケーション で使うために開発されました。特に、細粒度イベントモデル (fine-grained eventing model) での細粒度かつ疎結合コンポーネント開発を可能にします。 Seamでのイベントは、すでにご存知のように、いくつかのタイプがあります。

これらの多様なイベントすべてはJSF ELメソッドバインディング式を通じてSeamコンポーネントへマップされます。JSFイベントは、JSFテンプレートで次のように定義されます。


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

jBPM遷移イベントは、jBPMプロセス定義またはページフロー定義で規定されます。


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

JSF イベントや jPBM イベントの詳細については本ガイド以外でも見つけることができるので、 ここでは Seam によって定義される別の二種類のイベントについて見ていきます。

Seamページアクションはページのレンダリングの直前に発生するイベントです。 ページアクションはWEB-INF/pages.xmlで宣言します。 特定のJSFビューidのためのページアクションを定義することも可能です。


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

あるいは、 * ワイルドカードを使ってパターンに一致するすべてのビュー IDを指定することもできます。


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

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

複数のワイルドカード化されたページアクションがカレントビューidに一致するなら、 Seamは曖昧な指定から明確な指定への順 (least-specific to most-specific) で、 それらすべてのアクションを呼び出します。

ページアクションのメソッドはJSF outcomeを返すことができます。もしも、そのoutcome がnullでなければ、Seamはビューをナビゲートするためその定義済みナビゲーション規則を使います。

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

複数または条件付きのページアクションは<action>タグを使って指定できます。


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

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


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

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

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


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

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

JSF faces 要求 (フォーム送信) は「アクション」 (メソッドバインディング) と「パラメータ」 (入力値バインディング) の両方をカプセル化します。 ページアクションにもパラメータが必要かもしれません。

GET 要求はブックマーク可能なので、 ページパラメータは人間が読める要求パラメータとして引き渡されます (JSF フォーム入力とは異なるもの)。

アクションメソッドを指定する、あるいは指定しないページパラメータを使うことができます。

Seamでは、名前付き要求パラメータをモデルオブジェクトの属性を対応させる値バインディングが可能です。


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

<param>宣言は双方向で、まさにJSF入力の値バインディングのようです。

この背後にある本質的な考えは、他の任意のページから /hello.jsp への (または /hello.jsp から /hello.jsp へ戻るような) 遷移があるにもかかわらず、 値バインディングで参照されるモデル属性の値は対話 (または他のサーバ側の状態) を必要とせずに「記録されている」ということです。

もし name 属性が指定されていたら、要求パラメータは PAGE コンテキストを使って伝播します(モデルプロパティへはマッピングされません)。


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

ページパラメータの伝播は、マスターから詳細画面に遷移するようなマルチレイヤのCRUDページを作成したいときには特に便利です。それは以前操作していた(例えば、保存ボタンを押したときの)ビューや編集していたビューを「覚えておく」のに使えます。

これらすべてはかなり複雑に聞こえますし、そのような変わった概念を努力して使う価値があるのかと疑問に思うことでしょう。実際は、そのアイディアは一旦「理解」してしまえばとても自然なものです。時間をかけてこれを理解することは絶対に価値があります。ページパラメータはnon-faces要求をまたがって状態を伝播するのに最も洗練された方法です。それらは検索結果をブックマーク可能な検索画面にするような問題にとって特に素晴らしい方法です。そのような問題では、同じコードでPOSTとGET要求の両方を処理できるようなアプリケーションコードを書けるようにしたいと望むでしょう。ページパラメータを使えば、ビュー定義内で要求パラメータのリストを繰り返し書く必要がなくなり、リダイレクトをもっと簡単にコーディングできるようになります。

書き換えはpages.xml内のビューで発見される書き換えパターンを元に発生します。SeamのURL書き換えは同一のパターンに基づいて入力方向と出力方向の両方をURL書き換えを実施します。



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

この場合は、 /home のための任意の入力要求は /home.xhtml に送られます。さらに興味深いことには、通常 /home.seam を指し示す任意のリンクは /home に書き換えられます。書き換えパターンはクエリーパラメータの前のURL部分にのみマッチします。それゆえ、 /home.seam?conversationId=13/home.seam?color=red は両方ともこの書き換え規則にマッチします。

書き換え規則は、以下の規則に示すように、これらのクエリーパラメータを考慮することができます。



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

この場合、 /home/red の入力要求はあたかも /home.seam?color=red のように振る舞います。同様に、もしcolor がページパラメータなら /home.seam?color=blue と通常表示される出力URLは、代わりに /home/blue と出力されます。

Seamのフィンガープリントを隠す別のオプションを指定することで、デフォルトのSeamクエリーパラメータもURL書き換えを使ってマップ可能です。次の例では、/search.seam?conversationId=13/search-13と書き換えられます。



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

Seam URL書き換えは、ビュー単位での単純で双方向の書き換えを提供します。非Seamコンポーネントをカバーするより複雑な書き換え規則のためには、Seamアプリケーションは継続して org.tuckey URLRewriteFilter を使う、あるいはWebサーバでの書き換え規則を適用することが可能です。

URL書き換えはSeamに書き換えフィルタを有効にすることを要求します。書き換えフィルタについては項30.1.4.3. 「URL のリライト」で説明します。

複雑なモデルのプロパティのためにJSFコンバータを指定することも可能です。


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

あるいは代わりに次のようにすることもできます。


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

次のように、JSFバリデータと required="true" も使用できます。


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

あるいは代わりに次のようにすることもできます。


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

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

型変換や妥当性検証が失敗したなら、グローバルな FacesMessageFacesContextに追加されます。

Seamアプリケーションではfaces-config.xmlで定義される標準のJSFナビゲーション規則を使用できます。しかし、JSFナビゲーション規則は厄介な制限があります。

さらにpages.xmlfaces-config.xml の間に「オーケストレーション」ロジックが分散してしまうという問題があります。このロジックは pages.xml に統合した方が良いでしょう。

この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
>

次のように書き直すことができます。


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

しかし、DocumentEditor コンポーネントが文字列の戻り値(JSFの結果)を持つような汚いコードを書かなくてすめばさらに良くなるでしょう。Seamでは次のように書くことができます。


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

または、次のようにすら書くことができます。


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

最初の形式は後続の規則によって使用されるようにcoutcomeの値を決定する値バインディングを評価します。 二番目のアプローチはoutcomeを無視し、各々の規則の値バインディングを評価します。

もちろん、更新が成功したなら、現在の対話 (conversation) を終了させたいことでしょう。 これには、次のようにします。


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

対話が終了してしまうと、後続の要求は関心があるのがどのドキュメントであるのか知ることができません。要求パラメータとしてドキュメントIDを渡すことができます。そして、それはビューをブックマーク可能にもしてくれます。


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

outcomeがnullとなるのはJSFでは特別なケースです。coucomeがnullは「そのページを再表示する」 という意味に解釈されます。次のナビゲーション規則はnullではないoutcomeに適合しますが、outcomeがnullのものには適合しません


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

outcomeがnullの場合にナビゲーションをしたいのであれば、 代わりに次の形式を使います。


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

ビューidはJSF EL式として与えることができます。


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

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

もしも、大量のページアクション、ページパラメータ、ナビゲーション規則が あるなら、それらの定義を複数のファイルに分割したいことでしょう。 ビューidが/calc/calculator.jspのアクションやパラメータは calc/calculator.page.xml という名前のリソースに定義可能です。 この場合のルート要素は<page>要素で、ビューidは暗に指定されます。


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

Seamコンポーネント同士は互いのメソッドを呼ぶことだけでやりとりができます。 ステートフルコンポーネントはobserver/observableパターンを実装することすらできます。 しかし、コンポーネントが互いにメソッドを直接呼ぶとき、より疎結合な方法でやりとりできるために、Seamはコンポーネント駆動イベントを提供します。

イベントリスナー (observers) をcomponents.xmlに指定します。


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

ここでevent type は単なる任意の文字列です。

イベントが発生するとき、そのイベント用に登録されたアクションはcomponents.xml に出現した順番で呼び出されます。コンポーネントはどのようにイベントを発行するのでしょうか。Seamはこのために組み込みコンポーネントを提供します。

@Name("helloWorld")

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

あるいは、アノテーションを使うことも可能です。

@Name("helloWorld")

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

このイベントプロデューサはイベントコンシューマになんら依存していないことに注意してください。 そのイベントリスナーはまったくプロデューサと依存関係がないように実装できるのです。

@Name("helloListener")

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

上記の components.xml で定義されたメソッドバインディングは、コンシューマへのイベントのマップを扱います。もし components.xml ファイルを編集するのが嫌ならば、その代わりにアノテーションを使うこともできます。

@Name("helloListener")

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

なぜイベントオブジェクトについて今まで何も言及してこなかったのか疑問を思われるかもしれません。Seamでは、イベントプロデューサとリスナーの間の状態の伝播のためのイベントオブジェクトは必要ではありません。状態はSeamコンテキストで保持され、コンポーネント間で共有されます。しかし、もしイベントオブジェクトを渡したいのであれば、次のようにすることも可能です。

@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は特殊なフレームワークの統合のためにアプリケーションが利用可能な多くの組み込みイベントを定義します。そのイベントとは次のようなものです。

Seamコンポーネントは、他のコンポーネント駆動イベントを観察する (observe) のとまったく同様に これらのどのイベントでも観察することが可能です。

EJB 3.0 はセッション Bean コンポーネントに対して標準的なインタセプタモデルを導入しました。 Bean にインタセプタを追加するには、 @AroundInvoke というアノテーションが付加されたメソッドの付いたクラスを記述して、 その Bean に対してインタセプタのクラス名を指定する @Interceptors のアノテーションを付ける必要があります。

public class LoggedInInterceptor {


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

このインタセプタをアクションリスナーとして動作するセッションBeanに対して適用するためには、そのセッションBeanに @Interceptors(LoggedInInterceptor.class) というアノテーションを付加しなければなりません。これはちょっと見栄えの悪いアノテーションです。Seamはクラスレベルインタセプタのためのメタアノテーションとして @Interceptors を使えるようにEJB3のインタセプタフレームワーク上に構築されています。例えば、以下では、 @LoggedIn アノテーションを生成します。

@Target(TYPE)

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

こうして、 このインタセプタを適用するのにアクションリスナー Bean に@LoggedIn アノテーションだけを付加すればよくなりました。

@Stateless

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

インタセプタの順番が重要な場合 (通常は重要となる)、 インタセプタクラスに対して @Interceptor アノテーションを追加しインタセプタの半順序を指定することが可能です。

@Interceptor(around={BijectionInterceptor.class,

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

「クライアント側」インタセプタを持つこともできます。 EJB3 のいずれの組み込み機能とでも併用することができます。

@Interceptor(type=CLIENT)

public class LoggedInInterceptor
{
    ...
}

EJB インタセプタはステートフルで、 インタセプトする対象となるコンポーネントと同じライフルサイクルに従います。 状態を維持する必要がないインタセプタの場合、 Seam では @Interceptor(stateless=true) を指定することでパフォーマンス最適化ができるようになります。

Seamの多くの機能は、前の例で登場したようなインタセプタを含む組み込みのSeamインタセプタによって実装されています。コンポーネントにアノテーションを付加することによってこれらのインタセプタを明示的に指定する必要はありませんが、インタセプタを適用可能なすべてのSeamコンポーネントのために存在しているのです。

Seam インタセプタは EJB3 Bean だけでなく JavaBean コンポーネントにも使うことができます。

EJB は、 インタセプションを (@AroundInvoke を使った) ビジネスメソッドだけでなく、 ライフサイクルメソッド @PostConstruct@PreDestroy@PrePassivate そして @PostActive に対しても定義します。 Seam は、 コンポーネントとインタセプタに対するこれらすべてのライフサイクルメソッドを EJB3 Bean だけでなく JavaBean コンポーネントに対してもサポートします (JavaBean コンポーネントにとって意味のない @PreDestroy は除きます)。

JSF は例外処理に関しては驚くほど制限があります。 この問題の部分的な回避策として、 Seam は例外クラスにアノテーションを付けるか XML ファイルに例外クラスを宣言することで例外となる特定クラスを処理する方法を定義することができます。 この機能は、 指定された例外がトランザクションロールバックの原因になるべきか否かを指定するのに EJB 3.0 標準の @ApplicationException アノテーションと一緒に使われることが意図されていています。

Bean のビジネスメソッドによって例外がスローされると、 その例外は現在のトランザクションに直ちにロールバックが必要として印を付けるかどうかを制御できるよう明確な規則を EJB は定義しています。 システム例外 は常にトランザクションロールバックとなり、 アプリケーション例外 はデフォルトではロールバックとはなりませんが @ApplicationException(rollback=true) が指定されるとロールバックとなります。 (アプリケーション例外とは、 チェックの付いた例外、 または @ApplicationException アノテーションが付いたチェックのない例外です。 システム例外とは、 @ApplicationException アノテーションもチェックも付いていない例外です。)

ロールバック用にトランザクションに印を付けるのと、 実際にロールバックを行うのとは異なります。 例外規則にはトランザクションにロールバックが必要であると印が付けられることだけしか言及していませんが、 例外がスローされた後でもそれはアクティブのままである可能性があるということに注意してください。

Seam は EJB 3.0 例外のロールバック規則を Seam JavaBean コンポーネントに対しても適用します。

しかし、 これらの規則は Seam コンポーネント層でのみ適用されるます。 では、 例外がキャッチされることなく Seam コンポーネント層の外部に伝播し、 さらに JSF 層の外に伝播したらどうなるでしょうか。 仕掛かり中のトランザクションをオープンしたままで放置するのは間違いなので、 例外が発生し Seam コンポーネント層でキャッチされないと Seam はアクティブトランザクションを必ずロールバックします。

次の例外は Seam コンポーネント層の外部に伝播すると必ず HTTP 404 エラーになります。 スローされてもすぐには現在のトランザクションをロールバックしませんが、 別の Seam コンポーネントによって例外がキャッチされないとこのトランザクションはロールバックされます。

@HttpError(errorCode=404)

public class ApplicationException extends Exception { ... }

この例外は Seam コンポーネント層の外部に伝播すると必ずブラウザリダイレクトになります。 また、 現在の対話も終了させます。 これにより現在のトランザクションを即時ロールバックさせることになります。

@Redirect(viewId="/failure.xhtml", end=true)

@ApplicationException(rollback=true)
public class UnrecoverableApplicationException extends RuntimeException { ... }

ELを使ってリダイレクト先の ビューid を指定することも可能です。

この例外は Seam コンポーネント層の外部に伝播すると必ずユーザーへのメッセージを付けてリダイレクトされます。 また、 現在のトランザクションも即時ロールバックさせます。

@Redirect(viewId="/error.xhtml", message="Unexpected error")

public class SystemException extends RuntimeException { ... }

関心のあるすべての例外クラスへアノテーションを付加することは不可能なので、Seamはこの機能を 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
>

最後の <exception> 宣言はクラスを指定していないので、 アノテーションまたは pages.xml で指定されているもの以外すべての例外をキャッチします。

ELを使ってリダイレクト先の view-id を指定することもできます。

EL によってキャッチした例外インスタンスにアクセスすることができます。 Seamはそれを対話コンテキストに置きます。例外のメッセージにアクセスする例は次の通り。


...
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 は例外ハンドラによって実際に処理されたネストされた例外を保持します。その最も外側の(ラッパーの)例外はorg.jboss.seam.caughtExceptionによって取得可能です。

もし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
>

もしSeamアプリケーションフレームワークを使っている場合


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

もしSeamセキュリティを使っている場合


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

そして、JSFの場合


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

A ViewExpiredException occurs if the user posts back to a page once their session has expired. The conversation-required and no-conversation-view-id settings in the Seam page descriptor, discussed in 項7.4. 「Requiring a long-running conversation」, give you finer-grained control over session expiration if you are accessing a page used within a conversation.