SeamFramework.orgCommunity Documentation
コンテキスト依存コンポーネントモデルを補完するものとして、Seamアプリケーションの特徴と なっている極度の疎結合を促進させる2つの基本概念が存在します。 最初のものは、イベントがJSFライクなメソッド結合式(method binding expression) を介して イベントリスナーへマップできるような強力なイベントモデルです。 二番目のものは、ビジネスロジックを実装するコンポーネントに対して横断的関心事 (cross-cutting concerns) を適用するためにアノテーションやインターセプタを広範囲に 使用しているということです。
Seamコンポーネントモデルはイベント駆動アプリケーション で使うために開発されました。特に、細粒度イベントモデル (fine-grained eventing model) での細粒度かつ疎結合コンポーネント開発を可能にします。 Seamでのイベントは、すでにご存知のように、いくつかのタイプがあります。
JSFイベント
jBPM状態遷移イベント
Seamページアクション
Seamコンポーネント駆動イベント
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 によって定義される別の 2 種類のイベントについて見ていきます。
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
>
複数のワイルドカード化されたページアクションがカレントビューidに一致するなら、 Seamは曖昧な指定から明確な指定への順 (least-specific to most-specific) で、 それらすべてのアクションを呼び出します。
ページアクションのメソッドはJSF outcomeを返すことができます。もしも、そのoutcome が非nullなら、Seamはビューをナビゲートするためその定義済みナビゲーション規則を使います。
さらに、<page>要素で指定されたビューidは、実際のJSPやFacelets に対応する必要はないのです! そこで、ページアクションを使用したStrutsやWebWorkのような 伝統的なアクション指向フレームワークの機能を再現することもできます。
TODO: translate struts action into page action
non-facesリクエスト (たとえば、 HTTP Get リクエスト) に対するレスポンスで複雑な処理をしたい場合などに非常に便利です。
複数または条件付きのページアクションは<action>タグを使って指定できます。
<pages>
<page view-id="/hello.jsp">
<action execute="#{helloWorld.sayHello}" if="#{not validation.failed}"/>
<action execute="#{hitCount.increment}"/>
</page>
</pages
>
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入力の値結合のようです。
指定されたビューidに対するnon-faces (GET) リクエストが発生するとき、 Seamは、適切な型変換を施した後に、名前付きパラメータの値をそのモデルオブジェクトに設定します。
任意の <s:link> や <s:button> は透過的にリクエストパラメータを含みます。 パラメータ値は、 レンダリングフェーズの間に (<s:link> がレンダリングされるとき) 値結合を評価することによって決定されます。
ビューidに対する<redirect/>の任意のナビゲーションルールは リクエストパラメータを透過的に含みます。パラメータの値はアプリケーションフェーズの 最後に値結合を評価することで決定されます。
その値は透過的にビューidで指定されたページへのJSFフォーム送信に伝播します。 これはビューパラメータはfacesリクエストのためのPAGEスコープ のコンテキスト変数のように振舞うことを意味します。
この背後にある本質的な考えは、 /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ページを作成したいときには特に便利です。それは以前操作していた(例えば、保存ボタンを押したときの)ビューや編集していたビューを「覚えておく」のに使えます。
任意の <s:link> や <s:button> は、リクエストパラメータがビューのページパラメータとして記入されていれば、透過的にそのパラメータを伝播します。
その値は透過的にビューidで指定されたページへのJSFフォーム送信に伝播します。 (これはビューパラメータはfacesリクエストのためのPAGEスコープ のコンテキスト変数のように振舞います)。
これらすべてはかなり複雑に聞こえますし、そのような変わった概念を努力して使う価値があるのかと疑問に思うことでしょう。実際は、そのアイディアは一旦「理解」してしまえばとても自然なものです。時間をかけてこれを理解することは絶対に価値があります。ページパラメータは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に書き換えフィルターを有効にすることを要求します。書き換えフィルターについては項29.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
>
さらに良いことには、モデルベースのHiberanteバリデータアノテーションは自動的に認識されて妥当性の検証をします。
型変換や妥当性検証が失敗したなら、グローバルな FacesMessage がFacesContextに追加されます。
Seamアプリケーションではfaces-config.xmlで定義される標準の JSFナビゲーション規則を使用できます。しかし、JSFナビゲーション規則は厄介な制限があります。
リダイレクトで使われるときにリクエストパラメータを指定できません。
規則から対話 (conversation) の開始や終了ができません。
規則はアクションメソッドの戻り値の評価によって動作します。 つまり、任意のEL式を評価することはできません。
さらにpages.xml と faces-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
>
最初の形式は後続の規則によって使用されるようにcoutcomの値を決定する値結合を評価します。 二番目のアプローチは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
>
Null outcomeはJSFでは特別なケースです。null coucomeは「そのページを再表示する」 という意味に解釈されます。次のナビゲーション規則は非nullのoutcomeに適合しますが null outcomeでは適合しないです。
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<rule>
<render view-id="/viewDocument.xhtml"/>
</rule>
</navigation>
</page
>
null outcomeが発生したときにナビゲーションをしたいのであれば、 変わりに次の形式を使います。
<page view-id="/editDocument.xhtml">
<navigation from-action="#{documentEditor.update}">
<render view-id="/viewDocument.xhtml"/>
</navigation>
</page
>
view-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は特殊なフレームワークの統合のためにアプリケーションが利用可能な多くの組み込みイベントを定義します。そのイベントとは次のようなものです。
org.jboss.seam.validationFailed — JSFバリデーションが失敗したときに呼ばれます
org.jboss.seam.noConversation — 長期対話が存在しない状態で長期対話が要求されたときに呼ばれます
org.jboss.seam.preSetVariable.<name> — コンテキスト変数 <name> 設定されたときに呼ばれます
org.jboss.seam.postSetVariable.<name> — コンテキスト変数 <name> が設定されたときに呼ばれます
org.jboss.seam.preRemoveVariable.<name> — コンテキスト変数 <name> が設定されなくなったら呼ばれます
org.jboss.seam.postRemoveVariable.<name> — コンテキスト変数 <name> が設定されなくなったら呼ばれます
org.jboss.seam.preDestroyContext.<SCOPE> — <SCOPE> コンテキストが破壊される前に呼ばれます
org.jboss.seam.postDestroyContext.<SCOPE> — <SCOPE> コンテキストが破壊された後に呼ばれます
org.jboss.seam.beginConversation — 長期対話が開始するときにはいつでも呼ばれます
org.jboss.seam.endConversation — 長期対話が終了するときはいつでも呼ばれます
org.jboss.seam.conversationTimeout— 対話タイムアウトが発生するときに呼ばれます。対話IDはパラメータとして渡されます。
org.jboss.seam.beginPageflow — ページフローが開始するときに呼ばれます
org.jboss.seam.beginPageflow.<name> — ページフロー <name> が開始するときに呼ばれます
org.jboss.seam.endPageflow — ページフローが終了するときに呼ばれます
org.jboss.seam.endPageflow.<name> — ページフロー <name> が終了するときに呼ばれます
org.jboss.seam.createProcess.<name> — プロセス <name> が生成されるときに呼ばれます
org.jboss.seam.endProcess.<name> — プロセス <name> が終了するとき呼ばれます
org.jboss.seam.initProcess.<name> — プロセス <name> が対話と関連づけられるときに呼ばれます
org.jboss.seam.initTask.<name> — タスク <name> が対話と関連付けられるときに呼ばれます
org.jboss.seam.startTask.<name> — タスク <name> が開始させられるときに呼ばれます
org.jboss.seam.endTask.<name> — タスク <name> が終了させられるときに呼ばれます
org.jboss.seam.postCreate.<name> — コンポーネント <name> が生成されたときに呼ばれます
org.jboss.seam.preDestroy.<name> — コンポーネント <name> が破壊されるときに呼ばれます
org.jboss.seam.beforePhase — JSFフェーズの開始前に呼ばれます
org.jboss.seam.afterPhase — JSFフェーズの終了後に呼ばれます
org.jboss.seam.postInitialization — Seamが初期化完了し、すべてのコンポーネントを起動するときに呼ばれます
org.jboss.seam.postReInitialization — Seamが最初期化を完了し、再デプロイ後にすべてのコンポーネントを起動するときに呼ばれます
org.jboss.seam.postAuthenticate.<name> — ユーザが認証された後に呼ばれます
org.jboss.seam.preAuthenticate.<name> — ユーザを認証しようとする前に呼ばれます
org.jboss.seam.notLoggedIn — 認証されたユーザが存在しないところで認証が要求されるときに呼ばれます
org.jboss.seam.rememberMe — Seamセキュリティがクッキー内にユーザ名を検出したときに発生します
org.jboss.seam.exceptionHandled.<type> — キャッチされなかった例外がSeamによって処理されるときに呼ばれます
org.jboss.seam.exceptionHandled — キャッチされなかった例外がSeamによって処理されるときに呼ばれます
org.jboss.seam.exceptionNotHandled — キャッチされなかった例外のためのハンドラが存在しなかったときに呼ばれます
org.jboss.seam.afterTransactionSuccess — Seamアプリケーションフレームワークでトランザクションが成功するときに呼ばれます
org.jboss.seam.afterTransactionSuccess.<name> — <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の例外処理を有効にするには、主となるサーブレットフィルタを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
>*.seam</url-pattern>
</filter-mapping
>
ご使用の例外ハンドラを機能させる場合は、 web.xml の Facelets 開発モードおよび components.xml の 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を使ってリダイレクト先の viewId を指定することも可能です。
この例外は 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によって取得可能です。
pages.xml で定義された例外ハンドラについて、どの例外がログに記録されるかのログレベルを宣言できますし、ログに記録される例外を抑えることもできます。属性 log と logLevel は例外のロギングを制御するために使用できます。次の例のように log="false" と設定すると、指定された例外が発生するときにログメッセージは何も出力されません。
<exception class="org.jboss.seam.security.NotLoggedInException" log="false">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
もし log 属性が指定されなければ、 trueがデフォルトになります(つまり、例外はログに記録されます)。別の方法として、例外がログに記録されるレベルを制御する logLevel を指定することも可能です。
<exception class="org.jboss.seam.security.NotLoggedInException" logLevel="info">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
logLevel に指定可能な値は、fatal, error, warn, info, debug または trace です。もし logLevel が指定されていない、もしくは、不正な値が設定されていれば、その値は error がデフォルトになります。
もし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
>
ViewExpiredException はセッションの有効期限が切れたときにポストバックをページに送信しようとすると発生します。もし対話の中で操作中であればno-conversation-view-id と conversation-required を使えば細かなセッションの有効期限の制御が可能になります。