Seam - コンテキスト依存コンポーネント

Java EE 5 用フレームワーク

1.2.1.GA


目次

JBoss Seam はじめに
1. Seam チュートリアル
1.1. サンプルを試そう
1.1.1. JBoss AS 上でのサンプルの実行
1.1.2. Tomcat 上でのサンプル実行
1.1.3. サンプルのテスト起動
1.2. 初めての Seam アプリケーション: ユーザ登録サンプル
1.2.1. コードの理解
1.2.1.1. エンティティ Bean: User.java
1.2.1.2. ステートレスセッション Bean クラス: RegisterAction.java
1.2.1.3. セッション Bean ローカルインタフェース : Register.java
1.2.1.4. Seam コンポーネント配備記述子 : components.xml
1.2.1.5. WEB 配備記述子 : web.xml
1.2.1.6. JSF 設定 : faces-config.xml
1.2.1.7. EJB 配備記述子 : ejb-jar.xml
1.2.1.8. EJB 永続配備記述子 : persistence.xml
1.2.1.9. ビュー : register.jsp、registered.jsp
1.2.1.10. EAR 配備記述子 : application.xml
1.2.2. 動作内容
1.3. Seam でクリックが可能な一覧: 掲示板サンプル
1.3.1. コードの理解
1.3.1.1. エンティティ Bean : Message.java
1.3.1.2. ステートフルセッション Bean : MessageManagerBean.java
1.3.1.3. セッション Bean ローカルインタフェース : MessageManager.java
1.3.1.4. ビュー: messages.jsp
1.3.2. 動作内容
1.4. Seam と jBPM : TO-DO 一覧サンプル
1.4.1. コードの理解
1.4.2. 動作内容
1.5. Seam ページフロー: 数字当てゲームサンプル
1.5.1. コードの理解
1.5.2. 動作内容
1.6. 本格的 Seam アプリケーション: ホテル予約サンプル
1.6.1. はじめに
1.6.2. 予約サンプルの概要
1.6.3. Seam 対話の理解
1.6.4. The Seam UI 管理ライブラリ
1.6.5. Seam デバッグページ
1.7. Seam と jBPM を使った本格的アプリケーション: DVD ストアサンプル
1.8. Seam ワークスペース管理を使った本格的アプリケーション: 問題追跡システムサンプル
1.9. Hibernate を使った Seam サンプル: Hibernate 予約システムサンプル
1.10. RESTful Seam アプリケーション: Blog サンプル
1.10.1. "PULL" 型 MVC の使用
1.10.2. ブックマーク可能検索結果ページ
1.10.3. RESTful アプリケーションの "PUSH" 型 MVC の使用
1.11. JSF 1.2 RI を使用した JBoss Seam サンプルの実行
2. seam-gen を使って Seam を始めよう
2.1. 始める前に
2.2. 新しい Eclipse プロジェクトのセットアップ
2.3. 新規のアクションを生成する
2.4. アクションのあるフォームを生成する
2.5. 既存のデータベースからアプリケーションを自動生成する
2.6. EAR形式でアプリケーションをデプロイする
2.7. Seam と増分ホットデプロイメント
3. コンテキスト依存コンポーネントモデル
3.1. Seam コンテキスト
3.1.1. ステートレスコンテキスト (Stateless context)
3.1.2. イベントコンテキスト
3.1.3. ページコンテキスト (Page context)
3.1.4. 対話コンテキスト (Conversation context)
3.1.5. セッションコンテキスト (Session context)
3.1.6. ビジネスプロセスコンテキスト (Business process context)
3.1.7. アプリケーションコンテキスト (Application context)
3.1.8. コンテキスト変数
3.1.9. コンテキスト検索優先順位
3.1.10. 同時並行処理モデル
3.2. Seam コンポーネント
3.2.1. ステートレスセッション Bean
3.2.2. ステートフルセッション Bean
3.2.3. エンティティ Bean
3.2.4. JavaBean
3.2.5. メッセージ駆動型 Bean
3.2.6. インターセプション
3.2.7. コンポーネント名
3.2.8. コンポーネントスコープの定義
3.2.9. 複数ロールを持つコンポーネント
3.2.10. 組み込みコンポーネント
3.3. バイジェクション
3.4. ライフサイクルメソッド
3.5. 条件付きインストール
3.6. ロギング
3.7. Mutable インタフェースと @ReadOnly
3.8. ファクトリと管理コンポーネント
4. Seamコンポーネントの設定
4.1. プロパティ設定によるコンポーネントの設定
4.2. components.xmlによるコンポーネントの設定
4.3. 細分化した設定ファイル
4.4. 設定可能なプロパティの型
4.5. XML名前空間の使用
5. イベント、インターセプタ、例外処理
5.1. Seam イベント
5.1.1. ページアクション
5.1.1.1. ページパラメータ
5.1.1.2. ナビゲーション
5.1.1.3. ナビゲーション、ページアクション、パラメータの定義用に細分化したファイル
5.1.2. コンポーネント駆動イベント
5.1.3. コンテキスト依存イベント
5.2. Seam インターセプタ
5.3. 例外を管理する
5.3.1. 例外およびトランザクション
5.3.2. Seam 例外処理を有効にする
5.3.3. 例外処理に対してアノテーションを使用する
5.3.4. 例外処理に XML を使用する
6. 対話とワークスペースの管理
6.1. Seam の対話モデル
6.2. ネストされた対話
6.3. GET リクエストを使って対話を開始する
6.4. <s:link> と <s:button> の使いかた
6.5. 成功のメッセージ
6.6. 「明示的な」対話 ID の使いかた
6.7. ワークスペースの管理
6.7.1. ワークスペース管理と JSF ナビゲーション
6.7.2. ワークスペース管理と jPDL ページフロー
6.7.3. 対話切り替え
6.7.4. 対話一覧
6.7.5. ブレッドクラム (Breadcrumbs)
6.8. 対話型コンポーネントと JSF コンポーネントのバインディング
7. ページフローとビジネスプロセス
7.1. Seam でのページフロー
7.1.1. 2 つのナビゲーションモデル
7.1.2. Seam と 戻るボタン
7.2. jPDL ページフローの使用
7.2.1. ページフローの設定
7.2.2. ページフローの開始
7.2.3. ページノードと遷移
7.2.4. フローの制御
7.2.5. フローの終了
7.3. Seam でのビジネスプロセス管理
7.4. jPDL ビジネスプロセス定義の使用
7.4.1. プロセス定義の設定
7.4.2. アクターIDの初期化
7.4.3. ビジネスプロセスの初期化
7.4.4. タスク割り当て
7.4.5. タスクリスト
7.4.6. タスクの実行
8. Seam とオブジェクト/リレーショナルマッピング
8.1. はじめに
8.2. Seam 管理トランザクション
8.2.1. Seam 管理トランザクションを有効にする
8.3. Seam 管理の永続コンテキスト
8.3.1. JPA で Seam 管理の永続コンテキストを使用する
8.3.2. Seam 管理の Hibernate セッションを使用する
8.3.3. Seam 管理の永続コンテキストと微小な対話
8.4. JPA 「デリゲート」を使用する
8.5. EJB-QL/HQL で EL を使用する方法
8.6. Hibernate フィルタを使用する
9. Seam での JSF 形式検証
10. Seamアプリケーションフレームワーク
10.1. イントロダクション
10.2. Homeオブジェクト
10.3. Queryオブジェクト
10.4. Controllerオブジェクト
11. Seam と JBoss Rules
11.1. ルールの初期化
11.2. Seam コンポーネントからのルールの使用
11.3. jBPM プロセス定義からルール使用
12. セキュリティ
12.1. 概要
12.1.1. どちらのモードを使うのが適切か?
12.2. 要求条件
12.3. 認証
12.3.1. 設定
12.3.2. 認証メソッドの記述
12.3.3. ログインフォームの記述
12.3.4. 簡単な設定 ー まとめ
12.3.5. セキュリティ例外の処理
12.3.6. ログインリダイレクト
12.3.7. 高度な認証機能
12.3.7.1. コンテナのJAAS設定を利用する
12.4. エラーメッセージ
12.5. 認可
12.5.1. 核となる概念
12.5.2. セキュリティコンポーネント
12.5.2.1. @Restrictアノテーション
12.5.2.2. インラインによる制約
12.5.3. ユーザインターフェースのセキュリティ
12.5.4. ページ単位のセキュリティ
12.5.5. エンティティのセキュリティ
12.5.5.1. JPAでのエンティティセキュリティ
12.5.5.2. Hibernateでのエンティティセキュリティ
12.6. セキュリティルールの記述
12.6.1. パーミッションについての概要
12.6.2. ルールファイルの設定
12.6.3. セキュリティルールファイルの作成
12.6.3.1. ワイルドカードのパーミッションチェック
12.7. SSLによるセキュリティ
12.8. キャプチャテストの実装
12.8.1. キャプチャサーブレットの設定
12.8.2. ページにキャプチャを追加する
13. 国際化とテーマ (Internationalization and themes)
13.1. ロケール
13.2. ラベル
13.2.1. ラベルを定義する
13.2.2. ラベルを表示する
13.2.3. Faces メッセージ
13.3. タイムゾーン
13.4. テーマ
13.5. ロケールとテーマ設定のクッキーによる永続化
14. Seam Text
14.1. フォーマットの基本
14.2. プログラムコードや特殊文字を含むテキストの記述
14.3. リンク
14.4. HTMLの記述
15. iText PDF 生成
15.1. PDF サポートを使う
15.2. ドキュメントを作成する
15.2.1. p:document
15.3. 基本的なテキストのエレメント
15.3.1. p:paragraph
15.3.2. p:text
15.3.3. p:font
15.3.4. p:newPage
15.3.5. p:image
15.3.6. p:anchor
15.4. ヘッダとフッタ
15.4.1. p:header and p:footer
15.4.2. p:pageNumber
15.5. 章とセクション
15.5.1. p:chapter と p:section
15.5.2. p:title
15.6. 一覧
15.6.1. p:list
15.6.2. p:listItem
15.7. 表
15.7.1. p:table
15.7.2. p:cell
15.8. ドキュメントの定数
15.8.1. Color Values
15.8.2. 位置調整の値
15.9. iText を設定する
15.10. iText のリンク
16. Email
16.1. メッセージを作成する
16.1.1. 添付
16.1.2. HTML/Text 代替部分
16.1.3. 複数の受信者
16.1.4. 複数のメッセージ
16.1.5. テンプレートの作成
16.1.6. 国際化
16.1.7. その他のヘッダ
16.2. email を受信する
16.3. 設定
16.3.1. mailSession
16.3.1.1. JBoss AS の JNDI ルックアップ
16.3.1.2. Seam 設定のセション
16.4. タグ
17. 非同期性とメッセージング
17.1. 非同期性
17.1.1. 非同期メソッド
17.1.2. 非同期イベント
17.2. Seam でのメッセージング
17.2.1. 設定
17.2.2. メッセージ送信
17.2.3. メッセージ駆動型 Bean を使用したメッセージの受信
17.2.4. クライアントでのメッセージの受信
18. キャッシュ
18.1. SeamでJBossCacheを利用する
18.2. ページ断片のキャッシュ
19. Remoting
19.1. 設定
19.2. "Seam" オブジェクト
19.2.1. Hello World サンプル
19.2.2. Seam.Component
19.2.2.1. Seam.Component.newInstance()
19.2.2.2. Seam.Component.getInstance()
19.2.2.3. Seam.Component.getComponentName()
19.2.3. Seam.Remoting
19.2.3.1. Seam.Remoting.createType()
19.2.3.2. Seam.Remoting.getTypeName()
19.3. クライアント インターフェース
19.4. コンテキスト
19.4.1. 対話 ID の設定と読み込み
19.5. バッチリクエスト
19.6. データタイプの取り扱い
19.6.1. プリミティブ型 / 基本タイプ
19.6.1.1. String 型
19.6.1.2. Number 型
19.6.1.3. Boolean 型
19.6.2. JavaBeans
19.6.3. 日付と時刻
19.6.4. Enums
19.6.5. 集合
19.6.5.1. Bag
19.6.5.2. Map
19.7. デバッグ機能
19.8. メッセージをロードする
19.8.1. メッセージを変更する
19.8.2. ローディングメッセージを隠す
19.8.3. カスタムのローディングインジケーター
19.9. 返されるデータを制御する
19.9.1. 通常のフィールドを制約する
19.9.2. Map と 集合を制約する
19.9.3. 特定タイプのオブジェクトを制約する
19.9.4. 制約同士を組み合わせる
19.10. JMS メッセージング
19.10.1. 設定
19.10.2. JMS Topic にサブスクライブする
19.10.3. トピックのサブスクライブを中止する
19.10.4. ポーリングのプロセスを調整する
20. Spring Framework 統合
20.1. Seam コンポーネントを Spring bean にインジェクトする
20.2. Spring bean を Seam コンポーネントにインジェクトする
20.3. Spring bean を Seam コンポーネントにする
20.4. Seam スコープの Spring bean
20.5. Seam コンポーネントとしての Spring Application Context
21. Seam の設定と Seam アプリケーションのパッケージング
21.1. Seam の基本設定
21.1.1. Seam と JSF、servlet コンテナとの統合
21.1.2. Seam リソース Servlet
21.1.3. Seam servlet フィルター
21.1.3.1. 例外処理
21.1.3.2. リダイレクトによる対話の伝搬
21.1.3.3. マルチパートフォームの送信
21.1.3.4. キャラクターエンコーディング
21.1.3.5. カスタム servletsのコンテキスト管理
21.1.4. EJB コンテナと Seam の統合
21.1.5. facelet の使用
21.1.6. おっと、もう一つ重要情報
21.2. Java EE 5 で Seam を設定
21.2.1. パッケージング
21.3. JBoss 組み込み可能 EJB3 コンテナ の Seam 設定
21.3.1. 組み込み可能 EJB3 コンテナのインストール
21.3.2. 組み込み可能 EJB3 コンテナのデータソース設定
21.3.3. パッケージング
21.4. J2EEでの Seam の設定
21.4.1. Seam での Hibernateのブートストラップ
21.4.2. Seam での JPAのブートストラップ
21.4.3. パッケージング
21.5. JBoss マイクロコンテナを使い Seamを Java SE環境下で設定する
21.5.1. JBoss マイクロコンテナと Hibernateを使用する
21.5.2. パッケージング
21.6. SeamでのjBPM設定
21.6.1. パッケージング
21.7. ポータルでの Seamの設定
21.8. JBoss ASでの SFSBとセッションタイムアウトの設定
22. Seam アノテーション
22.1. コンポーネント定義用アノテーション
22.2. バイジェクション用アノテーション
22.3. コンポーネントのライフサイクルメソッド用アノテーション
22.4. コンテキスト境界用アノテーション
22.5. トランザクション境界用のアノテーション
22.6. 例外用のアノテーション
22.7. バリデーション用のアノテーション
22.8. Seam Remoting用のアノテーション
22.9. Seam インターセプタ用のアノテーション
22.10. 非同期用のアノテーション
22.11. JSF dataTable と併用するアノテーション
22.12. データバインディング用のメタアノテーション
22.13. パッケージング用のアノテーション
23. 組み込み Seam コンポーネント
23.1. コンテキストインジェクションのコンポーネント
23.2. ユーティリティコンポーネント
23.3. 国際化とテーマのコンポーネント
23.4. 対話を制御するためのコンポーネント
23.5. jBPM 関連のコンポーネント
23.6. セキュリティ関連のコンポーネント
23.7. JMS 関連のコンポーネント
23.8. メール関連のコンポーネント
23.9. 基盤となるコンポーネント
23.10. 特殊なコンポーネント
24. Seam JSF コントロール
25. ELの拡張
25.1. 設定
25.2. 使用方法
25.3. 制約
25.3.1. JSP 2.1との非互換性
25.3.2. JavaコードからのMethodExpressionの呼び出し
26. Seamアプリケーションのテスト
26.1. Seamコンポーネントのユニットテスト
26.2. Seamアプリケーションの統合テスト
26.2.1. モックを使用した統合テスト
27. Seam ツール
27.1. jBPM デザイナ と ビュアー
27.1.1. ビジネスプロセスデザイナ
27.1.2. ページフロービュアー
27.2. CRUD アプリケーションジェネレータ
27.2.1. Hibernate 設定ファイルの作成
27.2.2. Hibernate コンソール設定の作成
27.2.3. リバースエンジニアリング と コードジェネレーション
27.2.3.1. コードジェネレーションのランチャー
27.2.3.2. エクスポート
27.2.3.3. コードの生成と利用

JBoss Seam はじめに

Seam は Java EE 5 用のアプリケーションフレームワークです。 次のような理念に基づいています。

JSF と EJB 3.0 の統合

JSF と EJB 3.0 の 2 つは Java EE 5 の最良の新機能です。 EJB3 はサーバー側の機能や永続ロジックに対するまったく新しいコンポーネントモデルとなります。 一方、 JSF はプレゼンテーション層に対して優れたコンポーネントモデルとなります。 残念ながら、 どちらのコンポーネントモデルも単独ではコンピュータの問題のすべてを解決することはできません。 実際に、 JSF と EJB3 は併用した場合に最も効果的に動作します。 しかしながら、 Java EE 5 の仕様はこの 2 つのコンポーネントモデルを統合する標準の方法を提供していません。 幸い、 両モデルの開発者はこの状況を予感し、 他のソリューションの拡張や統合を可能にする標準の拡張ポイントを提供してくれていました。

Seam は JSF と EJB3 のコンポーネントモデルを統一し、 接着剤としてのコードを取り除き開発者がビジネス関連の問題により重点をおけるようにしてくれています。

統合 AJAX

Seam は、 ICEfaces および Ajax4JSF という JSF ベースのオープンソースな AJAX ソリューションをサポートしています。 これらのソリューションにより、 JavaScript コードを記述することなくユーザーインターフェースに AJAX 機能を追加することができるようになります。

また、 Seam は EJB3 コンポーネントに対して組み込みの JavaScript リモーティング層を提供します。 これにより、 中間のアクション層を必要とすることなく、 AJAX クライアントは簡単にサーバ側のコンポーネントを呼び出して JMS topic をサブスクライブすることができるようになります。

いずれの方法もうまく動作しない場合、 それは多くの並列同期の微細な AJAX リクエストがサーバー側で安全且つ効率的に処理される Seam のビルトイン並行処理状態管理ではないはずです。

ファーストクラスの構成によるビジネスプロセスの統合

オプションで、 Seam は jBPM を使って透過的なビジネスプロセス管理を統合します。 jBPM と Seam を使った複雑なワークフローの実装ががいかに容易であるか信じられないほどです。

Seam はまた、 同じ手段でプレゼンテーション層の対話フローの定義をも可能にします。

JSF はプレゼンテーション層に対して信じられないほど豊富なイベントモデルを提供しています。 Seam は全く同じイベント処理メカニズムを使って jBPM のビジネスプロセス関連のイベントを公開し、 Seam 均一コンポーネントモデルに対して一貫したイベントモデルを提供することでこのモデルの機能を拡張しています。

一貫した原則

Seam は一貫したコンポーネントモデルを提供します。 長期のビジネスプロセスから単一の WEB リクエストに至るまで、 いずれのコンテキストに関連付けられた状態でも Seam コンポーネントをステートフルとすることが可能です。

Seam では、 プレゼンテーション層のコンポーネントとビジネスロジックのコンポーネントの区別はありません。 EJB であれば、「どこでも」 Seam アプリケーションを書くことが可能です。 EJB は細かな調整ができず、 作成に苦労する小回りのきかないオブジェクトだと考えていた人にとっては驚きとして映るかもしれません。 しかし、 開発者の観点から見ると EJB 3.0 はその EJB としての性質を完全に変化させています。 EJB は微細な調整が可能なオブジェクトになり、 アノテーション付き Java Bean と同様に簡単になりました。 Seam でさえ JSF のアクションリスナとしてセッション Bean の使用を推奨しています。

単なる Java EE あるいは J2EE コンポーネントとは異なり、 Seam コンポーネントは WEB リクエストに関連した状態と、 トランザクション的なリソースに維持された状態とに同時にアクセスすることも可能です (メソッドパラメータを使って手作業で WEB リクエストの状態を伝播させる必要なく)。 昔なじみの J2EE プラットフォームにより必要とされたアプリケーションの階層化は良いことだと反対される方もいらっしゃるかもしれません。 Seam を使って同等の階層アーキテクチャを作成しても構いません。 違いは、 あなたが、 独自のアプリケーションを設計し、 どの層がどのように相互的に働くかを決定するということです。

宣言的状態管理

EJB 2.x. 以降の宣言的トランザクション管理や、 J2EE の宣言的セキュリティの概念には既に慣れてきていいます。 EJB 3.0 は宣言的永続コンテキスト管理も導入しています。 コンテキストが終了するときに必要なすべてのクリーンアップが行われることは保証される一方、 特定のコンテキストと関連する状態を管理する上で広範囲に渡る問題があります。 次に 3 つの例を示します。 Seam は、宣言的状態管理の概念をさらに広範囲に取り入れ、 それをアプリケーションの状態にも応用しています。 従来、 J2EE のアプリケーションは必ずと言っていいほどサーブレットセッションとリクエスト属性を取得し設定することにより手動で状態管理を実装しています。 状態管理を行うためのこの手段は、 アプリケーションがセッション属性のクリーンアップに失敗した場合やマルチウィンドウアプリケーションで異なるワークフローに関連付けられたセッションデータ同士が衝突する場合に多くのバグやメモリリークを生む源となります。 Seam にはほぼ完全にこのクラスのバグを解消する可能性が備わっています。

宣言的なアプリケーションの状態管理は Seam で定義されるコンテキストモデルの豊富さにより実現されます。 Seam は、 サーブレット仕様 — request、 session、 application — で定義されるコンテキストモデルに、 ビジネスロジック的により有意義である 2 つの新たなコンテキスト — 対話とビジネスプロセス — を加えることで機能拡張しました。

バイジェクション

制御の反転あるいは依存性のインジェクションの概念は JSF や EJB3 だけでなく、 多くのいわゆる「軽量コンテナ」にも存在します。 これらのコンテナのほとんどがステートレスサービスを実装するコンポーネントのインジェクションを重視しています。 ステートフルなコンポーネントのインジェクションがサポートされる場合であっても (JSF のように)、 ステートフルなコンポーネントのスコープは十分な柔軟性を持って定義できないため、 現実的にはアプリケーションの状態を扱うには役に立ちま せん。

バイジェクションは、 動的 (dynamic)コンテキスト依存 (contextual)双方向的 (bidirectional) という点において IoC とは異なります。 コンテキスト上の変数 (現在のスレッドと結びついた各種コンテキスト中の名前) をコンポーネントの属性の別名として対応させる機構と考えることができます。 バイジェクションはコンテナによるステートフルなコンポーネントの自動アセンブリを可能にします。 コンポーネントの属性を設定するだけで、 安全かつ容易にコンテキスト変数の値を操作することさえ可能になります。

ワークスペース管理

オプションで、 Seam アプリケーションはワークスペース管理機能を利用することも可能で、 ユーザは 1 つのブラウザウィンドウの中で異なる対話(ワークスペース)間を自由に行き来することができます。 Seam は正確なマルチウィンドウ操作だけでなく、 シングルウィンドウ内でマルチウィンドウのような操作も行うことができます。

どこでもアノテーション付きの POJO を

EJB 3.0 は、 宣言的な形式でコンテナに情報を提供する最も簡単な方法としてアノテーションと「例外による設定」を採用しています。 残念ながら、 JSF はまだ冗長な XML 設定ファイルに大きく依存しています。 Seam は、 EJB 3.0 によって提供されるアノテーションに宣言的状態管理および宣言的コンテキスト区分用のアノテーション一式を提供することにより機能拡張しています。 これにより、 うっとうしい JSF 管理の Bean 宣言を取り除き、 必要とされる XML を減少させ、本当に XML で定義すべき情報 (JSF ナビゲーション規則) だけになるようにします。

中核機能としてのテスト容易性

POJO である Seam コンポーネントはそもそもユニットテストが可能です。 しかし、 複雑なアプリケーションの場合はユニットテストだけでは不十分になります。 以前から、 統合テストは Java の WEB アプリケーションにとって煩雑でやっかいな作業となっていました。 このため、 Seam はフレームワークの中核機能として Seam アプリケーションのテスト容易性を提供しています。 ユーザーとのやりとり全体を再生し、 ビュー (JSP や Facelets ページ) 以外のシステムの全コンポーネントを作動させる JUnit テストや TestNG テストを容易に記述することができます。 Seam よって自動的に EJB コンポーネントが JBoss の組み込み可能 EJB3 コンテナにデプロイされる IDE の内部で、 これらのテストを直接実行することができます。

さあ、はじめましょう!

Seam は、EJB 3.0 をサポートするアプリケーションサーバーならいずれでも動作します。 新しい JBoss 組み込み可能 EJB3 コンテナを利用することで、 Tomcat のようなサーブレットコンテナや J2EE アプリケーションサーバーでも Seam を使用することができます。

ただし、 すべての人が EJB 3.0 に移行準備が整っているわけではないことも認識しています。 このため、 しばらくの間、 プレゼンテーションに JSF、永続に Hibernate (またはプレーン JDBC)、 アプリケーションロジックに JavaBeans を使用するアプリケーションに対しては Seam をフレームワークとして使用することができます。 これで、 EJB 3.0 への移行準備が整った時点での以降が簡単になります。

Java で複雑なウェブアプリケーションを記述する場合、 Seam、 JSF、 EJB3 の組み合わせが最もシンプルな手段となることがわかります。 必要とするコードが信じられないほど少なくなります。

第1章 Seam チュートリアル

1.1. サンプルを試そう

このチュートリアルでは、 JBoss AS 4.0.5 がダウンロードされ、 EJB 3.0 プロファイルがインストールされたものとして話を進めます。 (JBoss AS インストールを使用して) また、Seam のコピーもダウンロードして、 作業ディレクトリに展開されているものとします。

各 Seam サンプルのディレクトリーは、以下の要領で構成されています。

  • Web ページ、イメージあるいはスタイルシートは、 examples/registration/view にあります。

  • 配備記述子やデータインポートスクリプトなどのリソースは、 examples/registration/resources にあります。

  • Java ソースコードは、 examples/registration/src にあります。

  • Ant ビルドスクリプトは、 examples/registration/build.xml にあります。

1.1.1. JBoss AS 上でのサンプルの実行

最初に、$ANT_HOME$JAVA_HOME が正しく設定され、 Ant が正しくインストールされたことを確認してください。 次に、Seam をインストールしたルートフォルダにある build.properties ファイルに JBoss AS 4.0.5 のロケーションを設定してください。 まだ起動していなければ、 JBoss のルートディレクトリから bin/run.sh もしくは、bin/run.bat とタイプして JBoss アプリケーションサーバを起動してください。

次に、 examples/registration ディレクトリから ant deploy と入力してサンプルのビルドおよびデプロイを行います。

ブラウザから、 http://localhost:8080/seam-registration/ にアクセスしてみます。

1.1.2. Tomcat 上でのサンプル実行

最初に、$ANT_HOME$JAVA_HOME が正しく設定され、 Ant が正しくインストールされたことを確認してください。 次に、Seam をインストールしたルートフォルダにある build.properties ファイルに Tomcat 5.5 のロケーションを設定してください。

次に、examples/registration ディレクトリから ant deploy と入力プしてサンプルのビルドおよびデプロイを行います。

最後に、Tomcat を起動してください。

ブラウザから、 http://localhost:8080/jboss-seam-registration/ にアクセスしてみます。

サンプルを Tomcat にデプロイした場合、 EJB3 コンポーネントは、JBoss 組み込み EJB3 コンテナ (完全なスタンドアローン EJB コンテナ環境) 内部で作動します。

1.1.3. サンプルのテスト起動

ほとんどのサンプルは TestNG 統合テストスイートに対応しています。 一番簡単なテスト実行は、 examples/registration ディレクトリから、 ant testexample として起動させてください。 また、お使いの IDE から TestNG プラグインを利用してテスト実行することも可能です。

1.2. 初めての Seam アプリケーション: ユーザ登録サンプル

ユーザ登録サンプルは、データベースに新規ユーザのユーザ名、 実名、 パスワードをデータベースに保存できる簡単なアプリケーションです。 このサンプルは Seam の高度な機能の全てを見せることはできませんが、 JSF アクションリスナとして EJB3 セッション Bean を使用する方法や、 基本的な Seam の設定方法を見せてくれます。

EJB 3.0 にまだ不慣れな方もいらっしゃるかもしれませんので、 ゆっくり進めていきます。

最初のページは 3 つの入力フィールドを持つ基本的なフォームを表示します。 試しに、項目を入力してフォームをサブミットしてください。 これでユーザオブジェクトはデータベースに保存されます。

1.2.1. コードの理解

このサンプルは、2 つの JSP ページ、1 つのエンティティ Bean と、1 つのステートレスセッション Bean で実装されています。

基本から始めるために、コードを見てみましょう。

1.2.1.1. エンティティ Bean: User.java

ユーザデータに EJB エンティティ Beanが必要です。 このクラスでは、 アノテーションによって 永続性データ妥当性検証 を宣言的に定義しています。 Seam コンポーネントとしてのクラスを定義するために、別にいくつかのアノテーションも必要です。

例 1.1.

@Entity                                                                                  (1)
@Name("user")                                                                            (2)
@Scope(SESSION)                                                                          (3)
@Table(name="users")                                                                     (4)
public class User implements Serializable
{
   private static final long serialVersionUID = 1881413500711441951L;
   
   private String username;                                                              (5)
   private String password;
   private String name;
   
   public User(String name, String password, String username)
   {
      this.name = name;
      this.password = password;
      this.username = username;
   }
   
   public User() {}                                                                      (6)
   
   @NotNull @Length(min=5, max=15)                                                       (7)
   public String getPassword()
   {
      return password;
   }

   public void setPassword(String password)
   {
      this.password = password;
   }
   
   @NotNull
   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }
   
   @Id @NotNull @Length(min=5, max=15)                                                   (8)
   public String getUsername()
   {
      return username;
   }

   public void setUsername(String username)
   {
      this.username = username;
   }

}
(1)

EJB3 標準 @Entity アノテーションは、 User クラスがエンティティ Bean であることを示しています。

(2)

Seam コンポーネントは、 @Name アノテーションで指定される コンポーネント名 が必要です。 この名前は Seam アプリケーション中でユニークである必要があります。 JSF が Seam に Seam コンポーネント名と同じコンテキスト変数の解決を求める時に、 コンテキスト変数がそのとき未定義 (null) であれば、 Seam はインスタンスを生成してから新しいインスタンスをコンテキスト変数にバインドします。 このサンプルでは、 JSF が初めて user という変数と出会うときに、 Seam は User をインスタンス化します。

(3)

Seam がインスタンスを生成する時には、 必ずコンポーネントの デフォルトコンテキスト にあるコンテキスト変数に新しいインスタンスをバインドします。 デフォルトコンテキストは @Scope アノテーションを使用して定義されます。 User Bean はセッションスコープのコンポーネントです。

(4)

EJB 標準 @Table アノテーションは、 User クラスが users テーブルにマッピングされることを示しています。

(5)

namepasswordusername は、 エンティティ Bean の永続属性です。 JBoss の永続属性はすべてアクセスメソッドを定義しています。 レスポンスのレンダリングフェーズおよびモデル値の更新フェーズで JSF によりこのコンポーネントが使用されるときに必要となります。

(6)

空コンストラクタは、EJB と Seam の両方の仕様から必要となります。

(7)

@NotNull@Length アノテーションは、 Hibernate Validator フレームワークの一部です。 Seam は Hibernate Validator を統合しているため、 データの妥当性検証にこれを使用することができます (永続性に Hiberenate を使用していない場合でも使用できる)。

(8)

EJB 標準 @Id アノテーションは、 エンティティ Bean の主キーであることを示しています。

このサンプルで、もっとも注目してほしい重要なものは @Name@Scope アノテーションです。 このアノテーションは、 このクラスが Seam コンポーネントであることを規定しています。

以下では、 User クラスのプロパティは 直接 JSF コンポーネントにバインドされ、 モデル値の変更フェーズで JSF によって投入されることがわかります。 JSP ページとエンティティ Bean ドメインモデル間を行き来するデータのコピーに面倒なコードは必要ありません。

しかし、 エンティティ Bean はトランザクション管理やデータベースアクセスを行わないはずなので、 このコンポーネントを JSF のアクションリスナとしては使用できません。 このため、 セッション Bean が必要となります。

1.2.1.2. ステートレスセッション Bean クラス: RegisterAction.java

ほとんどの Seam アプリケーションは、セッション Bean を JSF アクションリスナとして使用します。 (好みに応じて JavaBean を使うことも可能)

アプリケーション内の JSF アクションは正確に 1 つのみで、 これにセッションBean メソッドが 1 つリンクしています。 このサンプルでは、 アクションに関連する状態はすべて User Bean によって保持されるため、 ステートレスセッション Bean を使用しています。

サンプルの中で、本当に注意すべきコードは以下のみです。

例 1.2.

@Stateless                                                                               (1)
@Name("register")
public class RegisterAction implements Register
{

   @In                                                                                   (2)
   private User user;
   
   @PersistenceContext                                                                   (3)
   private EntityManager em;
   
   @Logger                                                                               (4)
   private Log log;
   
   public String register()                                                              (5)
   {
      List existing = em.createQuery(
         "select username from User where username=#{user.username}")                    (6)
         .getResultList();
         
      if (existing.size()==0)
      {
         em.persist(user);
         log.info("Registered new user #{user.username}");                               (7)
         return "/registered.jsp";                                                       (8)
      }
      else
      {
         FacesMessages.instance().add("User #{user.username} already exists");           (9)
         return null;
      }
   }

}
(1)

EJB 標準 @Stateless アノテーションは、 このクラスをステートレスセッション Bean としてマークしています。

(2)

@In アノテーションは、 Seam によってインジェクトされる Bean の属性としてマークしています。 ここで、この属性は、user (インスタンス変数名) という名前のコンテキスト変数からインジェクトされます。

(3)

EJB 標準 @PersistenceContext アノテーションは、 EJB3 エンティティ Entity Manager にインジェクトするために使用されます。

(4)

Seam @Logger アノテーションは、 コンポーネントの Log インスタンスをインジェクトするために使用されています。

(5)

アクションリスナメソッドは、データベースとやり取りするために、 標準 EJB3 EntityManager API を使用し、JSF 結果 (outcome) を返します。 これはセッション Bean なので、 register() メソッドが呼ばれたときに、 トランザクションは自動的に開始され、終了したときにコミットされることに留意してください。

(6)

Seam では EJB-QL 内で JSF EL 式を使用することができます。 バックグラウンドで行われるため見えませんが、 これにより普通の JPA setParameter() が標準 JPA Query オブジェクトを呼び出すことになります。 便利でしょう?

(7)

Log API は、テンプレート化されたログメッセージを容易に表示可能です。

(8)

JSF アクションリスナメソッドは、次にどのページを表示するかを決定するストリング値の結果 (outcome) を返します。 null 結果 (outcome) (あるいは、void アクションリスナメソッド) は、 前のページを再表示します。 普通の JSF では、 結果 (outcome) から JSF view id を決定するために、 常に JSF ナビゲーション規則 を使用することが普通です。 複雑なアプリケーションにとって、この間接的命令は、実用的な良い慣行です。 しかし、このようなとても簡単なサンプルのために、 Seam は、結果 (outcome) として JSF view id の使用を可能とし、 ナビゲーション規則の必要性を取り除きました。 結果 (outcome) として view id を使用する場合、 Seam は、常にブラウザリダイレクトを行うことに留意してください。

(9)

Seam は、共通な問題の解決を支援するために多くの 組み込みコンポーネントを提供しています。 FacesMessages コンポーネントは、 テンプレート化されたエラーや成功メッセージを容易に表示可能です。 組み込み Seam コンポーネントは、 インジェクションあるいは、instance() メソッド呼び出しによって取得可能です。

ここで、@Scope を明示的に指定していないことに留意してください。 各 Seam コンポーネントタイプは、明示的にスコープが指定されない場合、 デフォルトのスコープが適用されます。 ステートレスセッション Bean のデフォルトスコープは、ステートレスコンテキストです。 実際、すべてのステートレスセッション Bean は、 ステートレスコンテキストに属します。

このセッション Bean のアクションリスナは、この小さなアプリケーションのために、 ビジネスロジックと永続ロジックを提供しています。 さらに複雑なアプリケーションでは、 コードを階層化し永続ロジックが専門のデータアクセスコンポーネントとなるようにリファクタリングする必要があるかもしれません。 これをするのは簡単ですが、 Seam は、アプリケーションの階層化のために特殊な方法を強要していないことに留意してください。

さらに、このセッション Bean は WEB リクエスト (例えば、 User オブジェクト内のフォームの値) に関連するコンテキストにアクセスすると同時に、 トランザクションリソース (EntityManager オブジェクト) で保持される状態にもアクセスすることに注目してください。 従来の J2EE アーキテクチャからの分岐点になります。 繰り返しますが、 従来の J2EE の階層化の方が使い易ければ、 そちらの方を Seam アプリケーションに実装することもできます。 ただし、 多くのアプリケーションにとってはあまり役に立ちません。

1.2.1.3. セッション Bean ローカルインタフェース : Register.java

当然、セッション Bean には、ローカルインタフェースが必要です。

例 1.3.

@Local
public interface Register
{
   public String register();
}

Javaコードは以上です。続いて配備記述子です。

1.2.1.4. Seam コンポーネント配備記述子 : components.xml

既に多くの Java フレームワークを使用した経験がある方なら、 プロジェクトが成熟するにつれ徐々に大きくなり管理し難くなる XML ファイルにコンポーネントクラスをすべてを宣言することにもそのうち慣れていくことでしょう。 Seam ではアプリケーションコンポーネントに XML が付随する必要がないこと知ったら、 きっとほっとすることでしょう。 大部分の Seam アプリケーションは、ほんの少しの XML しか必要としません。 また、 この XMLはプロジェクトが大きくなっていっても、 あまり大きくなりません。

それにもかかわらず、ある コンポーネントの ある 外部設定の規定が可能であることは、 多くの場合、有用です。 (特に、Seam に組み込まれたコンポーネント) ここで、2 つの選択があります。 しかし、最も柔軟性のある選択は、 WEB-INF ディレクトリに位置する components.xml と呼ばれるファイルに設定を規定することです。 Seam に、JNDI で EJB コンポーネントの見つけ方を指示するためには、components.xml ファイルを使用します。

例 1.4.

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core">
     <core:init jndi-pattern="@jndiPattern@"/>
</components>

このコードは、org.jboss.seam.core.init という名前の Seam コンポーネントの jndiPattern という名前のプロパティを設定します。

1.2.1.5. WEB 配備記述子 : web.xml

この小さなアプリケーションのプレゼンテーション層はWARにデプロイされます。 従って、WEB 配備記述子が必要です。

例 1.5.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- Seam -->

    <listener>
        <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
    </listener>

    <!-- MyFaces -->

    <listener>
        <listener-class>
            org.apache.myfaces.webapp.StartupServletContextListener
        </listener-class>
    </listener>

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Faces Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.seam</url-pattern>
    </servlet-mapping>

</web-app>

この web.xml ファイルは、Seam と MyFaces を設定します。 ここで見る設定は、Seam アプリケーションではいつも同じです。

1.2.1.6. JSF 設定 : faces-config.xml

すべての Seam アプリケーションはプレゼンテーション層として JSF ビューを使用します。 従って、faces-config.xml が必要です。

例 1.6.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config 
PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
                            "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>

    <!-- A phase listener is needed by all Seam applications -->
    
    <lifecycle>
        <phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
    </lifecycle>

</faces-config>

faces-config.xml ファイルは、Seam を JSF に統合します。 JSF 管理 Bean 宣言が不要なことに留意してください。 JSF 管理 Bean は、Seam コンポーネントです。 普通の JSF アプリケーションと比較すると、 Seam アプリケーションは、faces-config.xml をほとんど使用しません。

実際、基本的な記述子の設定だけあれば、 新しい機能を Seam アプリケーションに追加するときに必要となる XML は、 ナビゲーション規則とたぶんjBPM プロセス定義 だけです。 Seam は、XML に記された プロセスフロー設定データからビューを取得します。

この簡単なサンプルでは、 view id をアクションコードに埋め込んだため、 ナビゲーション規則さえ不要です。

1.2.1.7. EJB 配備記述子 : ejb-jar.xml

ejb-jar.xml ファイルは、 アーカイブ中のすべてのセッション Bean に SeamInterceptor を付加することによって EJB3 と統合します。

<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
         version="3.0">
         
   <interceptors>
     <interceptor>
       <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
     </interceptor>
   </interceptors>
   
   <assembly-descriptor>
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
      </interceptor-binding>
   </assembly-descriptor>
   
</ejb-jar>

1.2.1.8. EJB 永続配備記述子 : persistence.xml

persistence.xml ファイルは、EJB 永続プロバイダに、 データソースの場所を指示します。また、ベンダー特有の設定を含んでいます。 このサンプルでは起動時に自動スキーマエキスポートを可能としています。

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" 
             version="1.0">
    <persistence-unit name="userDatabase">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
    </persistence-unit>
</persistence>

1.2.1.9. ビュー : register.jspregistered.jsp

Seam アプリケーションのビューページは、 JSF をサポートする多くの技術を使用して実装されています。 JSP は多くの開発者にとって知られていること、ここでは最小限の要件しかないため、 このサンプルでは、JSP を使用しています。 (ただし、 JBoss のアドバイスに従っている場合は、 ご使用のアプリケーションには Facelet を使用しているでしょう。)

例 1.7.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
 <head>
  <title>Register New User</title>
 </head>
 <body>
  <f:view>
   <h:form>
     <table border="0">
       <s:validateAll>
         <tr>
           <td>Username</td>
           <td><h:inputText value="#{user.username}"/></td>
         </tr>
         <tr>
           <td>Real Name</td>
           <td><h:inputText value="#{user.name}"/></td>
         </tr>
         <tr>
           <td>Password</td>
           <td><h:inputSecret value="#{user.password}"/></td>
         </tr>
       </s:validateAll>
     </table>
     <h:messages/>
     <h:commandButton type="submit" value="Register" action="#{register.register}"/>
   </h:form>
  </f:view>
 </body>
</html>

ここで Seam 固有となるのは <s:validateAll> タグのみです。 この JSF コンポーネントは 含まれるすべての入力フィールドをエンティティbean で指定される Hibernate Validator アノテーションに対して検証するよう JSF に指示しています。

例 1.8.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
 <head>
  <title>Successfully Registered New User</title>
 </head>
 <body>
  <f:view>
    Welcome, <h:outputText value="#{user.name}"/>, 
    you are successfully registered as <h:outputText value="#{user.username}"/>.
  </f:view>
 </body>
</html>

これは、標準 JSF コンポーネントを使用した何の変哲もない旧式の JSP ページです。 Seam 独特のものはありません。

1.2.1.10. EAR 配備記述子 : application.xml

最後に、EARとして アプリケーションがデプロイされるため、配備記述子も必要になります。

例 1.9.

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"
             version="5">
             
    <display-name>Seam Registration</display-name>

    <module>
        <web>
            <web-uri>jboss-seam-registration.war</web-uri>
            <context-root>/seam-registration</context-root>
        </web>
    </module>
    <module>
        <ejb>jboss-seam-registration.jar</ejb>
    </module>
    <module>
        <java>jboss-seam.jar</java>
    </module>
    <module>
        <java>el-api.jar</java>
    </module>
    <module>
        <java>el-ri.jar</java>
    </module>
    
</application>

この配備記述子はエンタープライズアーカイブのモジュールとリンクし、 WEBアプリケーションをコンテキストルート /seam-registration にバインドします。

エンタープライズアプリケーション中のすべてのファイルを見終わりました。

1.2.2. 動作内容

フォームがサブミットされたとき、 JSF は、Seam に user という名前の変数を解決するよう要求します。 その名前にバインドされた値が存在しないため (どの Seam コンテキストにも)、 Seam は、user コンポーネントをインスタンス化し、 それを Seam セッションコンテキストに保管した後に、 User エンティティ Bean インスタンスを JSF に返します。

フォームの入力値は、 User エンティティで指定された Hibernate Validator 制約に対してデータ整合性検証が行われるようになります。 制約に違反していると JSF はそのページを再表示します。 これ以外は、 フォームの入力値を User エンティティ Bean のプロパティにバインドします。

次に、JSF は Seam に変数名 register の解決を要求します。 Seam は、ステートレスコンテキスト中の RegisterAction ステートレスセッション Bean を見つけ、 それを返します。 JSF は、register() アクションリスナメソッドを呼び出します。

この呼び出しを続行する前に、 Seam はメソッドコールをインターセプトし、 Seam セッションコンテキストから User エンティティをインジェクトします。

register() メソッドは入力されたユーザ名が既に存在するかどうかを調べます。 存在した場合、 エラーメッセージは FacesMessages コンポーネントでキューイングされ、 null 結果 (outcome) が返されてページが再表示されることになります。 FacesMessages コンポーネントはメッセージ文字列に組み込まれた JSF 式を補完し、 view に JSF FacesMessage を追加します。

そのユーザ名のユーザが存在しない場合は、 "/registered.jsp" 結果 (outcome) により registered.jsp ページへのブラウザリダイレクトが発生します。 JSF がページのレンダリングに到達すると、 Seam に user という名前の変数の解決を要求し、 Seam のセッションスコープから返される User エンティティのプロパティ値を使用します。

1.3. Seam でクリックが可能な一覧: 掲示板サンプル

データベースの検索結果をクリックが可能な一覧にすることは、 いずれのオンラインアプリケーションにおいてもたいへん重要な部分となります。 Seam は JSF に加えさらに特殊な機能を提供することで、 EJB-QL または HQL を使ったデータのクエリを容易にし、 JSF <h:dataTable> を使ったクリック可能な一覧としての表示を実現します。 この掲示板サンプルは、 この機能を実演しています。

1.3.1. コードの理解

この掲示板サンプルは、 1 つのエンティティ Bean である Message、 1 つのセッション Bean である MessageListBean、 そして 1 つの JSP から構成されています。

1.3.1.1. エンティティ Bean : Message.java

Message エンティティ Bean は、 タイトル、テキスト、掲示メッセージの日付と時間、 そして、メッセージが既読か否かを示すフラグを定義しています。

例 1.10.

@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
   private Long id;
   private String title;
   private String text;
   private boolean read;
   private Date datetime;
   
   @Id @GeneratedValue
   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }
   
   @NotNull @Length(max=100)
   public String getTitle() {
      return title;
   }
   public void setTitle(String title) {
      this.title = title;
   }
   
   @NotNull @Lob
   public String getText() {
      return text;
   }
   public void setText(String text) {
      this.text = text;
   }
   
   @NotNull
   public boolean isRead() {
      return read;
   }
   public void setRead(boolean read) {
      this.read = read;
   }
   
   @NotNull 
   @Basic @Temporal(TemporalType.TIMESTAMP)
   public Date getDatetime() {
      return datetime;
   }
   public void setDatetime(Date datetime) {
      this.datetime = datetime;
   }
   
}

1.3.1.2. ステートフルセッション Bean : MessageManagerBean.java

前述のサンプル同様、 1 つのセッション Bean MessageManagerBean があります。 それは、フォームにある 2 つのボタンに対応するアクションリスナメソッドを定義しています。 ボタンの 1 つは、一覧からメッセージを選択し、 もう 1 つのボタンは、メッセージを削除します。 この点において、前述のサンプルと大きな違いはありません。

しかし、 初めて掲示板ページに画面遷移するとき、 MessageManagerBean は、メッセージ一覧の取得も行います。 ユーザが画面を遷移させる方法はさまざまありますが、 これらのすべてが JSF アクションによって進められるわけではありません — 例えば、 ユーザがそのページをブックマークしているかもしれません。 従って、メッセージ一覧を取得する作業は、 アクションリスナメソッドの代わりに、 Seam factory method で行われます。

メッセージの一覧をサーバリクエストにまたがってメモリにキャッシュしたいので、 ステートフルセッション Bean でこれを行います。

例 1.11.

@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean implements Serializable, MessageManager
{

   @DataModel                                                                            (1)
   private List<Message> messageList;
   
   @DataModelSelection                                                                   (2)
   @Out(required=false)                                                                  (3)
   private Message message;
   
   @PersistenceContext(type=EXTENDED)                                                    (4)
   private EntityManager em;
   
   @Factory("messageList")                                                               (5)
   public void findMessages()
   {
      messageList = em.createQuery("from Message msg order by msg.datetime desc").getResultList();
   }
   
   public void select()                                                                  (6)
   {
      message.setRead(true);
   }
   
   public void delete()                                                                  (7)
   {
      messageList.remove(message);
      em.remove(message);
      message=null;
   }
   
   @Remove @Destroy                                                                      (8)
   public void destroy() {}

}
(1)

@DataModel アノテーションは、 java.util.List タイプの属性を、 javax.faces.model.DataModel インスタンスとして JSF ページに公開します。 これは、各行に対してクリック可能なリンクを持つ JSF <h:dataTable> 中の一覧を使用可能とします。 このサンプルでは、 DataModel は、 messageList という名前のセッションコンテキスト中で利用可能になります。

(2)

@DataModelSelection アノテーションは、 Seam にクリックされたリンクと関連した List 要素をインジェクトするよう指示しています。

(3)

@Outアノテーションは、次に選択された値を直接ページに公開します。 従って、クリック可能一覧の行が選択されるたびに、 Message は、ステートフル Bean の属性にインジェクションされ、 続いて message という名前のイベントコンテキスト変数にアウトジェクションされます。

(4)

このステートフル Bean は、EJB3 拡張永続コンテキスト を持っています。 この Bean が存在する限り、 クエリー検索された messages は、管理された状態に保持されます。 従って、 それに続くステートフル Bean へのメソッド呼び出しは、 明示的に EntityManager を呼び出すことなく、 それらの更新が可能です。

(5)

初めて JSP ページに画面遷移するとき、 messageList コンテキスト変数中に値を持っていません。 @Factory アノテーションは、Seam に MessageManagerBean インスタンスの生成を指示し、 初期値を設定するために findMessages() メソッドを呼び出します。 findMessages()messagesファクトリーメソッドと呼びます。

(6)

select() アクションリスナメソッドは、 選択された Messageに 既読 マークを付け、 データベース中のそれを更新します。

(7)

delete() アクションリスナメソッドは、 選択された Message をデータベースから削除します。

(8)

すべてのステートフルセッション Bean の Seam コンポーネントは、 @Remove @Destroy とマークされたメソッドを持つことが 必須 です。 これにより、Seam コンテキストが終わり、サーバサイドのあらゆる状態をクリーンアップするときに、 Seam は、確実にステートフル Bean の削除を行います。

これがセッションスコープの Seam コンポーネントであることに留意してください。 ユーザログインセッションと関連しログインセッションからのすべてのリクエストは、 同じコンポーネントのインスタンスを共有します。 (Seam アプリケーションでは、セッションスコープのコンポーネントは控えめに使用してください。)

1.3.1.3. セッション Bean ローカルインタフェース : MessageManager.java

もちろん、すべてのセッション Bean はインタフェースを持ちます。

@Local
public interface MessageManager
{
   public void findMessages();
   public void select();
   public void delete();
   public void destroy();
}

ここからは、サンプルコード中のローカルインタフェースの掲載を省略します。

components.xmlpersistence.xmlweb.xmlejb-jar.xmlfaces-config.xml、 そして application.xml は、前述までのサンプルとほぼ同じなので、スキップして JSP に進みましょう。

1.3.1.4. ビュー: messages.jsp

このJSPページは JSF <h:dataTable> コンポーネントを使用した簡単なものです。 Seam として特別なものはありません。

例 1.12.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
 <head>
  <title>Messages</title>
 </head>
 <body>
  <f:view>
   <h:form>
     <h2>Message List</h2>
     <h:outputText value="No messages to display" rendered="#{messageList.rowCount==0}"/>
     <h:dataTable var="msg" value="#{messageList}" rendered="#{messageList.rowCount>0}">
        <h:column>
           <f:facet name="header">
              <h:outputText value="Read"/>
           </f:facet>
           <h:selectBooleanCheckbox value="#{msg.read}" disabled="true"/>
        </h:column>
        <h:column>
           <f:facet name="header">
              <h:outputText value="Title"/>
           </f:facet>
           <h:commandLink value="#{msg.title}" action="#{messageManager.select}"/>
        </h:column>
        <h:column>
           <f:facet name="header">
              <h:outputText value="Date/Time"/>
           </f:facet>
           <h:outputText value="#{msg.datetime}">
              <f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/>
           </h:outputText>
        </h:column>
        <h:column>
           <h:commandButton value="Delete" action="#{messageManager.delete}"/>
        </h:column>
     </h:dataTable>
     <h3><h:outputText value="#{message.title}"/></h3>
     <div><h:outputText value="#{message.text}"/></div>
   </h:form>
  </f:view>
 </body>
</html>

1.3.2. 動作内容

最初に、 messages.jsp ページに画面遷移させるとき、 JSF ポストバック (faces リクエスト) でも、 ブラウザからの直接的な GET リクエスト (non-faces リクエスト) でも、 ページは、messageList コンテキスト変数を解決しようと試みます。 このコンテキスト変数は、初期化されていないため、 Seam は、ファクトリーメソッド findMessages()を呼び出します。 それは、データベースにクエリー発行や、 アウトジェクト (outject) された DataModel の結果取得を行います。 この DataModel は、 <h:dataTable> をレンダリングするために必要な行データを提供します。

ユーザが <h:commandLink> をクリックすると、 JSF は select() アクションリスナを呼び出します。 Seam はこの呼び出しをインターセプトして選択された行データを messageManager コンポーネントの message 属性にインジェクトします。 アクションリスナが実行されて、 選択 Message を既読マークを付けます。 呼び出しの終わりに、 Seam は、選択 Messagemessage という名前のコンテキスト変数にアウトジェクトします。 次に、 EJB コンテナはトランザクションをコミットし、 Message に対する変更がデータベースにフラッシュされます。 最後に、 このページが再度レンダリングされてメッセージ一覧を再表示、 その下に選択メッセージが表示されます。

ユーザが <h:commandButton> をクリックすると、 JSF は、delete() アクションリスナを呼び出します。 Seam はこの呼び出しをインターセプトし、 選択された行データを messageList コンポーネントの message 属性にインジェクトします。 アクションリスナが起動し、 選択 Message が一覧から削除され、 EntityManagerremove() が呼び出されます。 呼び出しの終わりに、 Seam は messageList コンテキスト変数を更新し、 message という名前のコンテキスト変数を消去します。 EJB コ ンテナはトランザクションをコミットし、 データベースから Message を削除します。 最後に、 このページが再度レンダリングされ、 メッセージ一覧を再表示します。

1.4. Seam と jBPM : TO-DO 一覧サンプル

jBPM は、ワークフローやタスク管理の優れた機能を提供します。 どのように jBPM が Seam と統合されているかを知るために、 簡単な To-Do 一覧アプリケーションをお見せしましょう。 タスクの一覧を管理することは、jBPM の中心的な機能であるため、 このサンプルには Java コードがほとんどありません。

1.4.1. コードの理解

このサンプルの中心は、jBPM のプロセス定義です。 2 つの JSP と 2 つのちょっとした JavaBean もあります。 (データベースアクセスやトランザクション特性がないので、 セッション Bean を使用する理由はありません。) それではプロセス定義から始めましょう。

例 1.13.

<process-definition name="todo">
   
   <start-state name="start">                                                            (1)
      <transition to="todo"/>
   </start-state>
   
   <task-node name="todo">                                                               (2)
      <task name="todo" description="#{todoList.description}">                           (3)
         <assignment actor-id="#{actor.id}"/>                                            (4)
      </task>
      <transition to="done"/>
   </task-node>
   
   <end-state name="done"/>                                                              (5)
   
</process-definition>
(1)

<start-state> ノードはプロセスの論理的な開始を表します。 プロセスが開始すると、 直ちに todo ノードに遷移します。

(2)

<task-node> ノードは 待ち状態 を表します。 ビジネスプロセスの実行が一時停止され、 1 つまたは複数のタスクが行われるのを待機します。

(3)

<task> 要素は、ユーザによって実行されるタスクを定義します。 このノードには 1 つのタスクしか定義されていないので、 それが完了すると実行が再開され、 終了状態に遷移していきます。 このタスクは、 todoList という名前の Seam コンポーネント (JavaBean の 一種) から description を取得します。

(4)

タスクが生成されたとき、タスクにはユーザあるいはユーザグループを割り当てる必要があります。 このサンプルでは、タスクは、現在のユーザに割り当てられています。 それは、actor という名前の組み込み Seam コンポーネントから取得します。 どのような Seam コンポーネントもタスク割り当てを実行するために使用される可能性があります。

(5)

<end-state>ノードは、ビジネスプロセスの論理的な終了を定義します。 実行がこのノードに到達したとき、 プロセスインスタンスは破棄されます。

JBossIDE に提供されたプロセス定義エディタを使用してプロセス定義を見た場合、 以下のようになります。

このドキュメントは、ノードのグラフとして ビジネスプロセス を定義します。 これはささいな現実にあり得るビジネスプロセスです。 実行されなければならない タスク は、1 つだけです。 タスクが完了したとき ビジネスプロセスは終了します。

最初の JavaBean はログイン画面 login.jsp を処理します。 処理作業は、 単に actor コンポーネントを使用して jBPM actor id を初期化するだけです。 (実際のアプリケーションでは、 ユーザ認証も必要です。)

例 1.14.

@Name("login")
public class Login {
   
   @In
   private Actor actor;
   
   private String user;

   public String getUser() {
      return user;
   }

   public void setUser(String user) {
      this.user = user;
   }
   
   public String login()
   {
      actor.setId(user);
      return "/todo.jsp";
   }
}

ここでは、組み込み Actor コンポーネントをインジェクトするために、 @In を使用しているのがわかります。

次の JSP 自体は重要ではありません。

例 1.15.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<f:view>
    <h:form>
      <div>
        <h:inputText value="#{login.user}"/>
        <h:commandButton value="Login" action="#{login.login}"/>
      </div>
    </h:form>
</f:view>
</body>
</html>

2 つめの JavaBean は、ビジネスプロセスインスタンスの開始とタスクの終了を担当します。

例 1.16.

@Name("todoList")
public class TodoList {
   
   private String description;
   
   public String getDescription()                                                        (1)
   {
      return description;
   }

   public void setDescription(String description) {
      this.description = description;
   }
   
   @CreateProcess(definition="todo")                                                     (2)
   public void createTodo() {}
   
   @StartTask @EndTask                                                                   (3)
   public void done() {}

}
(1)

description プロパティは、JSP ページからユーザ入力を受け取り、 タスク内容が設定されるように、それをプロセス定義に公開します。

(2)

Seam @CreateProcess アノテーションは、名前付きプロセス定義のために jBPM プロセスインスタンスを生成します。

(3)

Seam @StartTask アノテーションは、タスク上で作業を開始します。 @EndTask は、タスクを終了し、ビジネスプロセスの再開を可能にします。

現実的なサンプルでは、 @StartTask@EndTask が同じメソッド上に出現することはありません。 なぜなら、 通常はそのタスクを完了するために、 アプリケーションを使用して行うべき作業があるからです。

最後に、このアプリケーションのポイントは todo.jsp にあります。

例 1.17.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<f:view>
   <h:form id="list">
      <div>
         <h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
         <h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Description"/>
                </f:facet>
                <h:inputText value="#{task.description}"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Created"/>
                </f:facet>
                <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
                    <f:convertDateTime type="date"/>
                </h:outputText>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Priority"/>
                </f:facet>
                <h:inputText value="#{task.priority}" style="width: 30"/>
            </h:column>
            <h:column>
                <f:facet name="header">
                    <h:outputText value="Due Date"/>
                </f:facet>
                <h:inputText value="#{task.dueDate}" style="width: 100">
                    <f:convertDateTime type="date" dateStyle="short"/>
                </h:inputText>
            </h:column>
            <h:column>
                <s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
            </h:column>
         </h:dataTable>
      </div>
      <div>
      <h:messages/>
      </div>
      <div>
         <h:commandButton value="Update Items" action="update"/>
      </div>
   </h:form>
   <h:form id="new">
      <div>
         <h:inputText value="#{todoList.description}"/>
         <h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
      </div>
   </h:form>
</f:view>
</body>
</html>

1 つずつ見ていきます。

ページはタスク一覧をレンダリングしています。 これは、taskInstanceList と呼ばれる Seam 組み込みコンポーネントから取得します。 この一覧はJSFフォームの中に定義されています。

<h:form id="list">
   <div>
      <h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
      <h:dataTable value="#{taskInstanceList}" var="task" rendered="#{not empty taskInstanceList}">
         ...
      </h:dataTable>
   </div>
</h:form>

一覧の各要素は jBPM クラス TaskInstance のインスタンスです。 以下のコードは単に、一覧中の各タスクの興味深いプロパティを表示しています。 記述内容、 優先順や、 納期の値については、 ユーザーがこれらの値を更新できるよう入力コントロールを使用します。

<h:column>
    <f:facet name="header">
       <h:outputText value="Description"/>
    </f:facet>
    <h:inputText value="#{task.description}"/>
</h:column>
<h:column>
    <f:facet name="header">
        <h:outputText value="Created"/>
    </f:facet>
    <h:outputText value="#{task.taskMgmtInstance.processInstance.start}">
        <f:convertDateTime type="date"/>
    </h:outputText>
</h:column>
<h:column>
    <f:facet name="header">
        <h:outputText value="Priority"/>
    </f:facet>
    <h:inputText value="#{task.priority}" style="width: 30"/>
</h:column>
<h:column>
    <f:facet name="header">
        <h:outputText value="Due Date"/>
    </f:facet>
    <h:inputText value="#{task.dueDate}" style="width: 100">
        <f:convertDateTime type="date" dateStyle="short"/>
    </h:inputText>
</h:column>

このボタンは、 @StartTask @EndTask アノテーション付きのアクションメソッドが呼び出されることにより終了します。 それは、task id をリクエストパラメータとして Seam に渡します。

<h:column>
    <s:button value="Done" action="#{todoList.done}" taskInstance="#{task}"/>
</h:column>

(seam-ui.jar パッケージから、 Seam <s:button> JSF コントロールを使用していることに留意してください。)

このボタンは、 タスクのプロパティを変更するために使用されます。 フォームがサブミットされたとき、 Seam と jBPM は、タスクに対するどのような変化も永続化します。 アクションリスナメソッドは全く不要です。

<h:commandButton value="Update Items" action="update"/>

ページの 2 つ目のフォームは新しいアイテムを作成するために使用されます。 @CreateProcessアノテーション付きアクションメソッドから呼び出されることにより行われます。

<h:form id="new">
    <div>
        <h:inputText value="#{todoList.description}"/>
        <h:commandButton value="Create New Item" action="#{todoList.createTodo}"/>
    </div>
</h:form>

このサンプルには、その他いくつか必要なファイルがありますが、 それらは 標準的な jBPM や Seam の設定であり特殊なものはありません。

1.4.2. 動作内容

TODO

1.5. Seam ページフロー: 数字当てゲームサンプル

比較的自由な (アドホック) 画面遷移をさせる Seam アプリケーションの場合、 JSF/Seam ナビゲーション規則がページフローを定義するのに最適な方法となります。 画面遷移に制約が多いスタイルのアプリケーションの場合、 特によりステートフルなユーザインタフェースの場合、 ナビゲーション規則ではシステムの流れを本当に理解するのは困難になります。 フローを理解するには、 ビューページ、 アクション、 ナビゲーション規則からフローに関する情報をかき集める必要があります。

Seam は、jPDL プロセス定義を使うことでページフロー定義を可能にします。 この簡単な数字当てゲームサンプルからどのようにこれが実現されているかがわかります。

1.5.1. コードの理解

このサンプルは 1 つのJavaBean、3 つの JSP ページ、それと jPDL プロセスフロー定義で実装されています。 ページフローから見始めましょう。

例 1.18.

<pageflow-definition name="numberGuess">
   
   <start-page name="displayGuess" view-id="/numberGuess.jsp">
      <redirect/>
      <transition name="guess" to="evaluateGuess">
          <action expression="#{numberGuess.guess}" />
      </transition>                                                                      (1)
   </start-page>                                                                         (2)
                                                                                         (3)
   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
      <transition name="true" to="win"/>
      <transition name="false" to="evaluateRemainingGuesses"/>
   </decision>                                                                           (4)
   
   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
      <transition name="true" to="lose"/>
      <transition name="false" to="displayGuess"/>
   </decision>
   
   <page name="win" view-id="/win.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
   <page name="lose" view-id="/lose.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
</pageflow-definition>
(1)

<page> 要素は、待ち状態を定義しています。 ここでは、システムは特定の JSF ビューを表示し、ユーザ入力を待っています。 view-id は普通の JSF ナビゲーション規則で使用されている JSF view id と同じものです。 ページが画面遷移するときに、 redirect 属性は、Seam に post-then-redirect の使用を指示しています。 (この結果がブラウザ URL に表示されます。)

(2)

<transition> 要素は JSF 結果 (outcome) に名前を付けます。 JSF アクションがその結果 (outcome) となる場合に、 transition が起動されます。 jBPM transition action が呼び出された後、 実行はページフローグラフの次のノードに進みます。

(3)

transition の <action> は、 jBPM の transitionでそれが起こることを除けば、 JSF action と同じです。 transition action は、どのような Seam コンポーネントでも呼び出すことが可能です。

(4)

<decision> ノードはページフローを分岐させ、 JSF EL 式を評価することによって次に実行されるノードを決定します。

JBossIDE ページフローエディタでのページフローは以下のようになります。

ページフローを見終わりました。 アプリケーションの残りの部分を理解することはもう簡単です。

これはアプリケーションの主要なページ numberGuess.jsp です。

例 1.19.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>Guess a number...</title>
</head>
<body>
<h1>Guess a number...</h1>
<f:view>
    <h:form>
        <h:outputText value="Higher!" rendered="#{numberGuess.randomNumber>numberGuess.currentGuess}" />
        <h:outputText value="Lower!" rendered="#{numberGuess.randomNumber<numberGuess.currentGuess}" />
        <br />
        I'm thinking of a number between <h:outputText value="#{numberGuess.smallest}" /> and 
        <h:outputText value="#{numberGuess.biggest}" />. You have 
        <h:outputText value="#{numberGuess.remainingGuesses}" /> guesses.
        <br />
        Your guess: 
        <h:inputText value="#{numberGuess.currentGuess}" id="guess" required="true">
            <f:validateLongRange
                maximum="#{numberGuess.biggest}" 
                minimum="#{numberGuess.smallest}"/>
        </h:inputText>
        <h:commandButton type="submit" value="Guess" action="guess" />
        <br/>
        <h:message for="guess" style="color: red"/>
    </h:form>
</f:view>
</body>
</html>

アクションを直接呼び出す代わりに、 どのようにコマンドボタンはguess transitionを指定しているかに着目してください。

win.jsp ページはごく普通のものです。

例 1.20.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>You won!</title>
</head>
<body>
<h1>You won!</h1>
<f:view>
    Yes, the answer was <h:outputText value="#{numberGuess.currentGuess}" />.
    It took you <h:outputText value="#{numberGuess.guessCount}" /> guesses.
    Would you like to <a href="numberGuess.seam">play again</a>?
  </f:view>
</body>
</html>

lose.jsp も同様です (同様なので省略します)。 最後は、 JavaBean Seam コンポーネントです。

例 1.21.

@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess {
   
   private int randomNumber;
   private Integer currentGuess;
   private int biggest;
   private int smallest;
   private int guessCount;
   private int maxGuesses;
   
   @Create                                                                               (1)
   @Begin(pageflow="numberGuess")                                                        (2)
   public void begin()
   {
      randomNumber = new Random().nextInt(100);
      guessCount = 0;
      biggest = 100;
      smallest = 1;
   }
   
   public void setCurrentGuess(Integer guess)
   {
      this.currentGuess = guess;
   }
   
   public Integer getCurrentGuess()
   {
      return currentGuess;
   }
   
   public void guess()
   {
      if (currentGuess>randomNumber)
      {
         biggest = currentGuess - 1;
      }
      if (currentGuess<randomNumber)
      {
         smallest = currentGuess + 1;
      }
      guessCount ++;
   }
   
   public boolean isCorrectGuess()
   {
      return currentGuess==randomNumber;
   }
   
   public int getBiggest()
   {
      return biggest;
   }
   
   public int getSmallest()
   {
      return smallest;
   }
   
   public int getGuessCount()
   {
      return guessCount;
   }
   
   public boolean isLastGuess()
   {
      return guessCount==maxGuesses;
   }

   public int getRemainingGuesses() {
      return maxGuesses-guessCount;
   }

   public void setMaxGuesses(int maxGuesses) {
      this.maxGuesses = maxGuesses;
   }

   public int getMaxGuesses() {
      return maxGuesses;
   }

   public int getRandomNumber() {
      return randomNumber;
   }
}
(1)

最初に、JSP ページが numberGuess コンポーネントを要求するとき、 Seam は新しいコンポーネントを生成します。 そして、@Create メソッドが呼ばれ、 コンポーネント自身の初期化が可能になります。

(2)

@Begin アノテーションは、 Seam の対話を開始し (詳細は後述)、 対話のページフローを使用するためのページフロー定義を指定します。

お分かりの通り、この Seam コンポーネントは純粋なビジネスロジックです。 ユーザインタラクションのフローについて理解する必要はまったくありません。 これによりコンポーネント再利用の可能性を向上させます。

1.5.2. 動作内容

TODO

1.6. 本格的 Seam アプリケーション: ホテル予約サンプル

1.6.1. はじめに

この予約アプリケーションは以下の特徴を持つ本格的なホテルの部屋予約システムです。

  • ユーザ登録

  • ログイン

  • ログアウト

  • パスワード設定

  • ホテル検索

  • ホテル選択

  • 部屋予約

  • 予約確認

  • 現状の予約一覧

この予約アプリケーションは JSF、EJB 3.0、Seam とともにビューとして Facelet を使用しています。 JSF、Facelets、Seam、JavaBeans そして、Hibernate3 のアプリケーションの移植版もあります

このアプリケーションをある程度の期間、 いじってわかることの 1 つは、 それがとても 堅牢であることです。 戻るボタンをいじっても、 ブラウザの更新をしても、 複数のウィンドを開いても、 無意味なデータを好きなだけ入力しても、 アプリケーションをクラッシュさせることがとても困難であることがわかります。 これを達成するためにテストやバグ取りに何週間も掛かったと思われるかもしれませんが、 実際にはそんなことはありません。 Seam は、堅牢な WEB アプリケーションを簡単に構築できるように設計されています。 そして、これまでコーディングそのものによって得られていた堅牢性は、 Seam を使用することで自然かつ自動的に得られます。

サンプルアプリケーションのソースコードを見れば、 どのようにアプリケーションが動作しているか習得できます。 そして、この堅牢性を達成するために、 どのように宣言的状態管理や統合されたデータ妥当性検証が使用されているかを見ることができます。

1.6.2. 予約サンプルの概要

プロジェクトの構成は以前のものと同じです。 項1.1. 「サンプルを試そう」 を参照してください。 うまくアプリケーションが起動したならば、 ブラウザから指定して http://localhost:8080/seam-booking/ をアクセスすることが可能です。

ちょうど 9 つのクラス (加えて、6 つのセッション Bean のインタフェースと 1 つのアノテーションのインタフェース) が、 このアプリケーションの実装のために使われています。 6 つのセッション Bean アクションリスナは一覧に記載された特徴のためにすべてのビジネスロジックを含んでいます。

  • BookingListAction は、その時のログインユーザのために現状の予約を取得します。
  • ChangePasswordAction は、その時のログインユーザのパスワードを変更します。
  • HotelBookingAction は、アプリケーションの中核的機能を実装します。 ホテル部屋検索、選択、予約、予約確認。 この機能は、対話 として実装されており、 このアプリケーションではもっとも関心を引くクラスです。
  • RegisterAction は、新しいシステムユーザを登録します。

3 つのエンティティ Bean がアプリケーションの永続ドメインモデルを実装しています。

  • Hotel は、ホテルを表すエンティティ Bean です。
  • Booking は、現状の予約を表すエンティティ Bean です。
  • User は、ホテル予約ができるユーザを表すエンティティ Bean です。

1.6.3. Seam 対話の理解

気が向いたならばソースコードを読まれることをお勧めします。 このチュートリアルでは、特定の機能 (ホテル検索、選択、予約と確認) に集中します。 ユーザの視点から見ると、 ホテルの選択から予約確認までのすべては、1 つの連続した仕事の単位、 つまり 対話 です。 しかし、検索は、対話の一部ではありません。 ユーザは、 異なるブラウザタブで同じ検索結果ページから複数のホテルを選択可能です。

ほとんどの WEB アプリケーションのアーキテクチャは、対話を公開するための構造を持っていません。 対話に関連する状態管理に膨大な問題を引き起こすためです。 通常、Java WEB アプリケーションは 2 つの技術を組み合わせて使用します。 最初に、なんらかの状態が HttpSession に投げられ、 2 番目に、リクエストの後に必ず永続可能な状態がデータベースに書き込みされてから、 新規リクエストそれぞれの冒頭で永続可能な状態がデータベースから再構築されます。

データベースは最もスケーラビリティに乏しい層なので、 許容不能なほどスケーラビリティに乏しい結果となることもよくあります。 リクエストごとデータベースを行き来する転送量が増加するため、 追加待ち時間も問題となります。 この冗長な転送量を減少させるために、 Java アプリケーションではリクエスト間でよくアクセスされるデータを保管するデータキャッシュ (2 次レベル) を導入する場合がよくあります。 このキャッシュは、必ずしも効率的ではありません。 なぜならデータが無効かどうかの判断をユーザがデータの操作を終了したかどうかを基にして行うのではなく、 LRU ポリシーをベースとして行うためです。 さらに、 キャッシュは多くの並列トランザクション間で共有されるので、 キャッシュされた状態とデータベース間の一貫性維持に関する多くの問題をも取り入れてしまうことになるためです。

さて、HttpSession に保管された状態を考察してみましょう。 非常に注意深くプログラミングを行うことにより、 セッションデータのサイズを制御できる場合があるかもしれませんが、 想像するよりずっと困難です。 なぜならば、 WEB ブラウザは非線形な画面操作を臨機応変に許可しているためです。 しかし、システムの開発途中で、 ユーザは複数の並列対話を持つことを許されると述べられたシステム要件を突然見つけたとします (私も同様の状況に遭遇しました)。 別々の並列対話に関連するセッションステートを分離するメカニズムを開発すること、 そhしてブラウザウィンドウやタブを閉じることでユーザーが対話のいずれかを中止する場合に対話ステートが必ず破棄されるようフェールセーフを組み込むことは、 気が小さくてはできないでしょう。 (私は 2 度ほど実装したことがあります。 1 つはクライアントアプリケーション、1 つはSeam でした。 だけど、私は病的なことで有名です。)

さらによい方法があります。

Seam はファーストクラスの構造として 対話コンテキストを導入しています。 このコンテキスト内では対話状態を安全に維持することができ、 また十分に定義されたライフサイクルが必ず持たされます。 さらに、 対話コンテキストはユーザーが現在作業しているデータの自然なキャッシュとなるため、 アプリケーションサーバーとデータベース間でデータを継続的に行き来させる必要がありません。

通常、対話コンテキスト中で保持するコンポーネントはステートフルセッション Bean です。 (対話コンテキスト中でエンティティ Bean や JavaBean を保持することもできます。) Java コミュニティには、ステートフルセッション Bean が スケーラビリティを無効にしてしまうというデマが古くからあります。 1988年に WebFoobar 1.0 がリリースされた頃は真実だったかもしれませんが、 今日ではもはや真実ではありません。 JBoss 4.0 のようなアプリケーションサーバーはステートフルセッション Bean ステートの複製に対して非常に優れたメカニズムを持っています。 (例えば、 JBoss EJB3 コンテナは微細な部分まで複製を行い、 実際に変化した bean 属性値のみの複製を行います。) なぜステートフル Bean が非効率的かに関する従来の技術的な議論はすべて HttpSession にも等しく当てはまります。 従って、 パフォーマンスを改善するために、 ビジネス層のステートフルセッション Bean から WEB セッションに移行する実践については信じられないほど誤った方向に誘導されているので注意してください。 ステートフルセッション Bean を使う、 ステートフル bean を誤って使う、 ステートフル bean を不正なことに使うなどして拡張性のないアプリケーションを記述することはたしかに可能です。 しかし、 絶対に使うべきではないという意味ではありません。 とにかく、 Seam は安全な使用モデルを目的として案内しています。 ようこそ、 2005 年へ。

それでは、くどくど言うのは止めてチュートリアルに戻りましょう。

この予約サンプルアプリケーションは、 複雑な振る舞いを実現するために、 異なるスコープを持つステートフルコンポーネントがどのように連携することが可能であるかを示しています。 予約アプリケーションのメインページは、 ユーザにホテル検索を可能にしています。 検索結果は、Seam セッションスコープに保持されます。 ユーザがこれらのホテルの 1 つに遷移するとき、 対話は、開始します。 そして、対話スコープのコンポーネントは、 選択されたホテルを取得するために、 セッションスコープのコンポーネントを呼び返します。

手書きの JavaScript を使用することなくリッチクライアントの動作を実装するために、 Ajax4JSF を使用していることも示しています。

検索機能は、セッションスコープのステートフル Bean を使用して実装されます。 それは、上記のメッセージ一覧サンプルに見られるものと同様です。

例 1.22.

@Stateful                                                                                (1)
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@Restrict("#{identity.loggedIn}")                                                        (2)
public class HotelSearchingAction implements HotelSearching
{
   
   @PersistenceContext
   private EntityManager em;
   
   private String searchString;
   private int pageSize = 10;
   private int page;
   
   @DataModel
   private List<Hotel> hotels;                                                           (3)
   
   public String find()
   {
      page = 0;
      queryHotels();   
      return "main";
   }

   public String nextPage()
   {
      page++;
      queryHotels();
      return "main";
   }
      
   private void queryHotels()
   {
      String searchPattern = searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
      hotels = em.createQuery("select h from Hotel h where lower(h.name) like :search or lower(h.city) like :search or lower(h.zip) like :search or lower(h.address) like :search")
            .setParameter("search", searchPattern)
            .setMaxResults(pageSize)
            .setFirstResult( page * pageSize )
            .getResultList();
   }
   
   public boolean isNextPageAvailable()
   {
      return hotels!=null && hotels.size()==pageSize;
   }
   
   public int getPageSize() {
      return pageSize;
   }

   public void setPageSize(int pageSize) {
      this.pageSize = pageSize;
   }

   public String getSearchString()
   {
      return searchString;
   }

   public void setSearchString(String searchString)
   {
      this.searchString = searchString;
   }
   
   @Destroy @Remove
   public void destroy() {}                                                              (4)

}
(1)

EJB 標準 @Stateful アノテーションは、 このクラスがステートフルセッション Bean であることを識別しています。 ステートフルセッション Bean は、 デフォルトで対話コンテキストのスコープを持ちます。

(2)

@Restrict アノテーションはコンポーネントへのセキュリティ制限に適用します。 ログインユーザだけがコンポーネントにアクセスを許されるように制限します。 セキュリティの章では、Seam におけるセキュリティがさらに詳細に説明されています。

(3)

@DataModel アノテーションは、 JSF ListDataModel として List を公開します。 これは、検索画面でのクリック可能一覧の実装を容易にします。 このサンプルでは、 ホテル一覧は、 hotels という名前の対話変数の ListDataModel としてページを公開します。

(4)

EJB 標準 @Remove アノテーションは、 アノテーション付きのメソッドが呼ばれた後に、 ステートフルセッション Bean が削除され、そして、その状態が破棄されることを定義しています。 Seam では、 すべての ステートフルセッション Bean は、 @Destroy @Remove 付きメソッドが定義される必要があります。 これは、Seam がセッションコンテキストを破棄するときに呼ばれる EJB remove メソッドです。 実際、 @Destroy アノテーションは、 Seam コンテキストが終了するときに発生するさまざまな種類のクリーンアップに利用可能であり、 とても実用的です。 @Destroy @Remove メソッドがないと、 状態がリークするのでパフォーマンス関連の問題に悩まされます。

このアプリケーションの主なページは、Facelets ページです。 ホテルを検索に関連する抜粋を見てみましょう。

例 1.23.

<div class="section">
<h:form>
  
  <span class="errors">
    <h:messages globalOnly="true"/>
  </span>
    
  <h1>Search Hotels</h1>
  <fieldset> 
     <h:inputText value="#{hotelSearch.searchString}" style="width: 165px;">
        <a:support event="onkeyup" actionListener="#{hotelSearch.find}"                  (1)
                   reRender="searchResults" />
     </h:inputText>
     &#160;
     <a:commandButton value="Find Hotels" action="#{hotelSearch.find}" 
                      styleClass="button" reRender="searchResults"/>
     &#160;
     <a:status>                                                                          (2)
        <f:facet name="start">
           <h:graphicImage value="/img/spinner.gif"/>
        </f:facet>
     </a:status>
     <br/>
     <h:outputLabel for="pageSize">Maximum results:</h:outputLabel>&#160;
     <h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">
        <f:selectItem itemLabel="5" itemValue="5"/>
        <f:selectItem itemLabel="10" itemValue="10"/>
        <f:selectItem itemLabel="20" itemValue="20"/>
     </h:selectOneMenu>
  </fieldset>
    
</h:form>
</div>

<a:outputPanel id="searchResults">                                                       (3)
  <div class="section">
  <h:outputText value="No Hotels Found" 
                rendered="#{hotels != null and hotels.rowCount==0}"/>
  <h:dataTable value="#{hotels}" var="hot" rendered="#{hotels.rowCount>0}">
    <h:column>
      <f:facet name="header">Name</f:facet>
      #{hot.name}
    </h:column>
    <h:column>
      <f:facet name="header">Address</f:facet>
      #{hot.address}
    </h:column>
    <h:column>
      <f:facet name="header">City, State</f:facet>
      #{hot.city}, #{hot.state}, #{hot.country}
    </h:column> 
    <h:column>
      <f:facet name="header">Zip</f:facet>
      #{hot.zip}
    </h:column>
    <h:column>
      <f:facet name="header">Action</f:facet>
      <s:link value="View Hotel" action="#{hotelBooking.selectHotel(hot)}"/>             (4)
    </h:column>
  </h:dataTable>
  <s:link value="More results" action="#{hotelSearch.nextPage}" 
          rendered="#{hotelSearch.nextPageAvailable}"/>
  </div>
</a:outputPanel>
(1)

Ajax4JSF <a:support> タグは、 onkeyup のような JavaScript イベントが発生したとき、 JSF アクションイベントリスナが非同期の XMLHttpRequest から呼ばれることを可能にしています。 さらに良いことには、 再レンダリング属性は、JSF ページの一部分だけのレンダリングを可能とし、 非同期のレスポンスを受信したときに、部分的なページ更新の実行を可能にしています。

(2)

Ajax4JSF <a:status> タグは、 非同期のリクエストが返されるのを待つ間に、 ちょっとしたアニメーションイメージを表示させます。

(3)

Ajax4JSF <a:outputPanel> タグは、 非同期リクエストによって再レンダリング可能なページの領域を定義します。

(4)

Seam <s:link> タグは、 JSF アクションリスナを普通の (非 JavaScript) HTML リンクに付けることができます。 標準 <h:commandLink> と比べてこれが有利なのは、 "新しいウィンドウで開く" や "新しいタブで開く"といった操作を損なわないことです。 パラメータ #{hotelBooking.selectHotel(hot)} のメソッドバインディングを利用していることにも留意してください。 これは、標準統一された EL式 では不可能ですが、 Seam は、 すべてのメソッドバインディング表現でパラメータ使用できるよう EL式 を拡張しています。

このページは、タイプしたときに検索結果が動的に表示し、 ホテルの選択をさせ、 HotelBookingActionselectHotel() メソッドに選択結果を渡します。 そこでは、かなり興味深いことが起こっています。

対話と関係する永続データを自然にキャッシュするために、 予約サンプルアプリケーションがどのように対話スコープのステートフル Bean を利用するか見てみましょう。 以下のサンプルコードはかなり冗長ですが、 対話の各種ステップを実装するスクリプト化されたアクションの一覧と考えると理解しやすくなります。 ストーリーを読むように、 頭から順に読んでください。

例 1.24.

@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
   
   @PersistenceContext(type=EXTENDED)                                                    (1)
   private EntityManager em;
   
   @In                                                                                   (2)
   private User user;
   
   @In(required=false) @Out
   private Hotel hotel;
   
   @In(required=false) 
   @Out(required=false)
   private Booking booking;
     
   @In
   private FacesMessages facesMessages;
      
   @In
   private Events events;
   
   @Logger 
   private Log log;
   
   @Begin                                                                                (3)
   public String selectHotel(Hotel selectedHotel)
   {
      hotel = em.merge(selectedHotel);
      return "hotel";
   }
   
   public String bookHotel()
   {      
      booking = new Booking(hotel, user);
      Calendar calendar = Calendar.getInstance();
      booking.setCheckinDate( calendar.getTime() );
      calendar.add(Calendar.DAY_OF_MONTH, 1);
      booking.setCheckoutDate( calendar.getTime() );
      
      return "book";
   }

   public String setBookingDetails()
   {
      if (booking==null || hotel==null) return "main";
      if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
      {
         facesMessages.add("Check out date must be later than check in date");
         return null;
      }
      else
      {
         return "confirm";
      }
   }

   @End                                                                                  (4)
   public String confirm()
   {
      if (booking==null || hotel==null) return "main";
      em.persist(booking);
      facesMessages.add("Thank you, #{user.name}, your confimation number for #{hotel.name} is #{booking.id}");
      log.info("New booking: #{booking.id} for #{user.username}");
      events.raiseEvent("bookingConfirmed");
      return "confirmed";
   }
   
   @End
   public String cancel()
   {
      return "main";
   }
   
   @Destroy @Remove                                                                      (5)
   public void destroy() {}

}
(1)

この Bean は、EJB3 拡張永続コンテキスト を使用します。 その結果、エンティティインスタンスは、 ステートフルセッション Bean のライフサイクル全体の管理を維持します。

(2)

@Out アノテーションは、 メソッド呼び出しの後に、属性の値がコンテキスト変数にアウトジェクトされることを宣言します。 このサンプルでは、すべてのアクションリスナの呼び出しが完了した後に、 hotel の名前のコンテキスト変数は hotel インスタンス変数の値に設定されます。

(3)

@Begin アノテーションは、 アノテーション付きメソッドが長期対話を開始することを定義しています。 従って、リクエストの終了では現在の対話コンテキストは破棄されません。 その代わりに、 現在のウインドからのすべてのリクエストに再び関連し、 対話の非活動によるタイムアウトあるいは一致する @End メソッドの呼び出しにより破棄されます。

(4)

@End アノテーションは、 アノテーション付きメソッドが現在の長期対話を終了することを定義しています。 従って、リクエストの終わりで現在の対話コンテキストは破棄されます。

(5)

Seam は、対話コンテキストを破棄するとき、 この EJB remove メソッドは、呼び出されます。 このメソッドを定義することを忘れないでください。

HotelBookingAction は、 ホテル検索、選択、予約、予約確認を実装したすべてのアクションリスナを持っており、 そして、この操作に関連する状態をインスタンスに保持しています。 このコードが、 HttpSession 属性から get/set するものと比較して、 よりクリーンで簡単なコードであることに同意してもらえると考えています。

さらに良くするために、ユーザは、ログインセッション毎に複数の分離された対話を持つことが可能です。 試してみてください。 同時に 2 つの異なるホテル予約を作成することが可能です。 対話を長時間、放置した場合、 Seam は、最終的に対話をタイムアウトし、状態を破棄します。 対話が終了した後に、その対話ページに戻るボタンを押し、アクションの実行を試みた場合、 Seam は、対話が既に終了したことを検出し、検索ページにリダイレクトします。

1.6.4. The Seam UI 管理ライブラリ

予約アプリケーションの WAR ファイルの中身をチェックすれば、 WEB-INF/lib ディレクトリ中に seam-ui.jar が見つかります。 このパッケージは、Seam と統合する多くの JSF カスタムコントロールを含んでいます。 この予約アプリケーションは、 検索画面からホテルページへの画面遷移制御に <s:link> を使用しています。

<s:link value="View Hotel" action="#{hotelBooking.selectHotel}"/>

ここで、<s:link> を使用することで、 ブラウザの "新しいウィンドウを開く" 機能を阻害することなく、 アクションリスナに HTML リンクを付けることが可能です。 標準 JSF <h:commandLink> は、"新しいウィンドウを開く" と連動しません。 <s:link> は、 対話伝播ルールを含むその他多くの便利な機能を提供します。

予約アプリケーションは、特に、/book.xhtml においていくつかの他の Seam コントロールと Ajax4JSF コントロールを使用します。 ここでは、それらコントロールの詳細には触れませんが、 このコードを理解したいならば、 JSF フォームバリデーションのための Seam の機能を対象とする章を参照してください。

1.6.5. Seam デバッグページ

WAR は、seam-debug.jar も含みます。 この JAR が Facelet とともに WEB-INF/lib にデプロイされ、 そして、web.xml あるいは、seam.properties 中に以下のように Seam プロパティを設定した場合、

<context-param>
    <param-name>org.jboss.seam.core.init.debug</param-name>
    <param-value>true</param-value>
</context-param>

Seam デバッグページが有効になります。 このページから現在のログインセッションに関する Seam コンテキスト中の Seam コンポーネントの閲覧と検査が可能です。 ブラウザから、http://localhost:8080/seam-booking/debug.seam と入力してください。 examples/booking/view ディレクトリに、 debug.xhtml と呼ばれる facelets ページがあります。 このページで現在のログインセッションに関する Seam コンテキスト中の Seam コンポーネントを見たり検査したりすることができます。 ブラウザに、http://localhost:8080/seam-booking/debug.seam. と入力してください。

1.7. Seam と jBPM を使った本格的アプリケーション: DVD ストアサンプル

DVD ストアのデモアプリケーションは、 タスク管理とページフローのための jBPM の実践的な使用法を見せてくれます。

ユーザ画面は、検索やショッピングカート機能の実装のために jPDL ページフローを利用しています。

この管理画面は、オーダの承認やショッピングサイクルを管理するために jBPM を利用します。 ビジネスプロセスは、異なるプロセス定義を選択することにより動的に変更されるかもしれません。

TODO

dvdstore ディレクトリの中をご覧ください。

1.8. Seam ワークスペース管理を使った本格的アプリケーション: 問題追跡システムサンプル

問題追跡デモは Seam のワークスペース管理の機能をよく披露しています。 対話スイッチャ、対話一覧、ブレッドクラム

TODO

issues ディレクトリの中をご覧ください。

1.9. Hibernate を使った Seam サンプル: Hibernate 予約システムサンプル

Hibernate 予約デモは、 永続性のために Hibernate、 セッション Bean の代わりに JavaBean を使用した簡単な予約デモの移植版です。

TODO

hibernate ディレクトリの中をご覧ください。

1.10. RESTful Seam アプリケーション: Blog サンプル

Seam は、サーバサイドで状態保持するアプリケーションの実装をとても容易にします。 しかし、サーバサイドの状態管理は、いつも適切というわけではありません。 (特に、コンテンツ を提供する機能において ) この種の問題のために、ユーザにページをブックマークさせ、 そして、比較的ステートレスなサーバとする必要がしばしばあります、 その結果、ブックマークを通していつでもどんなページにもアクセス可能になります。 この Blog サンプルは、 Seam を使用した RESTful アプリケーションの実装方法を見せてくれます。 検索結果ページを含むすべてのアプリケーションのページは、 ブックマークが可能です。

この Blog サンプルは、"引っぱり (PULL) " - スタイル MVC の使用を実演しています。 ここで、ビューのためのデータ取得とデータ準備のアクションメソッドリスナを使用する代わりに、 ビューは、レンダリングしているコンポーネントからデータを引き出します (PULL)。

1.10.1. "PULL" 型 MVC の使用

index.xhtml facelets ページの一部は、 最新の Blog 登録のリストを表示しています。

例 1.25.

<h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3">
   <h:column>
      <div class="blogEntry">
         <h3>#{blogEntry.title}</h3>
         <div>
            <h:outputText escape="false" 
                  value="#{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt}"/>
         </div>
         <p>
            <h:outputLink value="entry.seam" rendered="#{blogEntry.excerpt!=null}">
               <f:param name="blogEntryId" value="#{blogEntry.id}"/>
               Read more...
            </h:outputLink>
         </p>
         <p>
            [Posted on 
            <h:outputText value="#{blogEntry.date}">
               <f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
            </h:outputText>]
            &#160;
            <h:outputLink value="entry.seam">[Link]
               <f:param name="blogEntryId" value="#{blogEntry.id}"/>
            </h:outputLink>
         </p>
      </div>
   </h:column>
</h:dataTable>

ブックマークからこのページに移動する場合、 <h:dataTable> により使用されるデータは実際にはどのように初期化されるのでしょうか。 ここで何が起きているかというと、 BlogBlog という名前の Seam コンポーネントによって、 必要なときに遅延して取得されて — 引き出されて — いるわけです。 これは、 Struts のような従来の WEB アクションに基づくフレームワークでは一般的なフロー制御と逆になっています。

例 1.26.

@Name("blog")
@Scope(ScopeType.STATELESS)
public class BlogService 
{
   
   @In                                                                                   (1)
   private EntityManager entityManager;
  
   @Unwrap                                                                               (2)
   public Blog getBlog()
   {
      return (Blog) entityManager.createQuery("from Blog b left join fetch b.blogEntries")
            .setHint("org.hibernate.cacheable", true)
            .getSingleResult();
   }

}
(1)

このコンポーネントは、Seam 管理永続コンテキスト を使用します。 これまで見てきた他のサンプルとは異なり、 この永続コンテキストは、Seam によって管理されます。 この永続コンテキストは、WEB リクエスト全体で有効で、 取得していないビュー関連にアクセスするときに発生するあらゆる例外の回避を可能にします。

(2)

@Unwrap アノテーションは、 Seam がクライアントに対して実際の BlogService コンポーネントではなくメソッドの返り値 — Blog — を渡すよう指示します。 これが Seam 管理コンポーネントパターン です。

これは、これまでのところ良いですが、 検索結果ページのようなフォームサブミットの結果のブックマークではどうでしょうか?

1.10.2. ブックマーク可能検索結果ページ

この Blog サンプルは、 各ページの右上にユーザの Blog 記事の検索を可能にする小さなフォームを持ちます。 これは、facelet テンプレート、template.xhtml に含まれる menu.xhtml ファイルに定義されます。

例 1.27.

<div id="search">
   <h:form>
      <h:inputText value="#{searchAction.searchPattern}"/>
      <h:commandButton value="Search" action="/search.xhtml"/>
   </h:form>
</div>

ブックマーク可能検索結果ページの実装のために、 検索フォームのサブミットを処理した後に、 ブラウザリダイレクトを実行する必要があります。 アクション結果 (outcome) として JSF ビュー ID を使用しているので、 Seam は、フォームがサブミットされたとき、自動的に ビュー ID にリダイレクトします。 別の方法として、以下のようなナビゲーションルールを定義することも可能です。

例 1.28.

<navigation-rule>
   <navigation-case>
      <from-outcome>searchResults</from-outcome>
      <to-view-id>/search.xhtml</to-view-id>
      <redirect/>
   </navigation-case>
</navigation-rule>

フォームは、以下と似たようなものになるでしょう。

例 1.29.

<div id="search">
   <h:form>
      <h:inputText value="#{searchAction.searchPattern}"/>
      <h:commandButton value="Search" action="searchResults"/>
   </h:form>
</div>

しかし、リダイレクトするとき、 http://localhost:8080/seam-blog/search.seam?searchPattern=seam のようなブックマーク URL を取得するために、 リクエストパラメータとしてフォームによってサブミットされた値を含む必要があります。 JSF は、これをする簡単な方法は提供しませんが、Seam は提供します。 WEB-INF/pages.xml で定義された Seam page parameter を使用します。

例 1.30.

<pages>
   <page view-id="/search.xhtml">
      <param name="searchPattern" value="#{searchService.searchPattern}"/>
   </page>
   ...
</pages>

これは、ページにリダイレクトするときに、Seam に searchPattern の名前のリクエストパラメータとして #{searchService.searchPattern} の値を含み、ページをレンダリングする前に、パラメータの値をモデルに再適用するよう指示します。

リダイレクトによって search.xhtml ページに移動します。

例 1.31.

<h:dataTable value="#{searchResults}" var="blogEntry">
   <h:column>
      <div>
         <h:outputLink value="entry.seam">
            <f:param name="blogEntryId" value="#{blogEntry.id}"/>
            #{blogEntry.title}
         </h:outputLink>
         posted on 
         <h:outputText value="#{blogEntry.date}">
            <f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
         </h:outputText>
      </div>
   </h:column>
</h:dataTable>

これも、また実際の検索結果を取得するために "PULL" 型 MVC を使用しています。

例 1.32.

@Name("searchService")
public class SearchService 
{
   
   @In
   private EntityManager entityManager;
   
   private String searchPattern;
   
   @Factory("searchResults")
   public List<BlogEntry> getSearchResults()
   {
      if (searchPattern==null)
      {
         return null;
      }
      else
      {
         return entityManager.createQuery("select be from BlogEntry be where lower(be.title) like :searchPattern or lower(be.body) like :searchPattern order by be.date desc")
               .setParameter( "searchPattern", getSqlSearchPattern() )
               .setMaxResults(100)
               .getResultList();
      }
   }

   private String getSqlSearchPattern()
   {
      return searchPattern==null ? "" : '%' + searchPattern.toLowerCase().replace('*', '%').replace('?', '_') + '%';
   }

   public String getSearchPattern()
   {
      return searchPattern;
   }

   public void setSearchPattern(String searchPattern)
   {
      this.searchPattern = searchPattern;
   }

}

1.10.3. RESTful アプリケーションの "PUSH" 型 MVC の使用

ごく希に、RESTful ページ処理のために PUSH 型 MVC を使用することが当然の場合があります。 そこで、Seam は、ページアクション の概念を提供します。 Blog サンプルは、 Blog 記入ページ、 entry.xhtml にページアクションを使用しています。 これは、少しわざとらしい感じで、ここでは、PULL 型 MVC を使用する方が容易かもしれません。

entryAction コンポーネントは、 Struts のような典型的な PUSH 型 MVC アクション指向フレームワークのように動作します。

例 1.33.

@Name("entryAction")
@Scope(STATELESS)
public class EntryAction
{
   @In(create=true) 
   private Blog blog;
   
   @Out
   private BlogEntry blogEntry;
   
   public void loadBlogEntry(String id) throws EntryNotFoundException
   {
      blogEntry = blog.getBlogEntry(id);
      if (blogEntry==null) throw new EntryNotFoundException(id);
   }
   
}

ページアクションは、pages.xml でも宣言されます。

例 1.34.

<pages>
   ...

   <page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry(blogEntry.id)}">
      <param name="blogEntryId" value="#{blogEntry.id}"/>
   </page>

   <page view-id="/post.xhtml" action="#{loginAction.challenge}"/>

   <page view-id="*" action="#{blog.hitCount.hit}"/>

</pages>

上記の例は、 その他の機能に対してページアクションを使っています — ログインチャレンジやページビューカウンタなど。 また、 ページアクションメソッドのバインディングにパラメータを使用しているのにも注目してください。 これは JSF EL の標準機能ではありませんが、 Seam ではページアクションだけでなく JSF メソッドのバインディングでも使用できるようになっています。

entry.xhtml ページがリクエストされると、 Seam は最初にページパラメータ blogEntryId をモデルにバインドし、 次に必要なデータ — blogEntry — を取得するページアクションを実行してから、 それを Seam イベントコンテキストに配置します。 最後に、以下がレンダリングされます。

例 1.35.

<div class="blogEntry">
   <h3>#{blogEntry.title}</h3>
   <div>
      <h:outputText escape="false" value="#{blogEntry.body}"/>
   </div>
   <p>
      [Posted on&#160;
      <h:outputText value="#{blogEntry.date}">
         <f:convertDateTime timezone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
      </h:outputText>]
   </p>
</div>

blog エントリがデータベースで見つからない場合、 EntryNotFoundException 例外がスローされます。 exception is thrown. この例外は 505 エラーではなく 404 であって欲しいので、 例外クラスのアノテーションを付けます。

例 1.36.

@ApplicationException(rollback=true)
@HttpError(errorCode=HttpServletResponse.SC_NOT_FOUND)
public class EntryNotFoundException extends Exception
{
   EntryNotFoundException(String id)
   {
      super("entry not found: " + id);
   }
}

別実装のサンプルは、メソッドバインディングでパラメータを使用しません。

例 1.37.

@Name("entryAction")
@Scope(STATELESS)
public class EntryAction
{
   @In(create=true) 
   private Blog blog;
   
   @In @Out
   private BlogEntry blogEntry;
   
   public void loadBlogEntry() throws EntryNotFoundException
   {
      blogEntry = blog.getBlogEntry( blogEntry.getId() );
      if (blogEntry==null) throw new EntryNotFoundException(id);
   }
   
}
<pages>
   ...

   <page view-id="/entry.xhtml" action="#{entryAction.loadBlogEntry}">
      <param name="blogEntryId" value="#{blogEntry.id}"/>
   </page>
   
   ...
</pages>

どの実装を選択するかは好みの問題です。

1.11. JSF 1.2 RI を使用した JBoss Seam サンプルの実行

JBoss AS 4.0 は JSF 1.1 実装 Apache MyFaces を同梱しています。 長い間待ちましたが、 いまだ MyFaces からの JSF 1.2 実装はありません。 これ以外の理由も含めて、 JBoss AS 4.2 はデフォルトで JSF 1.2 リファレンス実装を組み込むことになります。 4.2 のリリース後すぐに、 Seam サンプルを JSF 1.2 に移行する予定です。

待つことができない人のために、Seam は、すでに JSF 1.2 互換であり、 JBoss 4.0.5 上で JSF 1.2 を実行する Seam サンプルを手に入れることは簡単です。 あの予約サンプルで始めましょう。

  • jsf-api.jarjsf-impl.jarel-api.jarel-ri.jarserver/default/deploy/tomcat/jbossweb-tomcat55.sar/jsf-libsにコピーします。

  • myfaces-api.jarmyfaces-impl.jarserver/default/deploy/tomcat/jbossweb-tomcat55.sar/jsf-libs から削除します。

  • server/default/deploy/tomcat/jbossweb-tomcat55.sar/conf/web.xml を編集して、 myfaces-impl.jarjsf-impl.jar に置き換えます。

  • examples/booking/resources/WEB-INF/web.xml を編集して、 MyFaces listener を削除し RI listener のコメントを外します。

  • examples/booking/resources/WEB-INF/faces-config.xml を編集して、 JSF 1.2 XML スキーマ宣言を使用する SeamELResolver をインストールする行のコメントを外します。

  • examples/booking/resources/META-INF/application.xml を編集して、 Java モジュールとして el-api.jarel-impl.jar を宣言している行を削除します。

JBoss を再起動し、 examples/booking ディレクトリで ant と入力します。

第2章 seam-gen を使って Seam を始めよう

Seam ディストリビューションは、コマンドラインユーティリティを含んでおり、 Eclipse プロジェクトのセットアップ、 簡単な Seam のスケルトンコードの生成、 既存データベースからアプリケーションのリバースエンジニアリングをとても簡単にします。

これは、Seam 入門として良い方法です。 そして、状態をデータベースに保管するとてもつまらないアプリケーションを構築するために、 新しいおもちゃがどれほどすばらしいかを大げさに話す退屈な Ruby 野郎の 1 人に次にエレベータの中で捕まったとわかった時のために、 攻撃材料を与えてくれます。

このリリースでは、seam-gen は JBoss AS で使用するのが最良です。 プロジェクト設定をマニュアルで少し変更するだけで他の J2EE や Java 5 アプリケーションサーバ用に生成されたプロジェクトも使用可能です。

Eclipse がなくても seam-gen は使用可能ですが、 このチュートリアルでは、Eclipse を併用してデバッグや統合テストを行う方法を示したいと思います。 Eclipse をインストールしたくない方も、 このチュートリアルを続けることができます — コマンドラインからすべてのステップは実行可能です。

Seam-gen は、簡単に言ってしまえば、テンプレートと共に Hibernate Tools をラッピングした大きく見苦しい Ant スクリプトです。 これは、必要であれば簡単にカスタマイズできることを意味します。

2.1. 始める前に

始める前に、JDK 5 または JDK 6 とJBoss AS 4.0.5 と Ant 1.6、そして、それに合う Eclipse 用の JBoss IDE プラグイン と TestNG プラグインがインストールされていることを確認してください。 Eclipse の JBoss サーバビューに JBoss 設定を追加してください。 デバッグモードで JBoss を起動してください。 最後に、Seam ディストリビューションを展開したディレクトリで、 コマンドプロンプト起動してください。

JBoss には WAR や EAR のホット再デプロイメントに優れたサポートがあります。 残念ながら、 JVM にバグがあるため、 — 開発段階では一般的な — EAR の再デプロイメントを繰り返すと、最終的には JVM が perm gen スペースを使い果たしてしまうことになります。 この理由により、デプロイメント時には perm gen space を大きく確保した JVM で JBoss を稼動させることを推奨します。 JBoss IDE から JBoss を稼動させる場合は、 「VM 引数」の下にあるサーバ起動設定でこれを設定することができます。 以下のような値がよいでしょう。

-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512

十分なメモリがないとすると、以下が最小の推奨値です。

-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256

コマンドラインから JBoss を起動しているならば、 bin/run.conf の JVM オプション設定が可能です。

今すぐに変更を行いたくない場合は特に行う必要はありません — OutOfMemoryException が発生した時点で対処してください。

2.2. 新しい Eclipse プロジェクトのセットアップ

最初にすべきことは、あなたの環境用に seam-gen を設定することです。 ( JBoss AS インストールディレクトリ、Eclipse ワークスペース、 データベースコネクション ) それは簡単です。単純にタイプしてください。

cd jboss-seam-1.1.x
seam setup

以下のように必要な情報の入力を要求されるでしょう。

C:\Projects\jboss-seam>seam setup
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml

setup:
    [echo] Welcome to seam-gen :-)
    [input] Enter your Java project workspace [C:/Projects]

    [input] Enter your JBoss home directory [C:/Program Files/jboss-4.0.5.GA]

    [input] Enter the project name [myproject]
helloworld
    [input] Is this project deployed as an EAR (with EJB components) or a WAR (with no EJB support) [ear] (ear,war,)

    [input] Enter the Java package name for your session beans [com.mydomain.helloworld]
org.jboss.helloworld
    [input] Enter the Java package name for your entity beans [org.jboss.helloworld]

    [input] Enter the Java package name for your test cases [org.jboss.helloworld.test]

    [input] What kind of database are you using? [hsql] (hsql,mysql,oracle,postgres,mssql,db2,sybase,)
mysql
    [input] Enter the Hibernate dialect for your database [org.hibernate.dialect.MySQLDialect]

    [input] Enter the filesystem path to the JDBC driver jar [lib/hsqldb.jar]
../../mysql-connector.jar
    [input] Enter JDBC driver class for your database [com.mysql.jdbc.Driver]

    [input] Enter the JDBC URL for your database [jdbc:mysql:///test]

    [input] Enter database username [sa]
gavin
    [input] Enter database password []

    [input] Are you working with tables that already exist in the database? [n] (y,n,)
y
    [input] Do you want to drop and recreate the database tables and data in import.sql each time you deploy? [n] (y,n,)
n
[propertyfile] Creating new property file: C:\Projects\jboss-seam\seam-gen\build.properties
     [echo] Installing JDBC driver jar to JBoss server
     [echo] Type 'seam new-project' to create the new project

BUILD SUCCESSFUL
Total time: 1 minute 17 seconds
C:\Projects\jboss-seam>

このツールは気の利いたデフォルト値を提供します。 プロンプトに対して単に enter を押すだけで大丈夫です。

必須となる最重要な選択は、プロジェクトのEAR形式デプロイとWAR形式デプロイのどちらにするかです。 EAR プロジェクトは EJB3.0 に対応し、 Java EE 5 が必要です。 WAR プロジェクトは EJB3.0 には対応しませんがおそらく J2EE 環境にデプロイメントが可能でしょう。 WARは EAR に比べシンプルで理解しやすいパッケージです。 JBoss を ejb3 プロファイルでインストールした場合は ear を選択してください。 これ以外は war を選択してください。 ここからは、 このチュートリアルは EAR デプロイメントが選択されたと仮定しますが、 WAR デプロイメントの場合もまったく同じステップで進むことができます。

既存のデータモデルで作業をしている場合、 データベースに既にテーブルが存在していることを seam-gen に必ず知らせてください。

設定は seam-gen/build.properties に格納されていますが、 2 度目に seam setup を実行することで変更することも可能です。

以下のようにタイプすることで、Eclipse ワークスペースディレクトリに、 新規プロジェクトの生成が可能です。

seam new-project
C:\Projects\jboss-seam>seam new-project
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml

validate-workspace:

validate-project:

copy-lib:
     [echo] Copying project jars ...
     [copy] Copying 32 files to C:\Projects\helloworld\lib
     [copy] Copying 9 files to C:\Projects\helloworld\embedded-ejb

file-copy-wtp:

file-copy:
     [echo] Copying project resources ...
     [copy] Copying 12 files to C:\Projects\helloworld\resources
     [copy] Copying 1 file to C:\Projects\helloworld\resources
     [copy] Copying 5 files to C:\Projects\helloworld\view
     [copy] Copying 5 files to C:\Projects\helloworld
    [mkdir] Created dir: C:\Projects\helloworld\src

new-project:
     [echo] A new Seam project named 'helloworld' was created in the /Users/gavin/Documents/workspace directory
     [echo] Type 'seam explode' and go to http://localhost:8080/helloworld
     [echo] Eclipse Users: Add the project into Eclipse using File > New > Project and select General > Project (not Java Project)
     [echo] NetBeans Users: Open the project in NetBeans

BUILD SUCCESSFUL
Total time: 7 seconds
C:\Projects\jboss-seam>

Seam jar、依存する jar そして JDBC ドライバ jar を新しい Eclipse プロジェクトにコピーし、 Eclipse メタデータおよび Ant ビルドスクリプトに従って必要となるすべてのリソースと設定ファイル群、 facelets テンプレートファイル、 スタイルシートを生成します。 新規 -> プロジェクト... -> 一般 -> プロジェクト -> 次へ の手順でプロジェクトを追加し、 プロジェクト名 (この場合、 helloworld) を入力して、 完了 をクリックすれば、 Eclipse プロジェクトは自動的に展開された JBoss AS ディレクトリ構造にデプロイされます。 新規プロジェクトウィザードから Java プロジェクト は選択しないでください。

Eclipse のデフォルト JDK が SE 5 あるいは Java SE 6 JDK でなければ、 プロジェクト -> プロパティ -> Java コンパイラ の手順で、Java SE 5 互換の JDK を選ぶ必要があります。

別の方法として、Eclise の外部から seam explode とタイプすることでプロジェクトのデプロイが可能です。 seam explode.

welcome page を見るには、http://localhost:8080/helloworld に進んでください。 これは、テンプレート view/layout/template.xhtml を使用した facelets page, view/home.xhtml です。 Eclipse からこのページやテンプレートの編集が可能です。 そしてブラウザを更新することで即座に結果を見ることが可能です。

プロジェクトディレクトリに XML 設定ドキュメントが生成されますがびっくりしないでください。 これらのほとんどが標準 Java EE に関するもので、 1 度生成したら 2 度と見る必要のないものです。 全 Seam プロジェクトで 90% は同じものです。 (seam-gen が書けるほどですから記述が非常に簡単なドキュメントです。)

生成されたプロジェクトは3つのデータベースと永続性設定を含んでいます。 HSQLDB に対して TestNG ユニットテストを実行する場合に、 jboss-beans.xmlpersistence-test.xml、 そして import-test.sql ファイルが使用されます。 import-test.sql 中のデータベーススキーマとテストデータは、 常にテストが実行される前にデータベースにエキスポートされます。 myproject-dev-ds.xmlpersistence-dev.xml そして import-dev.sql ファイルは、 アプリケーションを開発データベースにデプロイするときに使用します。 seam-gen に既存データベースで作業しているかを伝えるかどうかによって、 スキーマはデプロイ時に自動的にエキスポートされる場合があります。 myproject-prod-ds.xmlpersistence-prod.xml そして import-prod.sql ファイルは、 本番環境データベースにアプリケーションをデプロイするときに使用します。 デプロイ時にスキーマは自動的にエキスポートされません。

2.3. 新規のアクションを生成する

従来のアクションスタイルの Web フレームワークに慣れている場合、 おそらく、どのように Java のステートレスアクションメソッドで簡単なWebページが生成することができるのだろうかと思われていると思います。 以下のように入力すると、

seam new-action

Seam はなんらかの情報を求めてきます。 そして、 プロジェクト用に新しい facelets page と Seam コンポーネントを生成します。

C:\Projects\jboss-seam>seam new-action ping
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml

validate-workspace:

validate-project:

action-input:
    [input] Enter the Seam component name
ping
    [input] Enter the local interface name [Ping]

    [input] Enter the bean class name [PingBean]

    [input] Enter the action method name [ping]

    [input] Enter the page name [ping]


setup-filters:

new-action:
     [echo] Creating a new stateless session bean component with an action method
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello\test
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello\test
     [copy] Copying 1 file to C:\Projects\hello\view
     [echo] Type 'seam restart' and go to http://localhost:8080/helloworld/ping.seam

BUILD SUCCESSFUL
Total time: 13 seconds
C:\Projects\jboss-seam>

新しい Seam コンポーネントを追加したので、 展開したディレクトリのデプロイを再起動する必要があります。 seam restart と入力するか、 Eclipse 内から生成されたプロジェクト build.xml ファイル の restart ターゲットを実行することで行うことができます。 再起動を強制する別の方法は、 Eclipse の resources/META-INF/application.xml ファイルを編集することです。 アプリケーションを変更するたびに JBoss を再起動する必要はないことに留意してください。

さあ、http://localhost:8080/helloworld/ping.seam に進んで、クリックボタンを押してください。 プロジェクトの src directory ディレクトリを見れば、このアクションに隠されたコードを見ることができます。 ping() メソッドにブレークポイントを置いて、 クリックボタンを押してください。

最後に、PingTest.xml ファイルを test パッケージに配置し、 Eclipse の TestNG プラグインを使用して統合テストを実行します。 別な方法として、 seam test を使用してテストを起動するか、 生成されたビルドから test ターゲットを起動します。

2.4. アクションのあるフォームを生成する

次のステップは、以下のようにフォームを生成することです。

seam new-form
C:\Projects\jboss-seam>seam new-form
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml

validate-workspace:

validate-project:

action-input:
    [input] Enter the Seam component name
hello
    [input] Enter the local interface name [Hello]

    [input] Enter the bean class name [HelloBean]

    [input] Enter the action method name [hello]

    [input] Enter the page name [hello]


setup-filters:

new-form:
     [echo] Creating a new stateful session bean component with an action method
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello\test
     [copy] Copying 1 file to C:\Projects\hello\view
     [copy] Copying 1 file to C:\Projects\hello\src\com\hello\test
     [echo] Type 'seam restart' and go to http://localhost:8080/hello/hello.seam

BUILD SUCCESSFUL
Total time: 5 seconds
C:\Projects\jboss-seam>

再びアプリケーションを再起動させ、 http://localhost:8080/helloworld/hello.seam に進みます。 生成されたコードを見てみましょう。 テストを実行します。 何か新しいフィールドをフォームと Seam コンポーネントに追加してみましょう。 (Java コードを変更したら常にデプロイを再起動することを忘れないようにしてください。)

2.5. 既存のデータベースからアプリケーションを自動生成する

手動でデータベースの中にテーブルを生成します。 (別のデータベースに切り替える必要がある場合はもう一度 seam setup を実行します。) ここで次のように入力します。

seam generate-entities

デプロイを再起動して、 http://localhost:8080/helloworld に進んでください。 データベースの参照、 既存オブジェクトの編集、 新しいオブジェクトの生成が可能です。 生成されたコードを見ると、 おそらくあまりに簡単なのに驚かれたと思います。 Seam はデータアクセスコードが手作業で簡単に記述できるように設計されたからです。 また seam-gen を使用して適当なことを行いたくないユーザーにも適しています。

2.6. EAR形式でアプリケーションをデプロイする

最後に、 標準の Java EE 5 パッケージングを使用してアプリケーションをデプロイできるようにしたいと思います。 まず、 seam unexplode を実行して展開したディレクトリを削除する必要があります。 EAR をデプロイするには、 コマンドプロンプトで seam deploy を入力する、 あるいは生成されたプロジェクトの build スクリプトの deploy ターゲットを実行します。 seam undeploy または undeploy ターゲットを使うとアンデプロイができます。

デフォルトでは、アプリケーションは dev profile でデプロイされます。 EAR は persistence-dev.xml ファイルと import-dev.sql ファイルを含み、 myproject-dev-ds.xml ファイルがデプロイされます。 プロファイルは変更可能で、 以下のように入力して prod profile を使用します。

seam -Dprofile=prod deploy

アプリケーション用に新しいデプロイメントプロファイルを定義することもできます。 プロジェクトに適切な名前が付いたファイルを追加します — 例えば、 persistence-staging.xmlimport-staging.sqlmyproject-staging-ds.xml などです。 -Dprofile=staging を使ってプロファイルの名前を選択します。

2.7. Seam と増分ホットデプロイメント

展開されたディレクトリで Seam アプリケーションをデプロイする場合、 開発時になんらかの増分ホットデプロイのサポートを受けるでしょう。 components.xml に以下の行を追加することで、 Seam と Facelets の両方でデバッグモードを有効にする必要があります。

<core:init debug="true"/>

以下のファイルは、 Webアプリケーションを完全に再起動することなく置き換えがされるかもしれません。

  • facelets ページ

  • pages.xml ファイル

ただし、 いずれかの Java コードを変更する必要がある場合は、 アプリケーションを完全に再起動する必要があります。 (JBoss では、 トップレベルのデプロイメント記述子に手を入れることで行えるでしょう。 EAR デプロイメントなら application.xml、 WAR デプロイメントなら web.xml です。)

しかし、 早いペースの編集/コンパイル/テストのサイクルを望むならば、 Seam は JavaBean コンポーネントの増分再デプロイメントをサポートしています。 この機能を有効にするためには、 JavaBean コンポーネントを WEB-INF/dev ディレクトリにデプロイする必要があります。 その結果、コンポーネントは WAR あるいは EAR クラスローダではなく、 特殊な Seam クラスローダによってロードされるようになります。

以下の制約を知っている必要があります。

  • コンポーネントは JavaBean コンポーネントでなければならず、 EJB3 Bean は不可です。 (この制約は修正中です。)

  • エンティティはホットデプロイできません。

  • components.xml でデプロイされたコンポーネントはホットデプロイできない場合があります。

  • ホットデプロイ可能なコンポーネントは、 WEB-INF/dev の外部にデプロイされたクラスからは見えません。

  • Seam デバックモードを有効にしなければなりません。

seam-gen を使用して WAR プロジェクトを生成する場合、 増分ホットデプロイメントは、src/action ソースディレクトリにあるクラスにそのまま使用可能です。 しかしならが、 seam-gen は EAR プロジェクトに対する増分ホットデプロイに対応していません。

第3章 コンテキスト依存コンポーネントモデル

Seam における 2 つの中心的概念は、 コンテキスト の概念とコンポーネントの概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。

Seam に組み込まれたコンテキストから説明を始めましょう。

3.1. Seam コンテキスト

Seam コンテキストはフレームワークによって生成、破棄されます。 アプリケーションは Java API 呼び出しによってコンテキスト区分 (demarcation) を明示的に制御することはできません。 コンテキストは通常、暗黙的ですが、場合によってコンテキストはアノテーションによって区分されます。

基本の Seam コンテキストは以下の通りです。

  • ステートレスコンテキスト (Stateless context)

  • イベント (またはリクエスト) コンテキスト (Event (or request) context)

  • ページコンテキスト (Page context)

  • 対話コンテキスト (Conversation context)

  • セッションコンテキスト (Session context)

  • ビジネスプロセスコンテキスト (Business process context)

  • アプリケーションコンテキスト (Application context)

これらのコンテキストのいくつかは、サーブレットや関連する仕様書から由来していることがわかります。 しかし、このうち 2 つは目新しいかもしれません。 対話コンテキストビジネスプロセスコンテキストです。 WEB アプリケーション中での状態管理がとても脆弱でエラーが発生しやすい 1 つの理由は、 3 つの組み込みコンテキスト (リクエスト、セッション、アプリケーション) がビジネスロジックの観点から特定の意味を持たないからです。 例えば、実際のアプリケーションのワークフローの観点から見るとユーザログインセッションは極めて自由裁量な構造です。 そのため、ほとんどの Seam コンポーネントは、対話コンテキストあるいはビジネスプロセスコンテキストのスコープに配置されます。 なぜなら、それらはアプリケーションの観点からとても意味のあるコンテキストだからです。

順に、各コンテキストを見てみましょう。

3.1.1. ステートレスコンテキスト (Stateless context)

本当に状態をもたないコンポーネント (主にステートレスセッション Bean) は、いつもステートレスコンテキスト (実際にはコンテキストではありません) に置かれます。 ステートレスコンポーネントは、あまり興味深いものでもなく、まったくオブジェクト指向でもありません。 でも、これらは重要でしばしば役に立ちます。

3.1.2. イベントコンテキスト

イベントコンテキストは「最も狭い」状態を持つコンテキストで、 他の種類のイベントを網羅する WEBリクエストコンテキストの概念の一般化です。 それにもかかわらず、JSF リクエストのライフサイクルと関連づけられたイベントコンテキストは、 イベントコンテキストの最も重要な用例であり、最もよく利用されるものです。 リクエスト終了時に、イベントコンテキストに関連するコンポーネントは破棄されますが、 それらの状態は、少なくともリクエストのライフサイクルの間では有効かつ明確です。

RMI 経由あるいは Seam Remoting により Seam コンポーネントを呼び出すとき、 イベントコンテキストは、その呼び出しだけのために生成、破棄されます。

3.1.3. ページコンテキスト (Page context)

ページコンテキストは、状態をレンダリングされたページの特定のインスタンスと関連づけを可能にします。 イベントリスナ中で状態の初期化が可能で、 実際にページをレンダリングしている間に、 ページに由来するどんなイベントからも状態にアクセスが可能です。 これは特にサーバサイドのデータ変化にリストが連動しているクリッカブルリストのような機能に役立ちます。 状態は実際にクライアントのためにシリアライズされます。 そのため、この構造は複数ウインドの操作や戻るボタンに対して極めて堅牢です。

3.1.4. 対話コンテキスト (Conversation context)

対話コンテキストは Seam でまさに中心となるコンセプトです。 対話 (conversation) は、ユーザの観点からの作業単位です。 それはユーザとのインタラクション、リクエスト、およびデータベーストランザクションをまたぐかもしれません。 しかし、ユーザにとって対話は、1 つの問題を解決します。 例えば、「ホテル予約」、「契約承認」、「注文作成」はすべて対話です。 対話というものが 1 つの「ユースケース」あるいは「ユーザストーリ」を実装していると考えたいかもしれませんが 関係は必ずしもその通りにはなりません。

対話は、「ユーザがこのウィンドウの中で現在していること」と関連づけられた状態を保持します。 1 人のユーザは、通常マルチウィンドウで、ある時点に進行中の複数の対話を持っているかもしれません。 対話コンテキストは、異なる対話からの状態の衝突をなくし、バグの原因とならないことを保証します。

対話の観点からアプリケーションについて考えることに慣れるには時間がかかるかもしれません。 しかし、慣れてしまうと、このコンセプトが大好きになり、もう対話なしでは考えられなくなるだろうと思います。

ある対話は単に 1 つのリクエストの間続いています。 複数のリクエストをまたぐ対話は、Seam によって提供されたアノテーションを使って、区分を示されなければなりません。

ある対話はまた タスク です。 タスクは長期ビジネスプロセスの観点では重要な意味を持つ対話であり、 タスクが首尾よく完了する場合、 ビジネスプロセスの状態遷移を引き起こす可能性を持っています。 Seam はタスク区分用に特別なアノテーションのセットを提供します。

より広い対話の "内部" で対話を発生させるような ネスト も可能です。 これは拡張機能です。

通常、実際にはリクエストとリクエストの間 servlet セッション中で Seam により対話状態は保持されます。 Seam は設定可能な 対話タイムアウト を実装し、 自動的に不活性な対話を破棄し、 ユーザが対話を中断しても、ユーザログインセッションにより保持される状態は際限なく増加しないことが保証されています。

Seam は同じプロセス中の同じ長期対話コンテキスト中で発生する並列のリクエスト処理をシリアル化します。

あるいは、Seam はクライアントブラウザの中に対話の状態を保持するように設定される場合もあります。

3.1.5. セッションコンテキスト (Session context)

セッションコンテキストはユーザログインに関する状態を保持します。 いくつかの事例では対話の間で状態を共有することが有用なことがありますが、 ログインユーザに関するグローバル情報以外のコンポーネントを保持するために、 セッションコンテキストを使用することは賛成できません。

JSR-168 portal 環境では、セッションコンテキストは portlet セッションを意味します。

3.1.6. ビジネスプロセスコンテキスト (Business process context)

ビジネスプロセスコンテキストは長期ビジネスプロセスに関する状態を保持します。 この状態は BPM エンジン (JBoss jBPM) によって管理や永続化が行われます。 ビジネスプロセスは、複数ユーザの複数インタラクションを橋渡しします。 従って、この状態は複数ユーザの間で明確な方法で共有されます。 現在のタスクは現在のビジネスプロセスインスタンスを決定し、 ビジネスプロセスのライフサイクルは プロセス定義言語 を使用することで外部に定義されます。 従って、ビジネスプロセスの区分のために特別なアノテーションはありません。

3.1.7. アプリケーションコンテキスト (Application context)

アプリケーションコンテキストはサーブレット仕様からおなじみのサーブレットのコンテキストです。 アプリケーションコンテキストは主に、設定データ、参照データ、メタモデルのような静的な情報を保持するために役立ちます。 例えば、Seam はアプリケーションコンテキスト中に Seam 設定やメタモデルを保管しています。

3.1.8. コンテキスト変数

コンテキストは名前空間、コンテキスト変数 のセットを定義します。 これらはサーブレット仕様のセッションやリクエスト属性と同様に機能します。 どのような値でもコンテキスト変数とバインドができますが、 通常、Seam コンポーネントインスタンスをコンテキスト変数にバインドします。

従って、コンテキスト中では、 コンポーネントインスタンスは、コンテキスト変数名 (いつもではないが通常はコンポーネント名と同じ) で識別されます。 Contexts クラスを通して特定のスコープの指定されたコンポーネントインスタンスにプログラム的にアクセスもできます。 それは Context インタフェースのスレッドに結びついたインスタンスへのアクセスを提供します。

User user = (User) Contexts.getSessionContext().get("user");

名前に関連する値を設定したり変更したりすることもできます。

Contexts.getSessionContext().set("user", user);

しかしながら、通常、インジェクションを通してコンテキストからコンポーネントを取得し、 アウトジェクションを通してコンポーネントインスタンスをコンテキストに配置します。

3.1.9. コンテキスト検索優先順位

上記のように、コンポーネントインスタンスは特定の周知のスコープから取得することもありますが、 それ以外の場合、すべてのステートフルスコープは 優先順位 に従って検索されます。 その順序は以下の通りです。

  • イベントコンテキスト

  • ページコンテキスト (Page context)

  • 対話コンテキスト (Conversation context)

  • セッションコンテキスト (Session context)

  • ビジネスプロセスコンテキスト (Business process context)

  • アプリケーションコンテキスト (Application context)

Contexts.lookupInStatefulContexts() を呼び出すことによって優先順位の検索も可能です。 JSF ページから名前によってアクセスする場合はいつも、優先順位検索が発生します。

3.1.10. 同時並行処理モデル

servlet 仕様も EJB 仕様も同じクライアントから起こる同時並行のリクエストを管理するための設備はまったく定義していません。 servlet コンテナは単純にすべてのスレッドを同時並行的に稼動させ、 スレッドセーフとすることをアプリケーションコードに任せます。 EJB コンテナはステートレスコンポーネントが同時並行的にアクセスされることを可能にし、 複数のスレッドがひとつのステートレスセッション Bean にアクセスするならば例外をスローします。

この振る舞いは粒度の細かい同期リクエストをベースとする古いスタイルの Web アプリケーションでは大丈夫であったかもしれません。 しかし、多くの粒度の細かい非同期リクエスト (AJAX) を多用する最新のアプリケーションのために、 同時並行はまぎれもない事実であり、プログラムモデルとしてサポートされなければなりません。 Seam は同時並行管理レイヤをコンテキストモデルに織り込みます。

Seam セッションとアプリケーションコンテキストはマルチスレッドになっています。 Seam は同時並行的に処理されるためにコンテキスト中での同時並行リクエストを許します。 イベントとページコンテキストは本来シングルスレッドです。 厳密に言えばビジネスプロセスコンテキストはマルチスレッドですが、 実際には同時並行はとてもまれで、この事実はほとんど着目されないかもしれません。 最後に、 同じ長期対話コンテキスト中の同時並行リクエストをシリアライズすることによって、 Seam は、対話コンテキストのために プロセスごと対話ごとのシングルスレッド モデルを実施します。

セッションコンテキストはマルチスレッドで、よく揮発性の状態を含むので、 Seam によりセッションスコープコンポーネントは同時並行アクセスからいつも保護されています。 Seam はデフォルトでリクエストを セッションスコープセッション Bean と JavaBean にシリアライズします。 ( そして、発生するどんなデッドロックも検出して打開します。) アプリケーションスコープのコンポーネントは通常揮発性の状態を保持しないため、 これはアプリケーションスコープのコンポーネントのためのデフォルトの振る舞いではありません。 なぜなら、グローバルレベルの同期化は 極端に コストがかかるからです。 しかし、 @Synchronized アノテーションを追加することで、 セッション Bean また JavaBean コンポーネントにシリアライズされたスレッドモデルを強制可能です。

この同時並行モデルは、 開発者側での特別な作業をまったく必要とすることなく、 AJAX クライアントが安全に揮発性セッションや対話状態を使用できることを意味します。

3.2. Seam コンポーネント

Seam コンポーネントは POJO (Plain Old Java Objects) です。 具体的には、Seam コンポーネントは JavaBean もしくは EJB 3.0 エンタープライズ Bean です。 Seam は コンポーネントが EJB が必須ではなく、また EJB 3.0 準拠のコンテナがなくても使用できますが、 Seam は EJB 3.0 を念頭にして設計され、EJB 3.0 と強く統合されています。 Seam は以下の コンポーネントタイプ をサポートします。

  • EJB 3.0 ステートレスセッション Bean

  • EJB 3.0 ステートフルセッション Bean

  • EJB 3.0 エンティティ Bean

  • JavaBean

  • EJB 3.0 メッセージ駆動型 Bean

3.2.1. ステートレスセッション Bean

ステートレスセッション Bean コンポーネントは、複数の呼出しに対して状態を保持することができません。 従って、それらは通常さまざまな Seam コンテキスト内の別コンポーネントの状態を操作するのに役に立ちます。 それらは JSF のアクションリスナとして使用できるかもしれませんが、 表示のために JSF コンポーネントにプロパティを提供することはできません。

ステートレスセッション Bean はいつもステートレスコンテキストに置かれます。

ステートレスセッション Bean は最も興味のわかない種類の Seam コンポーネントです。

3.2.2. ステートフルセッション Bean

ステートフルセッション Bean コンポーネントは、 Bean の複数の呼出しに対して状態を保持することができるだけでなく、 複数のリクエストに対して状態を保持することもできます。 データベースに保持されていないアプリケーションの状態は、 通常、ステートフルセッション Bean によって保持される必要があります。 これは Seam と他の多くの WEB アプリケーションフレームワークとの大きな違いです。 現在の対話の情報を直接 HttpSession に押し込める代わりに、 対話コンテキストに結びついたステートフルセッション Bean のインスタンス変数の中にそれを保持すべきです。 これは、Seam がこの状態のライフサイクルの管理を可能にし、 異なる同時実行中の対話に関連する状態の間に衝突がないことを保証します。

ステートフルセッション Bean はしばしば JSF アクションリスナ、または、 表示もしくはフォームのサブミットのためにプロパティを提供する JSF コンポーネントのバッキング Bean として使用されます。

デフォルトで、ステートフルセッション Bean は対話コンテキストとバインドします。 それらはページもしくはステートレスコンテキストとバインドできません。

セッションスコープのステートレスセッション Bean への同時並行リクエストは、 いつでも Seam によってシリアライズされます。

3.2.3. エンティティ Bean

エンティティ Bean はコンテキスト変数とバインドし、Seamコンポーネントとして機能することもあります。 エンティティは、コンテキスト依存識別子に加えて永続識別子を持つために、 エンティティのインスタンスは、Seam によって暗黙的にインスタンス化されるより、 むしろ Java コード中で明示的にバインドされます。

エンティティ Bean コンポーネントはバイジェクションもコンテキスト区分もサポートしません。 また、エンティティ Bean トリガのデータ妥当性検証の呼び出しもサポートしていません。

エンティティ Bean は、通常 JSF アクションリスナとして使用されませんが、 しばしば、表示あるいはフォームのサブミットのために JSF コンポーネントにプロパティを提供するバッキング Bean として機能します。 特に、エンティティ Bean をバッキング Bean として使用することは一般的であり、 追加 / 変更 / 削除タイプの機能の実装のためのステートレスセッション Bean アクションリスナといっしょに使用されます。

デフォルトで、エンティティ Bean は対話コンテキストとバインドします。 ステートレスセッション Bean とはバインドしません。

クラスタリングされた環境では、 ステートフルセッション Bean 中でエンティティ Bean の参照を保持することより、 エンティティ Bean を直接的に対話あるいはセッションスコープの Seam コンテキスト変数にバインドする方が非効果的であることに留意してください。 この理由のため、すべての Seam アプリケーションが Seam コンポーネントであるためにエンティティ Bean を定義するわけではありません。

3.2.4. JavaBean

JavaBean はステートレスあるいはステートフルセッション Bean のように使用されることもあります。 しかし、それらはセッション Bean の機能を提供していません。 (宣言的トランザクション区分、 宣言的セキュリティ、 効率的にクラスタ化された状態レプリケーション、 EJB 3.0 永続性、 タイムアウトメソッドなど)

後の章で、EJB コンテナなしで Seam や Hibernate を使用する方法を紹介しています。 このユースケースでは、コンポーネントはセッション Bean の代わりに JavaBean です。 しかし、多くのアプリケーションサーバでは、 ステートフルセッション Bean コンポーネントをクラスタリングするより、 対話あるいはセッションスコープの Seam JavaBean コンポーネントをクラスタリングする方が多少非効率的であることに留意してください。

デフォルトで、JavaBean はイベントコンテキストとバインドします。

セッションスコープの JavaBean への同時並行リクエストはいつも Seam によりシリアライズされます。

3.2.5. メッセージ駆動型 Bean

メッセージ駆動形 Bean は Seam コンポーネントとして機能することができます。 しかし、メッセージ駆動型 Bean は、他の Seam コンポーネントとまったく異なった形で呼び出されます。 コンテキスト変数を通じてそれらを呼び出す代わりに、 JMS queue あるいは、topic に送信されたメッセージを待ち受けます。

メッセージ駆動形 Bean は、Seam コンテキストとバインドできません。 また、それらの「呼び出し元」のセッションや対話状態にアクセスできません。 しかし、メッセージ駆動形 Bean は、バイジェクションと他の Seam の機能をサポートします。

3.2.6. インターセプション

そのマジック (バイジェクション、コンテキスト区分、データ妥当性検証など) を実行するために、 Seam はコンポーネントの呼び出しをインターセプトしなければなりません。 JavaBean では、Seam はコンポーネントのインスタンス化を完全に制御するため、特別な設定は不要です。 エンティティ Bean では、バイジェクションとコンテキスト区分が指定されていないため、インターセプションは不要です。 セッション Bean では、EJB インターセプタをセッション Bean コンポーネントのために登録しなければなりません。 アノテーションは以下のように使用します。

@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    ... 
}

しかし、もっと良い方法は、ejb-jar.xml にインターセプタを定義することです。

<interceptors>
   <interceptor>
      <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
   </interceptor>
</interceptors>
   
<assembly-descriptor>
   <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
   </interceptor-binding>
</assembly-descriptor>

3.2.7. コンポーネント名

すべての Seam コンポーネントは名前が必要です。 @Name アノテーションを使用してコンポーネントに名前を割り当てます。

@Name("loginAction")
@Stateless
public class LoginAction implements Login { 
    ... 
}

この名前は、Seam コンポーネント名 (seam component name) で、 EJB 標準で定義された他の名前との関連はありません。 しかし、Seam コンポーネント名はちょうど JSF 管理 Bean のように動作するため、 2 つのコンセプトは同一と考えることができます。

@Name はコンポーネント名を定義する唯一の方法ではありませんが、 いつも、どこかで 名前を指定する必要があります。 もしそうしないと、他の Seam アノテーションはどれも機能しないでしょう。

ちょうど JSF のように、Seam コンポーネントインスタンスは、 通常コンポーネント名と同じ名前のコンテキスト変数と結合します。 従って、例えば、Contexts.getStatelessContext().get("loginAction") を使って、 LoginAction にアクセスできるでしょう。 具体的には、Seam 自身がコンポーネントをインスタンス化する時はいつでも、 それはコンポーネント名によって新しいインスタンスを変数と結合します。 しかし、この場合も JSF のように、 アプリケーションはプログラムに基づいた API コールによってコンポーネントを他のコンテキスト変数と結合させることも可能です。 特定のコンポーネントがシステムの中で複数のロールを提供する場合のみ、これは有用です。 例えば、現在のログイン UsercurrentUser セッションコンテキスト変数に結合されているかもしれませんが、 一方で、ある管理機能の主体である Useruser 対話コンテキスト変数に結合されているかもしれません。

非常に大規模なアプリケーションのために、そして組み込み Seam コンポーネントのために、修飾名はしばしば使われます。

@Name("com.jboss.myapp.loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    ... 
}

Java コード中でも JSF の式言語中でも修飾されたコンポーネント名は使用できます。

<h:commandButton type="submit" value="Login"
                 action="#{com.jboss.myapp.loginAction.login}"/>

これはうっとうしいので、Seam は修飾名を簡単な名前にエイリアスする機能も提供します。 以下のような行を components.xml ファイルに追加してください。

<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

すべての組み込み Seam コンポーネントは修飾名を持っていますが、 それらのほとんどは Seam jar に含められている components.xml ファイルによって簡単な名前にエイリアスされます。

3.2.8. コンポーネントスコープの定義

@Scopeアノテーションを使用して、コンポーネントのデフォルトスコープ (コンテキスト) をオーバーライドすることができます。 これによって Seam によってインスタンス化される時に、 コンポーネントインスタンスがどんなコンテキストと結合するかを定義ができます。

@Name("user")
@Entity
@Scope(SESSION)
public class User { 
    ... 
}

org.jboss.seam.ScopeType 可能なスコープの列挙を定義します。

3.2.9. 複数ロールを持つコンポーネント

一部の Seam コンポーネントクラスはシステムの中で複数のロールを果たすことができます。 例えば、セッションスコープのコンポーネント User クラスは、 よく現在のユーザとして使用されますが、ユーザ管理画面では対話スコープのコンポーネントとして使用されます。 @Role アノテーションは、コンポーネントに対して異なったスコープを持つ追加指定のロールの定義を可能にしています。 これにより、 同じコンポーネントクラスを異なるコンテキスト変数にバインドすることができるようになります。 (どの Seam コンポーネント インスタンス も複数のコンテキスト変数にバインドが可能ですが、 これはクラスレベルで可能であり自動インスタンス化を利用しています。)

@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User { 
    ... 
}

@Roles アノテーションは、欲しいだけ多くの追加のロールの指定を可能にします。

@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION)
        @Role(name="tempUser", scope=EVENT)})
public class User { 
    ... 
}

3.2.10. 組み込みコンポーネント

多くのよいフレームワークのように、 Seam は自分自身が提供している機能を使うことを心掛けている (Eat Your Own Dog Food)。 組み込みの Seam インターセプタ (後述) と Seamコンポーネントのセットで実装されています。 これは、アプリケーションがランタイムで組み込みのコンポーネントとやり取り行うことを容易にします。 更に、組み込みのコンポーネントを独自の実装に置き換えることによって Seam の基本機能をカスタマイズすることさえ容易にします。 組み込みのコンポーネントは Seam の名前空間 org.jboss.seam.core と 同じ名前の Java パッケージにおいて定義されます。

組み込みコンポーネントは、Seam コンポーネントと同様にインジェクトすることも可能ですが、 便利なスタティック instance() メソッドも提供します。

FacesMessages.instance().add("Welcome back, #{user.name}!");

Seam は、Java EE 5 環境において密に統合されるよう設計されています。 しかし、私たちは、完全な EE 環境で実行されない多くのプロジェクトがあることを理解しています。 TestNG や JUnit のようなフレームワークを使用した容易なユニットテストや統合テストが、 決定的に重要であることを理解しています。 そこで、組み込み Seam コンポーネントのインストールにより、 通常 EE 環境のみでしか見られない重要なインフラストラクチャのブートストラップを可能とし、 Java SE 環境で Seam を簡単に実行できるようにしました。

例えば、組み込みコンポーネント org.jboss.seam.core.ejb をインストールすることによって Tomcat あるいはテストスイート中で EJB3 コンポーネントを稼動させることが可能です。 それは JBoss 組み込み EJB3 コンテナ (JBoss Embeddable EJB3 container) を自動的にブートストラップし、EJBコンポーネントをデプロイします。

あるいは、もしあなたが EJB 3.0 のすばらしい新世界の用意ができていないならば、 組み込みコンポーネント org.jboss.seam.core.hibernate をインストールすることによって、 永続性のための Hibernate3 とともに JavaBean コンポーネントだけを使う Seam アプリケーションを書くことが可能です。 J2EE 環境の外で Hibernate を使う時には、 たぶん、JTA トランザクション管理と JNDI サーバが必要でしょう。 それは組み込みコンポーネント org.jboss.seam.core.microcontainer を通して使用可能です。 これはにより、 Tomcat のような SE 環境中で JBoss アプリケーションサーバ からの堅牢な (bulletproof) JTA/JCA プーリングデータソースを利用することが可能になります。

3.3. バイジェクション

依存性の注入 あるいは 制御の逆転 は今ではもう大多数の Java 開発者に親しい概念です。 依存性の注入はあるコンポーネントが他のコンポーネントの参照を可能にします。 それはコンテナに setter メソッドあるいはインスタンス変数に他のコンポーネントを「インジェクト (注入) 」させることで実現します。 これまで見てきたすべての依存性の注入の実装では、 インジェクションはコンポーネントが生成されたときに起こり、 その後、参照はコンポーネントのライフサイクルの間で変化しません。 ステートレスコンポーネントにおいて、これは理にかなっています。 クライアントの観点から、特定のステートレスなコンポーネントのすべてのインスタンスは交換可能です。 一方、Seamはステートフルなコンポーネントの使用に重点を置いています。 従って、典型的な依存性の注入はもはやあまり有用な構造ではありません。 Seam はインジェクションの一般化として、バイジェクション (bijection) の概念を導入しました。 インジェクションと対照すると、バイジェクションは以下の通りです。

  • コンテキスト依存 - バイジェクションはさまざまな異なるコンテキストからステートフルなコンポーネントを組み立てるために使用されます。 (「より広い (wider) 」コンテキストからのコンポーネントは「より狭い (narrow) 」コンテキストからの参照も持つかもしれません。)

  • 値はコンテキスト変数から呼ばれるコンポーネントの属性にインジェクトされ、 また、コンポーネント属性からコンテキストにアウトジェクト (outjected) され戻されます。 インスタンス変数そのものを設定することで、呼ばれたコンポーネントが簡単にコンテキスト変数の値を操作することを可能にします。

  • 動的 - バイジェクションはコンポーネントが呼ばれるたびに発生します。 なぜなら、コンテキストの値は時間経過で変化し、 Seam コンポーネントがステートフルだからです。

本質的に、インスタンス変数の値をインジェクト、アウトジェクト、両方により指定することで、 バイジェクションはコンテキスト変数をコンポーネントのインスタンス変数にエイリアスを可能にします もちろん、バイジェクションを可能にするためにアノテーションが使用されています。

@In アノテーションは値がインスタンス変数にインジェクトされることを指定しています。

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    @In User user;
    ... 
}

あるいは、setter メソッド

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    ... 
}

デフォルトでは、 Seam はプロパティ名あるいはインジェクトされたインスタンス変数名を使用して、 すべてのコンテキストの優先順位検索を行います。 例えば、 @In("currentUser")を使用することで明示的にコンテキスト変数を指定することもできます。

指定されたコンテキスト変数と関連した既存のコンポーネントインスタンスが存在しないときに、 Seam にコンポーネントのインスタンスの生成を望むならば、 @In(create=true) を指定する必要があります。 値がオプションで (null でも可能) であれば、@In(required=false) を指定してください。

いくつかのコンポーネントでは、 それらが使用されるところではどこでも繰り返し @In(create=true) を指定する必要があるかもしれません。 このような場合、 コンポーネントに @AutoCreate アノテーションを付けることが可能で、 create=true を明示的に使用しなくても、 必要なとき常に作成されるようになります。

式の値をインジェクトすることも可能です。

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    @In("#{user.username}") String username;
    ... 
}

(コンポーネントライフサイクルとインジェクションについては次章により多くの情報があります。)

@Outアノテーションは、属性がインスタンス変数からもアウトジェクトされるべきことを指定します。

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    @Out User user;
    ... 
}

あるいは getter メソッドから

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    User user;
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

属性値はインジェクトされることもアウトジェクトされることも可能です。

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    @In @Out User user;
    ... 
}

あるいは、

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

3.4. ライフサイクルメソッド

セッション Bean と エンティティ Bean の Seam コンポーネントは、 すべての EJB3.0 ライフサイクルコールバックメソッドをサポートします (@PostConstruct@PreDestroy、など)。 Seam は @PreDestroy を除くこれらすべてのコールバックメソッドを JavaBean コンポーネントに拡張しています。 しかし、Seam はそれ自身のコンポーネントライフサイクルメソッドも定義します。

@Create メソッドは、Seam がコンポーネントをインスタンス化するときはいつでも、 呼び出されます。 @PostConstruct メソッドと違って、 このメソッドはコンポーネントが EJB コンテナによって作成された後に呼び出されます。 そして、一般的な Seam 機能のすべてにアクセスできます (バイインジェクションなど)。 コンポーネントは 1 つの @Create メソッドしか定義できません。

@Destroy メソッドは Seam コンポーネントがバインドしているコンテキストが終了するときに呼び出されます。 コンポーネントは唯一の @Destroy メソッドを定義できます。 ステートフルセッション Bean コンポーネントは、 @Destroy @Remove アノテーションを付けられたメソッドを定義することが 必須 です。

最後に、関連するアノテーションは @Startup アノテーションです。 それはアプリケーションやセッションスコープコンポーネントで利用可能です。 @Startup アノテーションは、 コンテキストが開始されたときにクライアントによる初めての参照を待つのではなく、 Seam に即座にコンポーネントをインスタンス化させさせます。 @Startup(depends={....}) を指定することで、 スタートアップコンポーネントのインスタンス化する順序の制御が可能です。

3.5. 条件付きインストール

@Install アノテーションは、 特定のデプロイメントシナリオでは必須で別の場合はそうでないようなコンポーネントの条件付インストレーションを可能にします。 これは以下の場合に便利です。

  • テストで特定のインフラストラクチャのコンポーネントをモックとしたい。

  • 特定のデプロイメントシナリオでコンポーネント実装を変更したい。

  • 依存性が有効な場合だけに特定のコンポーネントをインストールしたい (フレームワークの作者に便利)。

@Install は works by letting you specify 優先順位依存性 を指定することで動作します。

コンポーネントの優先順位は、 クラスパス中に同じコンポーネント名を持つ複数のクラスがある場合に、 インストールすべきコンポーネントを決定するために Seam が使用する番号です。 Seam はより優先湯順位が高いコンポーネントを選択します。 あらかじめ決められた優先順位の値があります (昇順)。

  1. BUILT_IN — Seam に組み込まれた最も優先順位が低いコンポーネントです。

  2. FRAMEWORK — サードパーティフレームワークによって定義されたコンポーネントは組み込みコンポーネントをオーバーライドする可能性がありますが、 アプリケーションによってオーバーライドされます。

  3. APPLICATION — デフォルト優先順位、 これはほとんどのアプリケーションコンポーネントにおいて適切です。

  4. DEPLOYMENT — デプロイメント固有のアプリケーションコンポーネント用です。

  5. MOCK — テストに使用されるモックオブジェクト用です。

JMS キューと対話する messageSender という名前のコンポーネントがあるとすると、

@Name("messageSender") 
public class MessageSender {
    public void sendMessage() {
        //do something with JMS
    }
}

ユニットテストで、 有効なJMS キューがないので、このメソッドを消してしまいたくなります。 ユニットテストが実行されるときにクラスパスに存在するけれどアプリケーションではデプロイされない mock コンポーネントを作成します。 application:

@Name("messageSender") 
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
    public void sendMessage() {
        //do nothing!
    }
}

優先順位 はクラスパスで両方のコンポーネントを発見したとき、 Seam がどちらのバージョンを使用するかを助けます。

クラスパスにある複数のクラスを正確に制御できるならば、 これはすばらしいことです。 しかし、 多くの依存性を持つ再利用可能なフレームワークを記述している場合、 多くの Jar 全体にそのフレームワークをブレークさせたいとは思わないでしょう。 他にどのようなコンポーネントがインストールされているか、 クラスパス中にどんなクラスが使用可能であるかに応じて、 インストールすべきコンポーネントを決める方法の方が好まれるはずです。 @Install アノテーションはこの機能も制御しています。 Seam は多くの組み込みコンポーネントの条件付きインストールを実現するために内部でこのメカニズムを使用します。 しかし、 ご使用のアプリケーションでは恐らく使用する必要がないでしょう。

3.6. ロギング

こんなうっとうしいコードを見るのに飽き飽きしているのは誰ですか?

private static final Log log = LogFactory.getLog(CreateOrderAction.class);
        
public Order createOrder(User user, Product product, int quantity) {
    if ( log.isDebugEnabled() ) {
        log.debug("Creating new order for user: " + user.username() + 
            " product: " + product.name() 
            + " quantity: " + quantity);
    }
    return new Order(user, product, quantity);
}

簡単なログメッセージのためのコードをどうしてこんなに冗長にすることができるのか想像するのは困難です。 実際のビジネスロジックに関連するコード行よりロギングに関連する方がより多くあります。 Java コミュニティが 10 年の間もっと良いものを考え出せなかったことは本当に驚きです。

Seam はたくさんのコードを簡素化するロギング API を提供します。

@Logger private Log log;
        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
    return new Order(user, product, quantity);
}

ログ 変数が静的であると宣言するかどうかは問題ではありません — ログ 変数が静的である必要があるエンティティ Bean コンポーネント以外なら、 どちらの方法でもうまくいくでしょう。

ストリング連結は、debug( メソッドの内部で起こるため、 うっとうしい if ( log.isDebugEnabled() ) による監視は不要であることに留意してください。 Seam はどのコンポーネントに Log をインジェクトしたかを知っているため、 通常、ログカテゴリを明示的に指定する必要ないことも留意してください。

UserProduct が、 現在のコンテキストで有効な Seam コンポーネントの場合、それはさらに良くなります。

@Logger private Log log;
        
public Order createOrder(User user, Product product, int quantity) {
    log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
    return new Order(user, product, quantity);
}

Seam ロギングは自動的に log4j あるいは JDK logging に出力を送付するかを選択します。 log4j がクラスパスに通っていれば、Seam はそれを使用します。 そうでなければ、Seam は JDK logging を使用します。

3.7. Mutable インタフェースと @ReadOnly

アプリケーションが明示的に setAttribute() を呼び出すとときに、 セッションにバインドした可変オブジェクトの状態変化が複製されるだけなので、 多くのアプリケーションサーバの機能は驚くほどいい加減な HttpSession クラスタリングの実装を持っています。 これはフェイルオーバが発生するときにだけに現れるので、 効果的に開発時間でテストされることができないバグの原因です。 さらに実際の複製メッセージはセッション属性とバインドしたシリアライズされたオブジェクトグラフ全体を含んでいます。 そして、それは非効率です。

もちろん、EJB ステートフルセッション Bean は自動的にダーティなチェックを実行が必要であり、 可変状態の複製と洗練された EJB コンテナは属性レベルの複製など最適化が行うことが可能です。 あいにく、すべての Seam ユーザが EJB 3.0 をサポートする恵まれた環境で作業をしているわけではありません。 そこで、セッションと対話スコープの JavaBean とエンティティ Bean コンポーネントのために、 Seam は Web コンテナセッションクラスタリングの上でクラスタセーフな状態管理の特別なレイヤを提供します。

セッションや対話スコープの JavaBean コンポーネントのために、Seam は、 コンポーネントがアプリケーションにより呼び出されるリクエストの毎に、 setAttribute() を呼ぶことにより自動的に複製を命じます。 もちろん、このストラテジは読み取りばかりするコンポーネントでは不十分です。 この振る舞いは、 org.jboss.seam.core.Mutable インタフェースを実装するか、 org.jboss.seam.core.AbstractMutable を拡張するか、 あるいは、コンポーネント中に独自のダーティチェックのロジックを記述するかにより制御可能です。 以下に例を示します。

@Name("account")
public class Account extends AbstractMutable
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        setDirty(this.balance, balance);
        this.balance = balance;
    }
    
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

あるいは、同様の効果を得るために @ReadOnly アノテーションの使用も可能です。

@Name("account")
public class Account
{
    private BigDecimal balance;
    
    public void setBalance(BigDecimal balance)
    {
        this.balance = balance;
    }
    
    @ReadOnly
    public BigDecimal getBalance()
    {
        return balance;
    }
    
    ...
    
}

セッションや対話スコープのエンティティ Bean コンポーネントの場合、 Seam は 複製が不要な場合、 ( 対話スコープの) エンティティが現在の Seam 管理の永続性コンテキストに関連付けられている限り、 リクエスト毎に setAttribute() を呼ぶことにより自動的に複製の作成を強制します。 このストラテジは必ずしも効率的ではないので、 セッションや対話スコープエンティティ Bean は注意して使用してください。 エンティティ Bean インスタンスを「管理」するために、 ステートフルセッション Bean や JavaBean をいつでも記述することができます。 以下に例を示します。

@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
    private Account account; // an entity bean
    
    @Unwrap
    public void getAccount()
    {
        return account;
    }
    
    ...
    
}

Seam アプリケーションフレームワークにおいて、 EntityHome クラスはこのパターンの優れたサンプルを提供しすることに留意してください。

3.8. ファクトリと管理コンポーネント

Seam コンポーネントではないオブジェクトと連携することもしばしばあります。 でも、やはり @In を使用して Seam コンポーネントにインジェクトし、 値やメソッドバインディング式などでそれらを使いたいと思うことがあります。 時には、それを Seam コンテキストのライフサイクルに関連付ける必要さえあります (例えば @Destroy)。 そこで、Seam コンテキストは Seam ではないいオブジェクトを含むことが可能で、 Seam は、コンテキストにバインドする非コンポーネントと連携することを容易にする 2、3 の優れた機能を提供します。

ファクトリコンポーネントパターン は、 Seam コンポーネントを非コンポーネントオブジェクト用のインスタンス化する機能として動作させます。 ファクトリメソッド は、 コンテキスト変数が参照されたときに呼び出されますが、 それとバインドした値は持っていません。 @Factory アノテーションを使用してファクトリメソッドを定義します。 ファクトリメソッドは値をコンテキスト変数とバインドし、 バインドされた値のスコープを決定します。 2 種類のファクトリメソッドスタイルがあります。 最初のスタイルは、Seam によりコンテキストにバインドされた値を返します。

@Factory(scope=CONVERSATION)
public List<Customer> getCustomerList() { 
    return ... ;
} 

2 番目のスタイルは、 値をコンテキスト変数そのものにバインドした void タイプのメソッドです。

@DataModel List<Customer> customerList;

@Factory("customerList")
public void initCustomerList() { 
    customerList = ...  ;
} 

どちらの場合も、 customerList コンテキスト変数を参照してその値が null になり、 その値のライフサイクルで行うことがこれ以上ない場合、 ファクトリメソッドが呼ばれます。 さらに強力なパターンは 管理コンポーネントパターン です。 この場合、 コンテキスト変数にバインドする Seam コンポーネントがあり、 このコンポーネントがコンテキスト変数の値を管理り、 残りはクライアントで見えない場合です。

管理コンポーネントは @Unwrap メソッドを持つすべてのコンポーネントです。 このメソッドは、クライアントに見えなくなる値を返し、 毎回 コンテキスト変数が参照されれば呼び出されます。

@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
    ...
    
    @Unwrap
    public List<Customer> getCustomerList() { 
        return ... ;
    }
}

コンテキストが終了するとき、クリーンアップが必要な重いオブジェクトを持つとき、 このパターンは特に役立ちます。 この場合、管理コンポーネントは @Destroy でクリーンアップを実行することもできます。

第4章 Seamコンポーネントの設定

XMLベースの構成を最小にするという哲学はSeamでは徹底されています。 それにもかかわらず、XMLを使ってSeamを設定したいというさまざまな理由が存在します。 Javaコードからデプロイメント固有の情報を切り離したい、 再利用可能なフレームワークを作成可能にしたい、 Seam組み込み機能を設定したい等の理由です。 Seamはコンポーネントを設定する二つのアプローチを提供します。 プロパティファイルまたはweb.xmlでのプロパティ設定による設定と、 components.xml による設定です。

4.1. プロパティ設定によるコンポーネントの設定

Seamコンポーネントには、サーブレットコンテキストパラメータ、あるいはクラスパスのルートに存在するseam.propertiesと名付けられたプロパティファイルのいずれかによって、設定プロパティを提供することが可能です。

設定可能はSeamコンポーネントは、設定属性に対してJavaBeansスタイルのプロパティ用セッターメソッドを公開しなければなりません。 com.jboss.myapp.settingsという名前のSeamコンポーネントが、setLocale () という名前のセッターメソッドを持つとすると、seam.propertiesファイルあるいはサーブレットコンテキストパラメータでcom.jboss.myapp.settings.localeという名前のプロパティを提供することが可能となり、Seamはそのコンポーネントを生成するときにはlocale属性の値を設定します。

同じメカニズムはSeam自身の設定にも使われます。たとえば、対話のタイムアウトを設定するには、 web.xmlまたは seam.propertiesにおいて、 org.jboss.seam.core.manager.conversationTimeoutの値を提供します。 (org.jboss.seam.core.managerという名前の組み込みSeamコンポーネントが存在し、 それにはsetConversationTimeout () というセッターメソッドがあります。)

4.2. components.xmlによるコンポーネントの設定

components.xmlファイルは、次に示すようにプロパティ設定よりも少しばかり強力です。

  • 自動的にインストールが完了済みとなっているコンポーネントを設定できます。 これには組み込みコンポーネントと、Seamデプロイメントスキャナーによって検出された @Nameアノテーション付きのアプリケーションコンポーネント、 の両方が含まれます。

  • @Nameアノテーションの無いクラスをSeamコンポーネントとしてインストール可能です。 これは異なる名前で複数回インストールされるようなある種のインフラストラクチャコンポーネントにとって最も有用です (たとえば、Seam管理対象永続コンテキスト) 。

  • @Nameアノテーションを持ってはいるものの、コンポーネントをインストールしないことを示す@Installのためにデフォルトでインストールしないコンポーネントをインストール可能です。

  • コンポーネントのスコープを上書き (override) できます。

components.xmlファイルは次の3つの異なる場所に置くことができます。

  • warWEB-INFディレクトリ

  • jarMETA-INFディレクトリ

  • @Nameアノテーション付きのクラスを含む任意のjar

通常、Seamコンポーネントはデプロイメントスキャナーがseam.properties ファイルやMETA-INF/components.xmlを持つアーカイブ内で @Nameアノテーションの付いたクラスを発見したときにインストールされます (ただし、@Install アノテーションがデフォルトでインストールしないと 指定していない限り) 。 components.xmlファイルを使えば、 アノテーションを上書きする必要があるような特別な場合に対処することができます。

たとえば、次のcomponents.xmlファイルは JBoss組み込みEJBコンテナをインストールします。

<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:core="http://jboss.com/products/seam/core">
    <core:ejb/>
</components>

これは以下と同じことをします。

<components>
    <component class="org.jboss.seam.core.Ejb"/>
</components>

これは2種類の異なるSeam管理対象永続コンテキストとインストールと設定を行います。

<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:core="http://jboss.com/products/seam/core"

    <core:managed-persistence-context name="customerDatabase"
                persistence-unit-jndi-name="java:/customerEntityManagerFactory"/>
        
    <core:managed-persistence-context name="accountingDatabase"
                persistence-unit-jndi-name="java:/accountingEntityManagerFactory"/>            

</components>

これは以下と同じです。

<components>
    <component name="customerDatabase" 
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/customerEntityManagerFactory</property>
    </component>
    
    <component name="accountingDatabase"
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/accountingEntityManagerFactory</property>
    </component>
</components>

この例はセッションスコープのSeam管理対象永続コンテキストを生成します (実際には推奨されるものではありません) 。

<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:core="http://jboss.com/products/seam/core"

  <core:managed-persistence-context name="productDatabase" 
                                   scope="session"
              persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components>
<components>
            
    <component name="productDatabase"
              scope="session"
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/productEntityManagerFactory</property>
    </component>

</components>

永続コンテキストのような基盤となるオブジェクトに対してauto-create オプションを使用するのは一般的なことです。そうすることで、@In アノテーションを使うときに明示的にcreate=trueを指定することを防ぐ ことができます。

<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:core="http://jboss.com/products/seam/core"

  <core:managed-persistence-context name="productDatabase" 
                             auto-create="true"
              persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components>
<components>
            
    <component name="productDatabase"
        auto-create="true"
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/productEntityManagerFactory</property>
    </component>

</components>

<factory>宣言は、値もしくはメソッド結合式を指定して、 それが最初に参照されたときにコンテキスト変数値を初期化するようにできます。

<components>

    <factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>

</components>

Seamコンポーネントの「エイリアス」 (別名) が生成可能です。

<components>

    <factory name="user" value="#{actor}" scope="STATELESS"/>

</components>

よく使用される式に対しても「エイリアス」を生成することすら可能です。

<components>

    <factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>

</components>

<factory>宣言でauto-create="true"を使うことは 日常的によく目にすることです。

<components>

    <factory name="session" value="#{entityManager.delegate}" scope="STATELESS" auto-create="true"/>

</components>

デプロイとテストの両方においてcomponents.xmlファイルをほんの少し修正 するだけで同じファイルを再利用したいということがあります。 Seamはcomponents.xmlファイル内に@wildcard@形式 のワイルドカードを配置することが可能で、Antビルドスクリプト (デプロイ時) やクラスパスに components.propertiesというファイルを与えること (開発時) によって 値を置き換えることができます。このアプローチはSeamサンプルプログラムで見ることができます。

4.3. 細分化した設定ファイル

もしもXMLで設定が必要な大量のコンポーネントがあるなら、components.xml に含まれる情報をを多くの細かなファイルに分割することは意味があるでしょう。 Seamはあるクラス com.helloworld.Helloの設定を com/helloworld/Hello.component.xmlという名前のリソース内に 置くことができます。 (もしかしたらこのパターンに見覚えがあるかもしれません。なぜなら、 Hibernateでも同様のやり方をしているからです。) そのファイルのルート要素は<components> または <component>要素のいずれかが可能です。

最初のオプションはファイル内に複数コンポーネントの定義が可能です。

<components>
    <component class="com.helloworld.Hello" name="hello">
        <property name="name">#{user.name}</property>
    </component>
    <factory name="message" value="#{hello.message}"/>
</components>

二番目のオプションは単一コンポーネントしか定義または設定できませんが、 煩雑さはありません。

<component name="hello">
    <property name="name">#{user.name}</property>
</component>

二番目のオプションでは、クラス名はコンポーネント定義が登場するファイル名によって暗に指定されます。

あるいは、com/helloworld/components.xmlcom.helloworldパッケージ内のすべてのクラスの設定をすることも可能です。

4.4. 設定可能なプロパティの型

文字列、プリミティブ、プリミティブラッパー型は、あなたが予想する通りに設定できます。

org.jboss.seam.core.manager.conversationTimeout 60000
<core:manager conversation-timeout="60000"/>
<component name="org.jboss.seam.core.manager">
    <property name="conversationTimeout">60000</property>
</component>

文字列またはプリミティブから構成される配列、セット、リストもサポートされます。

org.jboss.seam.core.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml
<core:jbpm>
    <core:process-definitions>
        <value>order.jpdl.xml</value>
        <value>return.jpdl.xml</value>
        <value>inventory.jpdl.xml</value>
    </core:process-definitions>
</core:jbpm>
<component name="org.jboss.seam.core.jbpm">
    <property name="processDefinitions">
        <value>order.jpdl.xml</value>
        <value>return.jpdl.xml</value>
        <value>inventory.jpdl.xml</value>
    </property>
</component>

文字列値のキーと、文字列またはプリミティブの値から成るマップでさえもサポートされます。

<component name="issueEditor">
    <property name="issueStatuses">
        <key>open</key> <value>open issue</value>
        <key>resolved</key> <value>issue resolved by developer</value>
        <key>closed</key> <value>resolution accepted by user</value>
    </property>
</component>

最後に、値結合式 (value-binding expression) を使ってコンポーネントを連携させることができます。 これは@Inをつかった注入とはまったく異なるので注意してください。 なぜなら、それは呼び出し時ではなく、コンポーネント生成時に起こるからです。 したがって、JSFやSpringのような既存のIoCコンテナによって提供される依存性注入により近いです。

<drools:managed-working-memory name="policyPricingWorkingMemory" rule-base="#{policyPricingRules}"/>
<component name="policyPricingWorkingMemory"
          class="org.jboss.seam.drools.ManagedWorkingMemory">
    <property name="ruleBase">#{policyPricingRules}</property>
</component>

4.5. XML名前空間の使用

サンプルプログラムを通して、2種類の相異なるコンポーネント宣言の方法があります。 XML名前空間を使用する方法と使用しない方法です。次は、名前空間を使用しない典型的な components.xmlファイルを示します。 それはSeamコンポーネントDTDを使っています。

<?xml version="1.0" encoding="UTF-8">
<!DOCTYPE components PUBLIC "-//JBoss/Seam Component Configuration DTD 1.2//EN"
                            "http://jboss.com/products/seam/components-1.2.dtd">
<components>

    <component class="org.jboss.seam.core.init">
        <property name="debug">true</property>
        <property name="jndiPattern">@jndiPattern@</property>
    </component>

    <component name="org.jboss.sean.core.ejb" installed="@embeddedEjb@" />
    
</components>

ご覧の通り、これは幾分煩雑です。 さらに悪いことには、コンポーネントと属性の名前は、デプロイ時の妥当性検証の対象となりません。

名前空間を使ったバージョンはこのようになります。

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-1.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd">

    <core:init debug="true" jndi-pattern="@jndiPattern@"/>

    <core:ejb installed="@embeddedEjb@"/>

</components>

スキーマ宣言は冗長ではありますが、実際のXMLの内容は簡潔かつ理解しやすいものです。 このスキーマは利用可能な各コンポーネントと属性に関する詳細情報を提供するもので、 XMLエディタでインテリジェントな自動補完入力を可能にします。 名前空間付きの要素の使用は、正しいcomponents.xmlファイルの生成と保守をより簡単にしてくれます。

さて、これは組み込みSeamコンポーネントに対しては良く機能するようですが、はたしてユーザコンポーネントに対してはどうでしょうか? 最初に、Seamは2つの混在したモデルをサポートします。 1つはユーザコンポーネントに対する一般的な<component> 宣言。 もう1つは組み込みコンポーネントに対する名前空間付きの宣言です。 Seamはユーザコンポーネントに対しても簡単に名前空間を宣言できるようにしてくれています。

任意のJavaパッケージには、@Namespaceアノテーションをパッケージに付加することによって、XML名前空間を関連付けることができます。 (パッケージレベルのアノテーションは、パッケージディレクトリ内のpackage-info.javaという名前のファイルで宣言されます。) これはseapayデモからの例です。

@Namespace(value="http://jboss.com/products/seam/examples/seampay")
package org.jboss.seam.example.seampay;

import org.jboss.seam.annotations.Namespace;

やらなければならないことは、components.xmlで名前空間スタイルを使うことだけです! こうして次のように書くことが可能になります。

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home new-instance="#{newPayment}"
                      created-message="Created a new payment to #{newPayment.payee}" />

    <pay:payment name="newPayment"
                 payee="Somebody"
                 account="#{selectedAccount}"
                 payment-date="#{currentDatetime}"
                 created-date="#{currentDatetime}" />
     ...
</components>

または、

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home>
        <pay:new-instance>"#{newPayment}"</pay:new-instance>
        <pay:created-message>Created a new payment to #{newPayment.payee}</pay:created-message>
    </pay:payment-home>
    
    <pay:payment name="newPayment">
        <pay:payee>Somebody"</pay:payee>
        <pay:account>#{selectedAccount}</pay:account>
        <pay:payment-date>#{currentDatetime}</pay:payment-date>
        <pay:created-date>#{currentDatetime}</pay:created-date>
     </pay:payment>
     ...
</components>

これらのサンプルは名前空間付き要素の2つの利用モデルを説明します。 最初の宣言では<pay:payment-home>paymentHomeコンポーネントを参照しています。

package org.jboss.seam.example.seampay;
...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

その要素名はコンポーネント名をハイフンで連結した形式になっています。 その要素の属性名はプロパティ名をハイフンで連結した形式になっています。

二番目の宣言では、<pay:payment>要素はorg.jboss.seam.example.seampayパッケージでのPaymentクラスを参照します。 Payment のケースでは、あるエンティティがSeamコンポーネントとして宣言されようとしています。

package org.jboss.seam.example.seampay;
...
@Entity
public class Payment
    implements Serializable
{
    ...
}

ユーザ定義コンポーネントに対して妥当性検証と自動補完入力が機能するようにしたいなら、 スキーマが必要になります。Seamはコンポーネントの集まりからスキーマを自動生成するような機能 はまだ提供していませんので、手動で生成する必要があります。標準的なSeamパッケージのスキーマ定義はガイドとして利用できます。

次はSeamによって使用済みの名前空間です。

  • components —http://jboss.com/products/seam/components

  • core —http://jboss.com/products/seam/core

  • drools —http://jboss.com/products/seam/drools

  • framework —http://jboss.com/products/seam/framework

  • jms —http://jboss.com/products/seam/jms

  • remoting —http://jboss.com/products/seam/remoting

  • theme —http://jboss.com/products/seam/theme

  • security —http://jboss.com/products/seam/security

  • mail —http://jboss.com/products/seam/mail

  • web —http://jboss.com/products/seam/web

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

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

5.1. Seam イベント

Seam コンポーネントモデルはイベント駆動アプリケーション で使うために開発されました。 特に、 微調整が行われたイベントモデルで微調整を必要とする疎結合コンポーネントの開発を可能にします。 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 種類のイベントについて見ていきます。

5.1.1. ページアクション

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 は曖昧な指定から明確な指定への順でそれらすべてのアクションを呼び出します。

ページアクションのメソッドは JSF outcome を返すことができます。 その outcome が null 以外の場合、 Seam はビューのナビゲートに定義済みナビゲーション規則を使用します。

さらに、 <page> 要素で指定されたビュー id は実際の JSP や Facelets ページに対応する必要がありません。 このため、 ページアクションを使用した Struts や WebWork のような従来のアクション指向フレームワークの機能を再現することができます。

TODO: translate struts action into page action

non-facesリクエスト (たとえば、 HTTP Get リクエスト) に対するレスポンスで複雑な処理をしたい場合などに非常に便利です。

5.1.1.1. ページパラメータ

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 へ戻るような) 他の任意のページを得ることができるのにもかかわらず、 値結合で参照されるモデル属性の値は対話 (または他のサーバー側の状態) を必要とせずに「記録されている」ということです。

かなり複雑な構造で、 このような非標準の構造が本当に役に立つのだろうかと不思議に思われるかもしれません。 実際には、 理解するとこの概念はごく自然なことになりますので、 時間を割いて理解するだけの価値は十分にあります。 ページパラメータは non-faces リクエスト全体への状態の伝播に最適な方法となります。 同じコードで POST と GET の両リクエストを処理する独自のアプリケーションコードを記述できるようにしたい場合、 特にブックマーク可能な結果ページでの画面検索などの問題に対して役立ちます。 ページパラメータによりビュー定義内でのリクエストパラメータの反復表示を解消し、 コードに 対するリダイレクトをより容易ににします。

ページパラメータを使用するために実際のページアクションメソッドバインディングは必要ありません。 以下で十分に有効になります。

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

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>

5.1.1.2. ナビゲーション

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

  • リダイレクトする場合はリクエストパラメータを指定できません。

  • 規則から対話の開始や終了はできません。

  • 規則はアクションメソッドの戻り値の評価によって動作します。 つまり、 任意の EL 式を評価することはできません。

さらに問題なのは組合せ (orchestration) のロジックが 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>

しかし、 文字列の値を持つ戻り値 (JSF outcome) と DocumentEditor コンポーネントを併用する必要がなかったならば、 より良かったでしょう。そこで 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>

最初の形式は後続の規則によって使用されるように outcome の値を決定する値結合を評価します。 二番目のアプローチは outcome を無視し、 それぞれ可能性のある規則に対して値結合を評価します。

更新が成功したら当然、 現在の対話を終了させたい場合がほとんどでしょう。 これには、次のようにします。

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

ただし、 対話を終了すると、 現在表示させているドキュメントなどその対話に関連する状態はすべて失うことになります。 解決策のひとつとして、 リダイレクトを使わず直接レンダリングを使う方法です。

<page view-id="/editDocument.xhtml">
    
    <navigation from-action="#{documentEditor.update}">
        <rule if="#{documentEditor.errors.empty}">
            <end-conversation/>
            <render 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>

5.1.1.3. ナビゲーション、ページアクション、パラメータの定義用に細分化したファイル

ページアクションやページパラメータが大量にある、 または単にナビゲーション規則を多量に持っている場合、 宣言を複数のファイルに分割したいことでしょう。 ビュー 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>

5.1.2. コンポーネント駆動イベント

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

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

<components>
    <event type="hello">
        <action expression="#{helloListener.sayHelloBack}"/>
        <action expression="#{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);
    }
}

5.1.3. コンテキスト依存イベント

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.beginPageflow.<name> — ページフローの <name> が開始すると呼び出されます

  • 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.postAuthenticate.<name> — ユーザーが認証されると呼び出されます

  • org.jboss.seam.preAuthenticate.<name> — ユーザー認証が試行される前に呼び出されます

  • org.jboss.seam.notLoggedIn — 認証されるユーザーがなく認証が必要とされている場合に呼び出されます

  • org.jboss.seam.rememberMe — Seam セキュリティがクッキーにユーザー名を検出すると発生します

Seam コンポーネントは、 他のコンポーネント駆動イベントを監視するのとまったく同じようにこれらのいずれのイベントも監視することができます。

5.2. Seam インターセプタ

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 は除きます)。

5.3. 例外を管理する

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

5.3.1. 例外およびトランザクション

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

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

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

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

5.3.2. 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 デバッグモードも無効にする必要があります。

5.3.3. 例外処理に対してアノテーションを使用する

次の例外は 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 { ... }

@Redirect は JSF ライフサイクルのレンダリングフェーズ中に発生した例外に対しては動作しないので注意してください。

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

@Redirect(viewId="/error.xhtml", message="Unexpected error")
public class SystemException extends RuntimeException { ... }

5.3.4. 例外処理に XML を使用する

興味を持っている例外クラスすべてに対してアノテーションを付加することはできないので、 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 によってキャッチした例外インスタンスにアクセスすることができます。 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">#{handledException.message}</message>
        </redirect>
    </exception>

</pages>

第6章 対話とワークスペースの管理

本章では、 そろそろ Seam の対話モデルについて詳細に理解していくことにします。

事のはじまりは、 3 つの思いつきが融合した結果、 Seam 「対話」の概念となったことです。

  • ワークスペース という目的、 これは 2002 年にビクトリア州政府 (オーストラリア) のプロジェクトで思いつきました。 このプロジェクトで、私は Struts の上にワークスペース管理を実装せざるを得なくなりました。2 度と繰り返したいとは思わないような経験でした。

  • 楽観的セマンティクスで動作するアプリケーショントランザクションという思いつきに加え、 ステートレスなアーキテクチャをベースとする既存のフレームワークでは拡張された永続コンテキストの効率的な管理は実現できないことを実感した事実でした。 (Hibernate チームは LazyInitializationException に対する非難は聞き飽きていましたし、 実際にはこれは Hibernate に問題があるのではなく、 むしろ Spring フレームワークや J2EE における従来の stateless session facade (anti) パターンなどステートレスアーキテクチャでサポートされる極端に限定的な永続コンテキストモデルに問題があったのです。)

  • ワークフロータスクという思いつき

こうした思いつきを統一しフレームワークで強力なサポートを提供することで、 以前よりすっきりしたコードでより豊かで効率的なアプリケーションをビルドできるパワフルな構成概念を得ました。

6.1. Seam の対話モデル

これまでに見た例は、以下の規則に従う非常に単純な対話モデルを利用します。

  • JSF リクエストライフサイクルである、 リクエスト値の適用、 プロセスの妥当性検証、 モデル値の更新、 アプリケーションの呼び出し、 レスポンスのレンダリングの各フェーズの期間、 常にアクティブな対話コンテキストがあります。

  • JSF リクエストライフサイクルであるビューの復元フェーズの終わりで、 Seam はそれまでの長期対話コンテキストの復元を試みます。 存在しなければ、 Seam は新しいテンポラリの対話コンテキストを生成します。

  • @Begin メソッドが出てくると、 テンポラリの対話コンテキストは長期対話に昇格します。

  • @End メソッドが出てくると、 どのような長期対話コンテキストでもテンポラリの対話に降格されます。

  • JSF リクエストライフサイクルであるレスポンスのレンダリングのフェーズの終わりで、 Seam は長期対話コンテキストの内容を記憶するか、 テンポラリ対話コンテキストの内容を破棄します。

  • どのような faces リクエスト (JSF ポストバック) でも対話コンテキストを伝播します。 デフォルトでは、 non-faces リクエスト (例えば、 GET リクエスト) は対話コンテキストを伝播しませんが、 これについての詳細は下記を参照してください。

  • JSF リクエストライフサイクルがリダイレクトによって短縮される場合、 Seam は透過的に現在の対話コンテキストを保存及び復元します。 — その対話が @End (beforeRedirect=true) で既に終了されていない限り。

Seam は透過的に JSF のポストバックとリダイレクト全体に渡り対話コンテキストを伝播します。 特に何もしなければ、 non-faces リクエスト (例えば、 GETリクエスト) は対話コンテキストを伝播せずに新しいテンポラリの対話内で処理されます。 これが通常 (必ずではないが) 望ましい動作となります。

non-faces リクエスト全体に Seam 対話を伝播させたい場合、 リクエストパラメータとして Seam 対話 ID を明示的にコード化する必要があります。

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

JSF のようにする場合には以下のようにします。

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

Seam タグライブラリを使用する場合、 以下は等価です。

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

ポストバック用の対話コンテキストの伝播を無効にしたい場合は、 同様のテクニックが使えます。

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

Seam タグライブラリを使用する場合、 以下は等価です。

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

対話コンテキストの伝播を無効にすることと、 対話を終了することとは全く異なることですので注意してください。

conversationPropagation リクエストパラメータ または <s:conversationPropagation> タグは、 対話の開始と終了、あるいはネストされた対話の開始にも使用することができます。

<h:commandLink action="main" value="Exit">
    <s:conversationPropagation type="end"/>
</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>

この対話モデルは、 マルチウィンドウ操作に関して正常に動作するアプリケーションの構築を容易してくれます。 多くのアプリケーションにとって必要なものはこれだけです。 複雑なアプリケーションのなかには以下の追加要件の両方あるいはどちらかを必要とするものがあります。

  • 対話には、 連続的に実行したり同時に実行することもある多くの小さな単位のユーザーインタラクションも含まれます。 より小さいネストされた対話には単独の対話状態セットがあり、 また外側の対話状態へのアクセスもあります。

  • ユーザは同じブラウザのウィンドウ内でいくつもの対話を切り換えることができます。 この機能がワークスペース管理と呼ばれるものです。

6.2. ネストされた対話

ネストされた対話は既存の対話のスコープ内で @@Begin(nested=true) とマークされたメソッドを呼び出すことによって作成されます。 ネストされた対話はそれ自身の対話コンテキストを持っていて、 また、 外側の対話のコンテキストへの読取り専用アクセスも持っています (外側の対話のコンテキスト変数を読むことはできるが、書き込みはできない) 。 次に @End が出てくると、 ネストされた対話は破棄されて外側の対話が対話スタックを「POP」することによって再開します。 対話は任意の深さにネストすることができます。

特定のユーザーアクティビティ (ワークスペース管理や戻るボタン) により、 内側の対話が終了する前に外側の対話が開始させることができます。 この場合、 同じ外側の対話に属する複数の並列ネスト対話を持つことができます。 ネストされた対話が終了する前に外側の対話が終了すると、 Seam はネストされた対話コンテキストを外側のコンテキストと共にすべて破棄します。

対話は継続可能な状態と見なすこともできます。 ネストされた対話により、 ユーザーインタラクションにおける様々なポイントにおいてアプリケーションは一貫した継続可能な状態を捕らえることができるようになります。 従って、 戻るボタンを押すことやワークスペース管理に対して正しい動作を保証します。

TODO: 戻るボタンを押した場合に、 どのようにしてネストされた対話が不正が発生しないよう防止するかを示す例。

通常、 現在ネストされている対話の親となる対話にコンポーネントが存在する場合、 このネストされている対話は同じインスタンスを使用します。ときには、 親となる対話内に存在するコンポーネントインスタンスがその子となる対話からは見えなくなるように、 ネストされるそれぞれの対話内に別々のインスタンスを持たせると便利なことがあります。 コンポーネントに @PerNestedConversation アノテーションを付けるとこれを行うことができます。

6.3. GET リクエストを使って対話を開始する

ページが non-faces リクエスト (例、 HTTP GET リクエスト) 経由でアクセスされる場合、 JSF は起動されるアクションリスナを全く定義しません。 ユーザーがそのページをブックマークする、あるいは <h:outputLink> からそのページに行き着く場合などに発生します。

ページがアクセスされたら直ちに対話を開始したい場合があります。 JSF アクションメソッドがないため、 アクションに @Begin アノテーションを付けるという普通の方法では問題を解決することができません。

このページが状態をコンテキスト変数にフェッチする必要がある場合、 さらなる問題が発生します。 すでに、2 つの問題解決方法を見てきました。 Seam コンポーネントにその状態が保持される場合、 @Create メソッドでその状態をフェッチできます。 保持されていなければ、 コンテキスト変数に対して @Factory メソッドを定義することができます。

これらのオプションがうまくいかない場合、 Seam では pages.xml ファイルに ページアクション を定義することができます。

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

ページがレンダリングされるようとするときは常に、 レスポンスのレンダリングフェーズの冒頭でこのアクションメソッドが呼び出されます。 ページアクションが null 以外の結果を返す場合、 Seam は適切な JSF 及び Seam ナビゲーションの規則を処理するので、 まったく異なるページがレンダリングさることになるかもしれません。

ページのレンダリング前に行いたいことが対話の開始だけなら、 ビルトインアクションメソッドを次のように使用できます。

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

また、 このビルトインアクションは JSF コントロールからも呼び出すことができ、 同様に #{conversation.end} を使って対話を終了することができます。

既存の対話にジョインするあるいはネストされる対話を開始する、 ページフローまたはアトミック対話を開始するためにさらに制御が必要な場合は、 <begin-conversation> エレメントを使用してください。

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

また、 <end-conversation> エレメントもあります。

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

1 番目の問題を解決するには、 現在 5 つのオプションから選択できます。

  • @Create メソッドに @Begin アノテーションを追加する

  • @Factory メソッドに @Begin アノテーションを追加する

  • Seam ページアクションメソッドに @Begin アノテーションを追加する

  • pages.xml<begin-conversation> を使用する

  • #{conversation.begin} を Seam ページアクションメソッドとして使用する

6.4. <s:link><s:button> の使いかた

JSF コマンドリンクは常に JavaScript でフォームサブミットを行います。 これによりウェブブラウザの「新しいウィンドウで開く」または「新しいタブで開く」機能を動作させなくしてしまいます。 プレーンの JFS でこの機能が必要な場合は、 <h:outputLink> を使用する必要があります。 ただし、 <h:outputLink> には重要な制限が 2 つあります。

  • JSF はアクションリスナーを <h:outputLink> につなげる方法を提供していません。

  • 実際にフォームサブミットがないため、 JSF は選択された DataModel の列を伝播しません。

Seam は 1 番目の問題解決に対しては ページアクションという概念を提供していますが、 これは 2 番目の問題についてはまったく役に立ちません。 これについてはリクエストパラメータを渡すという RESTful 方法を使ってサーバー側にある選択オブジェクトに再問い合わせを行うことで回避できました。 Seam ブログサンプルアプリケーションなどの場合には、 実際にこれが最適の手段となります。 RESTful スタイルはサーバー側の状態を必要としないためブックマーク機能をサポートします。 ブックマークはあまり必要ないなどこれ以外の場合は、 @DataModel および @DataModelSelection を使用すると非常に便利で透過的になります。

この機能を補ってさらに対話伝播の管理をより簡略化するために、 Seam は <s:link> JSF タグを提供しています。

このリンクは JSF ビュー ID だけを指定することができます。

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

あるいは、 アクションメソッドを指定することができます (この場合、 アクションの結果は最終的なページを確定する)。

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

JSF ビュー ID とアクションメソッドの両方を指定すると、 アクションメソッドが null 以外の結果を返さない限り「ビュー」が使用されます。

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

リンクは <h:dataTable> 内で使用する DataModel の選択列を自動的に伝播します。

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

既存の対話のスコープを残しておくことができます。

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

対話を開始、 終了、 またはネストすることができます。

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

リンクが対話を開始すると、 使用されるページプローを指定することもできます。

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

jBPM タスクリストを使用する場合の taskInstance 属性です。

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

(上記の例は DVD ストアデモアプリケーションを参照してください。)

最後に、 ボタンとしてレンダリングされる「リンク」が必要な場合は <s:button> を使用します。

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

6.5. 成功のメッセージ

動作に対して成功したか失敗したかをユーザーに示すメッセージを表示するのは非常に一般的です。 これには、 JSF FacesMessage を使うと便利です。 残念ながら、 成功のアクションはブラウザリダイレクトを要することが多く、 JSF はリダイレクト全体に faces のメッセージは伝播しません。 このためプレーン JSF で成功のメッセージを表示するのはかなり困難になります。

ビルトインの対話スコープ Seam コンポーネントである facesMessages がこの問題を解決してくれます。 (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");
    }
}

facesMessages に追加されるメッセージはすべてすぐ次のフェースであるレスポンスレンダリングフェーズで現在の対話に対して使用されます。 これは Seam がリダイレクト全体に一時的な対話コンテキストを維持するので長期実行の対話がない場合でも機能します。

JSF EL 式を faces メッセージサマリーに含ませることもできます。

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

たとえば、 通常の方法でメッセージを表示することができます。

<h:messages globalOnly="true"/>

6.6. 「明示的な」対話 ID の使いかた

通常、 Seam は各セッション内の各対話に対して特に意味のない固有 ID を生成します。 対話を開始するときに、 この ID 値をカスタマイズすることができます。

この機能を使って対話 ID 生成アルゴリズムをカスタマイズすることができます。

@Begin(id="#{myConversationIdGenerator.nextId}") 
public void editHotel() { ... }

あるいは、 何か意味のある対話 ID を割り当てるのに使うこともできます。

@Begin(id="hotel#{hotel.id}") 
public String editHotel() { ... }
@Begin(id="hotel#{hotelsDataModel.rowData.id}") 
public String selectHotel() { ... }
@Begin(id="entry#{params['blogId']}")
public String viewBlogEntry() { ... }
@BeginTask(id="task#{taskInstance.id}") 
public String approveDocument() { ... }

ご覧のように、 上記の例では、 特定のホテル、ブログ、あるいはタスクが選択される毎に同じ対話 id となります。 既に存在するものと同じ対話 id での対話では何が起こるのでしょうか? そうです。 Seam は既存の対話を検出し、 @Begin メソッドを再び開始することなくその対話へリダイレクトします。 この特長は、 ワークスペース管理を使用するときに生成されるワークスペースの数を制御する際に役立ちます。

6.7. ワークスペースの管理

ワークスペース管理は、1 つのウィンドウの中で対話を「切り換える」能力です。 Seam はワークスペース管理を Java コードのレベルで完全に透過的にします。 ワークスペース管理を可能にするために、必要なすべては以下の通りです。

  • それぞれのビュー ID (JSF または Seam ナビゲーションルールを使用する場合) またはページノード (jPDL ページフロー) に詳細のテキストを入力します。 この詳細テキストはワークスペース切り替えによってユーザーに表示されます。

  • ページの中に 1 つ以上の標準ワークスペース切り替え JSP または facelets の断片を含ませます。 標準の断片はドロップダウンメニュー、 対話のリスト、 ブレッドクラム (breadcrumbs) を介してワークスペース管理をサポートします。

6.7.1. ワークスペース管理と JSF ナビゲーション

JSF または Seam ナビゲーションルールを使用する場合、 Seam は対話の現在の view-id を復元してその対話に切り替えます。 ワークスペースの記述的なテキストは pages.xml と呼ばれるファイルで定義され、 Seam はこのファイルが WEB-INF ディレクトリ内の faces-config.xml のすぐ次に配置されていることを期待します。

<pages>
    <page view-id="/main.xhtml">Search hotels: #{hotelBooking.searchString}</page>
    <page view-id="/hotel.xhtml">View hotel: #{hotel.name}</page>
    <page view-id="/book.xhtml">Book hotel: #{hotel.name}</page>
    <page view-id="/confirm.xhtml">Confirm: #{booking.description}</page>
</pages>

このファイルが期待する場所になくても Seam アプリケーションは正常に動作を続行します。 動作しない機能はワークスペースの切り替え機能のみです。

6.7.2. ワークスペース管理と jPDL ページフロー

jPDL ページフロー定義を使う場合、 Seam は現在の jBPM のプロセス状態を復元することによって対話に切り替えます。 同じ view-id に現在の <page> に応じて異なる詳細を持たせることができるためこれはより柔軟なモデルになります。 詳細テキストは <page> ノードで定義されます。

<pageflow-definition name="shopping">

   <start-state name="start">
      <transition to="browse"/>
   </start-state>
   
   <page name="browse" view-id="/browse.xhtml">
      <description>DVD Search: #{search.searchPattern}</description>
      <transition to="browse"/>
      <transition name="checkout" to="checkout"/>
   </page>
   
   <page name="checkout" view-id="/checkout.xhtml">
      <description>Purchase: $#{cart.total}</description>
      <transition to="checkout"/>
      <transition name="complete" to="complete"/>
   </page>
   
   <page name="complete" view-id="/complete.xhtml">
      <end-conversation />
   </page>
   
</pageflow-definition>

6.7.3. 対話切り替え

次の断片を JSP または facelets のページに含ませて、 現在の対話またはその他いずれのアプリケーションのページにも切り替えられるドロップダウンメニューを取得します。

<h:selectOneMenu value="#{switcher.conversationIdOrOutcome}">
    <f:selectItem itemLabel="Find Issues" itemValue="findIssue"/>
    <f:selectItem itemLabel="Create Issue" itemValue="editIssue"/>
    <f:selectItems value="#{switcher.selectItems}"/>
</h:selectOneMenu>
<h:commandButton action="#{switcher.select}" value="Switch"/>

この例では、 ユーザに新しい対話を開始させる 2 つの追加アイテムに加えて、 各対話のためのアイテムを含むメニューがあります。

6.7.4. 対話一覧

対話一覧は対話切り替えに非常によく似ていますが、 表形式で表示される点が異なります。

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

恐らく、 多くの方が独自のアプリケーションに合うようカスタマイズを希望するだろうと思います。

対話一覧は便利ですが、 ページ上で対話一覧によって占有される領域が多いため、 すべてのページに対しては対話一覧を置きたくないでしょう。

対話一覧によりユーザーがワークスペースを破壊することができるので留意してください。

6.7.5. ブレッドクラム (Breadcrumbs)

ブレッドクラムは、 ネストされた対話モデルを使うアプリケーションで役に立ちます。 ブレッドクラムは、 現在の対話スタック内の対話へのリンクの一覧になります。

<t:dataList value="#{conversationStack}" var="entry">
    <h:outputText value=" | "/> 
    <h:commandLink value="#{entry.description}" action="#{entry.select}"/>
</t:dataList>

JSF は驚いたことにループに対して標準コンポーネントを全く提供していないため、 ここでは、 MyFaces <t:dataList> コンポーネントを使用していることに注意してください。

これらすべての機能が動作しているところを確認するには、 Seam 問題追跡システム (Seam Issue Tracker) デモを参照してください。

6.8. 対話型コンポーネントと JSF コンポーネントのバインディング

重要ではありませんが、 JSF コンポーネントへのバインディングの保持には使用できないという制限が対話型コンポーネントにはあります。 (一般的には、 アプリケーション論理からビューに対する強い依存関係を作ってしまうため、 絶対的に必要でない限り一般的にはこの JSF の機能は使用しないようにした方がよいでしょう。) postback リクエストで、 Seam 対話コンテキストが復元される前、 ビューの復元フェーズ中にコンポーネントのバインディングは更新されます。

これを回避するには、 イベントスコープコンポーネントを使ってコンポーネントバインディングを格納し、 それを必要とする対話スコープコンポーネントにインジェクトします。

@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;
    
    ...
}

第7章 ページフローとビジネスプロセス

JBoss jBPM は、Java SE や EE 環境のためのビジネスプロセス管理エンジンです。 jBPM は、ビジネスプロセスや、ユーザインタラクションを、 待ち状態、 デシジョン、タスク、WEBページなどを、 ノードの図式として表現を可能にします。 図式は、簡単でとても読みやすい jPDL と呼ばれる XML 表現を使用して定義されており、 Eclipse プラグインを利用して、編集、グラフィックによる視覚化が可能です。 jPDL は拡張可能な言語であり、 WEB アプリケーションのページフローを定義することから、典型的なワークフローの管理、SOA 環境におけるサービスのオーケストレーションまで適応します。

Seam アプリケーションは jBPM を2 つの異なる問題に使用します。

  • 複雑なユーザインタラクションを含むページフローを定義します。 jPDL プロセス定義は、対話のためのページフローを定義します。 Seamコンバセーションは、シングルユーザとの相対的に短期な対話のインタラクションであると考えられます。

  • ビジネスプロセスを包括的に定義します。 ビジネスプロセスは、複数ユーザの複数の対話の範囲を含むかもしれません。 その状態は jBPM データベースの中で永続的なので、長期的であると考えられます。 複数ユーザのアクティビティの調整は、 シングルユーザとのインタラクションについて動作を記述するよりずっと複雑な問題です。 そこで、jBPM は複数の並行な実行パスを扱うようなタスク管理のための洗練された機能を提供します。

これら 2 つのものを混乱しないでください。 それらはかなり違うレベルあるいは粒度で動作します。 ページフロー対話、 そして、タスク すべてはシングルユーザとの 1 つのインタラクションを参照します。 ビジネスプロセスはいくつものタスクをまたぎます。 さらに、jBPM の 2 つのアプリケーションは直交しています (互いに独立していること) 。 それらを一緒に使うことも、独立して使うことも、使わないこともできます。

Seam を使うために、jPDL を知る必要はありません。 JSF あるいは、Seam ナビゲーション規則を使って、ページフローを定義することに満足な場合、 あるいは、アプリケーションがプロセス駆動というよりデータ駆動の場合、 おそらくjBPM は不要でしょう。 しかし、明確な図式表現でユーザインタラクションを考えることが、 より堅牢なアプリケーションの構築に役立つことは理解できます。

7.1. Seam でのページフロー

Seam には、ページフローを定義する 2 つの方法があります。

  • JSFあるいはSeam ナビゲーション規則の利用 - ステートレスなナビゲーションモデル

  • jPDL の利用 - ステートフルなナビゲーションモデル

簡単なアプリケーションでは、ステートレスなナビゲーションモデルで十分です。 とても複雑なアプリケーションは、場所に応じて両方を使用します。 それぞれのモデルは、それぞれの強みも弱みもあります。

7.1.1. 2 つのナビゲーションモデル

ステートレスなモデルは、 一組の名前の付いた論理的なイベントの結果 (outcome) から 直接、結果として生じるビューのマッピングを定義します。 ナビゲーション規則は、どのページがイベントのソースであったかということ以外、 アプリケーションによって保持されたどのような状態も全く気にしません。 これは、アクションリスナメソッドがページフローを決めなければならないことがあることを意味しています。 なぜなら、それらだけがアプリケーションの現在の状態にアクセスできるからです。

これは JSF ナビゲーション規則を使用したページフローの例です。

<navigation-rule>
    <from-view-id>/numberGuess.jsp</from-view-id>
        
    <navigation-case>
        <from-outcome>guess</from-outcome>
        <to-view-id>/numberGuess.jsp</to-view-id>
        <redirect/>
    </navigation-case>

    <navigation-case>
        <from-outcome>win</from-outcome>
        <to-view-id>/win.jsp</to-view-id>
        <redirect/>
    </navigation-case>
        
    <navigation-case>
        <from-outcome>lose</from-outcome>
        <to-view-id>/lose.jsp</to-view-id>
        <redirect/>
    </navigation-case>

</navigation-rule>

これは Seam ナビゲーション規則を使用したページフローの例です。

<page view-id="/numberGuess.jsp">
        
    <navigation>
        <rule if-outcome="guess">
            <redirect view-id="/numberGuess.jsp"/>
        </rule>
        <rule if-outcome="win">
            <redirect view-id="/win.jsp"/>
        </rule>
        <rule if-outcome="lose">
            <redirect view-id="/lose.jsp"/>
        </rule>
    </navigation-case>

</navigation-rule>

ナビゲーション規則が冗長過ぎると考えるならば、 アクションリスナーメソッドから直接、ビューIDを返すことが可能です。

public String guess() {
    if (guess==randomNumber) return "/win.jsp";
    if (++guessCount==maxGuesses) return "/lose.jsp";
    return null;
}

これは、リダイレクトの結果であることに留意ください。 リダイレクト中に使用するパラメータを指定することも可能です。

public String search() {
    return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}

ステートフルなモデルは、 名前の付いた論理的なアプリケーションの状態間で起こる遷移の組み合わせを定義します。 このモデルでは、jPDL ページフロー定義中に、どのようなユーザインタラクションのフロー表現も可能であり、 インタラクションのフローを全く知らないアクションリスナーメソッドを書くことも可能です。

これは jPDL を使用したページフロー定義の例です。

<pageflow-definition name="numberGuess">
    
   <start-page name="displayGuess" view-id="/numberGuess.jsp">
      <redirect/>
      <transition name="guess" to="evaluateGuess">
      	<action expression="#{numberGuess.guess}" />
      </transition>
   </start-page>
   
   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
      <transition name="true" to="win"/>
      <transition name="false" to="evaluateRemainingGuesses"/>
   </decision>
   
   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
      <transition name="true" to="lose"/>
      <transition name="false" to="displayGuess"/>
   </decision>
   
   <page name="win" view-id="/win.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
   <page name="lose" view-id="/lose.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
</pageflow-definition>

ここで、すぐに気づく 2 つのことがあります。

  • JSF/Seam ナビゲーション規則は、より 簡単です。 (しかし、これは、根底となる Java コードがより複雑化であるという事実をあいまいにしています。)

  • jPDL は、JSP や Java コードを見る必要がなく、 即座に、ユーザインタラクションの理解ができます。

それに加えて、ステートフルモデルは、もっと 制約的 です。 各論理的な状態 (ページフローの各ステップ) に対して他の状態に遷移可能な制約された組み合わせがあります。 ステートレスモデルは、アドホックな モデルです。 それは、アプリケーションではなく、 比較的制約のない、ユーザが次に行きたいところを決めるフリーフォームナビゲーションに適しています。

ステートフル / ステートレスナビゲーションの判断は、 典型的なモーダル / モーダレスインタラクションの考え方ととてもよく似ています。 さて、アプリケーションをモーダルな振る舞いから回避することは、 対話を持つ 1 つの主な理由ですが、 Seam アプリケーションは、 通常、単純な意味でのモーダルではありません。 しかし、Seam アプリケーションは、 特定な対話レベルで、モーダル可能であり、しばしばそうです。 モーダルな振る舞いは、 可能な限り回避したものとして知られています。 ユーザがしたいことの順番を予測することは、とても困難です。 しかし、ステートフルモデルの存在意義があるのは疑う余地はありません。

2 つのモデルの最大の違いは、 戻るボタンの振る舞いです。

7.1.2. Seam と 戻るボタン

JSF あるいは Seam ナビゲーション規則が使用されている場合、 Seam は、ユーザに戻る、進む、更新ボタンの自由なナビゲーションを可能にします。 これが発生したとき、 内部的な対話状態の一貫性を保持することは、 アプリケーションの責任です。 Struts や WebWork のような対話モデルをサポートしない WEB アプリケーションフレームワーク、 そして、EJB ステートレスセッションBean や Spring framework のようなステートレスコンポーネントモデルの組み合わせの経験は、 多くの開発者にこれをすることは、ほとんど不可能であることを教えていました。 しかし、Seam のコンテキストでの経験から、 ステートフルセッション Bean に裏付けられた明確な対話モデルがあるところでは、 それは実際とても簡単です。 通常、それは、アクションリスナーメソッドの最初に、 no-conversation-view-id アノテーションと null チェックの使用を組合わせる程度に簡単です。 私たちは、フリーフォームナビゲーションのサポートは、 ほぼいつも要求されるものと考えています。

この場合、no-conversation-view-idの宣言は pages.xmlで行います。 対話中のレンダリングされたページからのリクエストの場合、 異なるページにリダイレクトして、その対話は存在していないことを Seamに伝えることになります。

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

一方、ステートフルモデルでは、 戻るボタンを押すことは、前の状態への未定義な遷移として中断されます。 なぜなら、ステートフルモデルは、 現在の状態からの遷移の組み合わせを強制します。 ステートフルモデルでは、戻るボタンは、デフォルトで無効です。 Seamは透過的に戻るボタンの使用を検知し、 前の "古い" ページからのアクションが実行されるのをブロックし、 そして、単純に、"現在の" ページをリダイレクトします。 (そして、faces メッセージを表示します。) これを特徴と考えるか、あるいは、ステートフルモデルの制約と考えるかは、 アプリケーション開発者としての視点次第です。 ユーザとしては、この特徴はイライラさせられるかもしれません。 特定のページからの back="enabled" 設定により、 戻るボタンナビゲーションを可能とすることもできます。

<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page>

これは、checkout 状態 から以前のどの状態 にでも戻るボタンでの遷移が可能です。

もちろん、ページフローのレンダリングされたページからのリクエストの場合も、 異なるページにリダイレクトして、そのページフローでの対話は存在していないことを 定義しなければなりません。この場合、no-conversation-view-id の宣言は、ページフロー定義で行います:

<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled" 
        no-conversation-view-id="/main.xhtml">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page>

実際、どちらのナビゲーションモデルも、それにふさわしい場所があります。 いつ、どちらのモデルがふさわしいかは、すぐに理解できるようになります。

7.2. jPDL ページフローの使用

7.2.1. ページフローの設定

Seam の jBPM 関連のコンポーネントをインストールし、 ページフロー定義の場所を指示する必要があります。 この components.xml に Seam 設定を指定することができます。

<core:jbpm>
    <core:pageflow-definitions>
        <value>pageflow.jpdl.xml</value>
    </core:pageflow-definitions>
</core:jbpm>

最初の行は jBPM を設定します、2 番目は jPDL ベースのページフロー定義を指定しています。

7.2.2. ページフローの開始

@Begin@BeginTask あるいは、 @StartTask アノテーションを使用して、 プロセス定義の名前を指定することによって、 jPDL ベースのページフローを開始します:

@Begin(pageflow="numberguess")
public void begin() { ... }

もしくは、pages.xmlを使用してページフローを開始できます。

<page>
        <begin-conversation pageflow="numberguess"/>
    </page>

RENDER_RESPONSE フェーズの間にページフローを開始する場合、 — 例えば @Factory または @Create メソッドの期間 — 私達は既にレンダリングされているページにいると考えます。 そして、 上記のサンプルのように、 ページフローの最初のノードとして <start-page> ノードを使用します。

しかし、ページフローがアクションリスナ呼び出しの結果として開始される場合、 アクションリスナの結果 (outcome) は、レンダリングされる最初のページを決定します。 この場合、ページフローの最初のノードとして <start-state> を使用し、 それぞれの可能な結果 (outcome) のために遷移を宣言します。

<pageflow-definition name="viewEditDocument">

    <start-state name="start">
        <transition name="documentFound" to="displayDocument"/>
        <transition name="documentNotFound" to="notFound"/>
    </start-state>
    
    <page name="displayDocument" view-id="/document.jsp">
        <transition name="edit" to="editDocument"/>
        <transition name="done" to="main"/>
    </page>
    
    ...
    
    <page name="notFound" view-id="/404.jsp">
        <end-conversation/>
    </page>
    
</pageflow-definition>

7.2.3. ページノードと遷移

<page> ノードは、システムがユーザ入力を待っている状態を表します。

<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition name="guess" to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page>

view-id は JSF ビューIDです。 <redirect/> 要素は、 JSF ナビゲーション規則の <redirect/> と同じ作用、 つまり、ブラウザの更新ボタンの問題を解決するために、 post-then-redirect を行います。 (Seam は、 ブラウザのリダイレクトを超えて対話コンテキストを伝播します。 従って、Seamでは、Ruby on Rails スタイルの "flash" の概念は不要です。)

遷移名は、numberGuess.jsp において、 ボタン あるいは、リンクをクリックすることによって起動された JSF 結果 (outcome) の名前です。

<h:commandButton type="submit" value="Guess" action="guess"/>

遷移が、このボタンをクリックすることによって起動されるときに、 numberGuess コンポーネントの guess () メソッドと呼び出すことによって、 jBPM は、遷移のアクションを起動します。 jPDL においてアクションを指定するために使わるシンタックスは、 JSF EL 式とよく似ていること、 そして、遷移のアクションハンドラは、 ちょうど現在の Seam コンテキストにおける Seam コンポーネントのメソッドであることに注意してください。 従って、JSF イベントのために既に持っているものと、ちょうど同じ jBPM イベントのモデルを持ちます。 (一貫した原則 (The One Kind of Stuff principle))

nullでのoutcome の場合 (例えば、action が定義されていないコマンドボタン)、 もし、名前のない遷移があるならば、Seam は遷移するためのシグナルを送ります。 あるいは、もし、すべての遷移が名前を持つならば、単純にページを再表示します。 従って、サンプルページフローを少し単純化でき、このボタンは

<h:commandButton type="submit" value="Guess"/>

以下の名前のない遷移でのアクションを実行します。

<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page>

ボタンにアクションメソッドを呼ばせることも可能です。 この場合、アクション結果 (outcome) が遷移を決定します。

<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
    <transition name="correctGuess" to="win"/>
    <transition name="incorrectGuess" to="evaluateGuess"/>
</page>

しかし、これは質の悪いスタイルだと考えます。 なぜなら、フロー制御の責任をページフロー定義の外側の他のコンポーネントに移動しているからです。 ページフローに関連することをそれ自身に集中することは、より良いことです。

7.2.4. フローの制御

通常、ページフローを定義するとき、jPDL より強力な機能はいりませんが、 <decision> ノードが必要です。

<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
    <transition name="true" to="win"/>
    <transition name="false" to="evaluateRemainingGuesses"/>
</decision>

デシジョンは Seam コンテキスト中では JSF EL 式によって評価されます。

7.2.5. フローの終了

<end-conversation>、または、@End を使用して対話を終了します。 (実際、可読性のために、両方 の使用を勧めます。)

<page name="win" view-id="/win.jsp">
    <redirect/>
    <end-conversation/>
</page>

オプションとして、transition 名を指定して、タスクを終了することができます。 この場合、Seam はビジネスプロセスにおいて現在のタスク終了の信号を送るでしょう。

<page name="win" view-id="/win.jsp">
    <redirect/>
    <end-task transition="success"/>
</page>

7.3. Seam でのビジネスプロセス管理

ビジネスプロセスは、だれ (who) がタスクを実行することができるか、 いつ (when) タスクを実行すべきかという明確なルールに従って、 ユーザ、あるいは、ソフトウェアのシステムによって実行されなければならない明確なタスクの集合です。 Seam jBPMインテクグレーションは、ユーザにタスクリストを表示し、それらのタスクを管理することを容易にします。 Seam はまた BUSINESS_PROCESS コンテキスト中のビジネスプロセスに関連する、 状態をアプリケーションに保管させ、 jBPM 変数経由でその状態を永続化させます。

<page> の代わりに、<task-node> ノードを持つ以外、 簡単なビジネスプロセス定義はページフロー定義とほぼ同じであるように見えます。 (一貫した原則 (The One Kind of Stuff principle)) 長期間のビジネスプロセスにおいて、 待ち状態は、システムが、ユーザがログインし、タスクを実行するのを待っているところです。

<process-definition name="todo">
   
   <start-state name="start">
      <transition to="todo"/>
   </start-state>
   
   <task-node name="todo">
      <task name="todo" description="#{todoList.description}">
         <assignment actor-id="#{actor.id}"/>
      </task>
      <transition to="done"/>
   </task-node>
   
   <end-state name="done"/>
   
</process-definition>

同じプロジェクトの中に、jPDL ビジネスプロセス定義と、 jPDL ページフロー定義を持つことは可能です。 そうであれば、2 つの関係は ビジネスプロセス中の <task>は ページフロー <process-definition>全体と一致します。

7.4. jPDL ビジネスプロセス定義の使用

7.4.1. プロセス定義の設定

jBPM を設定し、そのjBPMにビジネスプロセス定義の場所を指示する必要があります。

<core:jbpm>
    <core:process-definitions>
        <value>todo.jpdl.xml</value>
    </core:process-definitions>
</core:jbpm>

7.4.2. アクターIDの初期化

いつでも現在ログインしているユーザを知っている必要があります。 jBPM は、actor idgroup actor idによって、ユーザを識別します。 actor と呼ばれる組み込み Seam コンポーネントを使用することにより、 現在の actor id を指定します。

@In Actor actor;

public String login() {
    ...
    actor.setId( user.getUserName() );
    actor.getGroupActorIds().addAll( user.getGroupNames() );
    ...
}

7.4.3. ビジネスプロセスの初期化

ビジネスプロセスインスタンスを初期化するためには、 @CreateProcess アノテーションを使用します。

@CreateProcess(definition="todo")
public void createTodo() { ... }

また、 pages.xmlを使用してビジネスプロセスの初期化も行えます:

<page>
    <create-process definition="todo" />
</page>

7.4.4. タスク割り当て

プロセスが開始したときタスクインスタンスが生成されます。 これらにはユーザまたはユーザグループを割り当てなければなりません。 actor ids は、ハードコーディングすることも、Seam コンポーネントに委譲することもできます。

<task name="todo" description="#{todoList.description}">
    <assignment actor-id="#{actor.id}"/>
</task>

この場合、 単純に現在のユーザにタスクを割り当てます。 タスクをプールに割り当てることもできます。

<task name="todo" description="#{todoList.description}">
    <assignment pooled-actors="employees"/>
</task>

7.4.5. タスクリスト

いくつかの組み込み Seam コンポーネントによりタスクリストの表示が容易になっています。 pooledTaskInstanceList は、 ユーザが自分自身に割り当てることができるプールされたタスクのリストです。

<h:dataTable value="#{pooledTaskInstanceList}" var="task">
    <h:column>
        <f:facet name="header">Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
    </h:column>            	
</h:dataTable>

<s:link> の代わりに、 普通の JSF <h:commandLink> を使用することもできます。

<h:commandLink action="#{pooledTask.assignToCurrentActor}"> 
    <f:param name="taskId" value="#{task.id}"/>
</h:commandLink>

pooledTask コンポーネントは、 単純にタスクを現在のユーザに割り当てる組み込みコンポーネントです。

taskInstanceListForType コンポーネントは、 現在のユーザに割り当てられた特定タイプのタスクを含んでいます。

<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
    <h:column>
        <f:facet name="header">Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
    </h:column>            	
</h:dataTable>

7.4.6. タスクの実行

タスクの作業を開始させるために、リスナメソッドに、 @StartTask あるいは @BeginTaskを使用します。

@StartTask
public String start() { ... }

また、 タスクの実行を pages.xml を使用して始めることもできます:

<page>
    <start-task />
</page>

これらのアノテーションは、 ビジネスプロセス全体に関して意味を持つ、 特殊な種類の対話を開始します。 この対話による処理はビジネスプロセスコンテキストの中で保持する状態にアクセスできます。

@EndTask を使用して対話を終了する場合、 Seam はタスクの完了サインを送信します。

@EndTask(transition="completed")
public String completed() { ... }

また、 pages.xmlも使用できます:

<page>
    <end-task transition="completed" />
</page>

(もしくは、 上記のように、<end-conversation> も使用可能です。)

この時点で、 jBPM はビジネスプロセス定義を引継ぎ、実行を続行します。 (より複雑なプロセスにおいては、 プロセス実行が再開する前に完了する必要があるタスクがあるかもしれません。)

複雑なビジネスプロセスの管理を実現する各種の高度な機能の全体的な概要については jBPM ドキュメントを参照してください。

第8章 Seam とオブジェクト/リレーショナルマッピング

Seam は EJB 3.0 で導入される Java Persistence API および Hibernate3 の 2 つの最も一般的な Java 用永続アーキテクチャに対して広範なサポートを提供します。 Seam 固有の状態管理アーキテクチャにより、 いかなるウェブアプリケーションフレームワークからも高度な ORM 統合を実現します。

8.1. はじめに

Seam は、 旧世代の Java アプリケーションアーキテクチャの典型であるステートレス性に悩む Hibernate チームのフラストレーションから生まれました。 Seam の状態管理アーキテクチャは元々、 永続性に関する問題の解決を目的として設計されました — 特に楽観的なトランザクションの処理に関連する問題。 スケーラブルなオンラインアプリケーションは常に楽観的なトランザクションを使用します。 微小レベル (データベース/JTA) のトランザクションは、 アプリケーションが並列している極少数のクライアント群のみをサポートするよう設計されていない限り、 ユーザーのインテラクションをスパンしません。 しかし、 目的とするほぼすべての作業はまずユーザーに対するデータの表示に関連し、 次に少し遅れて同じデータの更新に関連してきます。 このため、 Hibernate は楽観的なトランザクションをスパンした永続コンテキストという目的に対応するよう設計されました。

残念ながら、 Seam や EJB 3.0 より以前の「ステートレス」と呼ばれるアーキテクチャには楽観的なトランザクションを表示するための構成概念がありませんでした。 このため、 代わりに微小なトランザクションに対してスコープされる永続コンテキストを提供していました。 当然、 ユーザーにとっては問題が多くなり、 恐怖の LazyInitializationException として Hibernate に関するユーザーからの苦情は最多となりました。 ここで必要なのはアプリケーション層で楽観的トランザクションを表示する構成概念なのです。

EJB 3.0 はこの問題を認識し、 コンポーネントの寿命に対してスコープされる拡張永続コンテキストでステートフルなコンポーネント (ステートフルセッション bean) という目的を導入します。 これは問題に関して完全なソリューションではありませんが (それ自体は便利な構成となる) 、 2 つの問題があります。

  • ステートフルセッション bean の寿命はウェブ層でコード経由により手作業で管理されなければなりません (これは微妙な問題であり実際にはかなり困難であることがわかります)。

  • 同じ楽観的トランザクション内のステートフルコンポーネント間での永続コンテキストの伝播は可能ですが簡単ではありません。

Seam は、 対話及び対話に対してスコープされるステートフルセッション bean コンポーネントを提供することにより 1 番目の問題を解決します。 (ほとんどの対話は実際にはデータ層で楽観的トランザクションを表示します。) 永続コンテキストの伝播を必要としないような多くのシンプルなアプリケーション (Seam ブッキングデモなど) にはこれで十分です。 各対話内で軽く作用しあっているコンポーネントを多く持っているようなもう少し複雑なアプリケーションの場合、 コンポーネント群全体への永続コンテキストの伝播は重要な問題となります。 このため、 Seam は EJB 3.0 の永続コンテキスト管理モデルを拡張して対話スコープの拡張永続コンテキストを提供しています。

8.2. Seam 管理トランザクション

EJB セッション bean は宣言型トランザクション管理を特長としています。 EJB コンテナは bean が呼び出されると透過的にトランザクションを起動し、 呼出しが終了するとトランザクションも終了させることが可能です。 JSF アクションリスナーとして動作するセッション bean メソッドを記述する場合、 そのアクションに関連するすべての作業を 1 つのトランザクションで行うことができ、 アクションの処理が完了したら必ずコミットまたはロールバックされるようにすることができます。 これは素晴らしい機能であり、 いくつかの Seam アプリケーションに必要とされるものはこれだけです。

ただし、 この方法には問題が 1 つあります。 Seam アプリケーションは単一のメソッドコールからセッション bean へのリクエストに対して全データアクセスを行わない可能性があります。

  • このリクエストにはいくつかの疎結合コンポーネントによる処理を必要とする場合があります。 それぞれのコンポーネントが web 層から個別に呼び出されます。 Seam ではリクエストごと web 層から EJB コンポーネントへのコールが複数あるのはよく見られることです。

  • ビューのレンダリングには関連の遅延フェッチが必要な場合があります。

1 リクエストごとのトランザクション数が多くなると、 使用しているアプリケーションが多くの並列リクエストを処理している際にそれだけ多くの微小で個別の問題に遭遇する可能性が高くなります。 書き込み動作はすべて、 必ず、 同じトランザクション内で起こらなければならないからです。

Hibernate ユーザーはこの問題を回避するため

このパターンは通常、 リクエスト全体にスパンする単一トランザクションとして実装されます。 この実装ではいくつかの問題があります。 もっとも深刻となる問題は、 トランザクションをコミットするまでそれが成功なのかどうか全く確認できないことです — ただし、 「ビュー内のオープンセッション」トランザクションがコミットされるようになると、 ビューは完全にレンダリングされるので、 レンダリングされるレスポンスはすでにクライアントにフラッシュされている場合があります。 ユーザーのトランザクションが成功しなかったことをユーザーに知らせるにはどうしたらよいでしょうか。

Seam はトランザクション個別の問題と関連フェッチの問題の両方を解決しながら、 「ビュー内のオープンセッション」で問題を回避します。 解決法は 2 つに分けられます。

  • トランザクションに対してスコープされるのではなく、 対話に対してスコープされる拡張永続コンテキストを使用する

  • 1 リクエストに対して 2 つのトランザクションを使用する、 1 番目はモデル値の更新フェーズの開始からアプリケーション呼び出しフェーズの終わりまでスパンし、 2 番目はレスポンスのレンダリングフェーズをスパンする

次のセクションでは、 対話スコープの永続コンテキストの設定方法について説明していきますが、 まず最初に Seam トランザクション管理を有効にする方法を説明しておく必要があります。 Seam トランザクション管理なしで対話スコープの永続コンテキストを使用することができ、 また Seam 管理永続コンテキストを使用していない場合でも Seam トランザクション管理を利用すると便利なことがあるので留意しておいてください。 ただし、 この 2 つの機能は連携して動作するよう設計されているため、 併用する方が最適です。

8.2.1. Seam 管理トランザクションを有効にする

Seam managed transactions を利用するには、 SeamPhaseListener の代わりに TransactionalSeamPhaseListener を使用する必要があります。

<lifecycle>
     <phase-listener>
        org.jboss.seam.jsf.TransactionalSeamPhaseListener
    </phase-listener>
</lifecycle>

Seam トランザクション管理は、 EJB 3.0 コンテナ管理の永続コンテキストを使用している場合にも便利です。 しかし、 Java EE 5 環境の外で Seam を使用している場合、 その他 Seam 管理の永続コンテキストを使用するような場合に特に役立ちます。

8.3. Seam 管理の永続コンテキスト

Seam を Java EE 5 環境の外で使用している場合、 コンテナによる永続コンテキストのライフサイクルの管理は期待できません。 EE 5 環境であっても、 単一の対話の範囲内で連携する多くの疎結合コンポーネントを持つ複雑なアプリケーションがあるかもしれず、 この場合にはコンポーネント間での永続コンテキストの伝播が簡単ではなくエラーが発生しやすい場合があります。

いずれの場合では、 コンポーネントで managed persistence context (JPA 用) または managed session (Hibernate 用) のいずれかを使用する必要があります。 Seam 管理永続コンテキストは単純にビルトインの Seam コンポーネントで対話コンテキストで EntityManager または Session のインスタンスを管理します。 @In でインジェクトすることができます。

Seam 管理の永続コンテキストはクラスタ化された環境で非常に効率的です。 EJB 3.0 の仕様はコンテナによるコンテナ管理拡張永続コンテキストの使用を許可しない最適化を Seam は実行することができます。 ノード間の永続コンテキストの状態を複製することなく拡張永続コンテキストの透過的なフェールオーバーをサポートします。 (この見過ごされてしまった点については、 次回の EJB 仕様のリビジョンで修正したいと考えています。)

8.3.1. JPA で Seam 管理の永続コンテキストを使用する

管理永続コンテキストの設定は簡単です。 components.xml 内に次のように記述します。

<core:managed-persistence-context name="bookingDatabase" 
                           auto-create="true"
            persistence-unit-jndi-name="java:/EntityManagerFactories/bookingData"/>

この設定により対話スコープの bookingDatabase という名前の Seam コンポーネントが作成され、 JNDI 名 java:/EntityManagerFactories/bookingData を持つ永続ユニット (EntityManagerFactory インスタンス) の EntityManager インスタンスの寿命を管理します。

当然、 EntityManagerFactory が JNDI にバウンドされたことを確認する必要があります。 JBoss では、 次のプロパティ設定を persistence.xml に追加すると確認を行うことができます。

<property name="jboss.entity.manager.factory.jndi.name" 
          value="java:/EntityManagerFactories/bookingData"/>

これで次を使用してインジェクトされる EntityManager ができます。

@In EntityManager bookingDatabase;

8.3.2. Seam 管理の Hibernate セッションを使用する

Seam 管理 Hibernate セッションも同様にcomponents.xml で次のように記述することができます。

<core:hibernate-session-factory name="hibernateSessionFactory"/>

<core:managed-hibernate-session name="bookingDatabase" 
                         auto-create="true"
           session-factory-jndi-name="java:/bookingSessionFactory"/>

java:/bookingSessionFactoryhibernate.cfg.xml で指定されるセッションファクトリ名にします。

<session-factory name="java:/bookingSessionFactory">
    <property name="transaction.flush_before_completion">true</property>
    <property name="connection.release_mode">after_statement</property>
    <property name="transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property>
    <property name="transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
    <property name="connection.datasource">java:/bookingDatasource</property>
    ...
</session-factory>

Seam はセッションをフラッシュしないので、 hibernate.transaction.flush_before_completion を常に有効にしてセッションが JTA トランザクションのコミットより先に自動的にフラッシュされるようにしなければならないので注意してください。

これで、 次のコードを使って JavaBean コンポーネントにインジェクトされる管理 Hibernate Session ができます。

@In Session bookingDatabase;

8.3.3. Seam 管理の永続コンテキストと微小な対話

merge() 演算を使用したり、 各リクエストの冒頭でデータを再ロードしたり、 LazyInitializationExceptionNonUniqueObjectException と格闘しなくとも、 対話にスコープされる永続コンテキストによりサーバーに対して複数のリクエストをスパンする楽観的なトランザクションをプログラムすることができるようになります。

楽観的トランザクション管理では楽観的ロックでトランザクションの隔離と一貫性を実現できるので、 Hibernate と EJB 3.0 いずれも @Version アノテーションを提供することで楽観的ロックの使用を容易にしています。

デフォルトでは、 永続コンテキストは各トランザクションの終わりでフラッシュされます (データベースと同期される)。 これが目的の動作である場合もありますが、 すべての変更はメモリに保持され対話が正常に終了したときにのみデータベースに書き込まれる動作を期待することの方が多いでしょう。 これにより真に微小な対話を可能にします。 EJB 3.0 エキスパートグループの中の JBoss、 Sun、 Sybase 以外の特定のメンバーによって長期的な見通しを考慮に入れず短絡的な決定がなされてしまったため、 EJB 3.0 永続を使用した微小な対話の実装を行うシンプルで使用に適したポータブルな方法が現在ありません。 ただし、 Hibernate では仕様により定義される FlushModeType に対するベンダー拡張としてこの機能を提供しています。 また、 他のベンダーもじきに同様の拡張を提供するだろうことを期待しています。

Seam では対話の開始時に FlushModeType.MANUAL を指定することができます。 現在は Hibernate が永続を実現する構成要素である場合にのみ機能しますが、 他の同等ベンダーによる拡張もサポートする予定です。

@In EntityManager em; //a Seam-managed persistence context

@Begin(flushMode=MANUAL)
public void beginClaimWizard() {
    claim = em.find(Claim.class, claimId);
}

これで claim オブジェクトは残りの対話の永続コンテキストによる管理を維持します。 このクレームに変更を加えることができます。

public void addPartyToClaim() {
    Party party = ....;
    claim.addParty(party);
}

ただし、 これらの変更は明示的にフラッシュが発生するよう強制するまではデータベースに対してフラッシュされません。

@End
public void commitClaim() {
    em.flush();
}

8.4. JPA 「デリゲート」を使用する

EntityManager インターフェースにより getDelegate() メソッドを介してベンダー固有の API にアクセスすることができます。 必然的に、 Hibernate が最も関心の高いベンダーとなり、 org.hibernate.Session が最も強力となるデリゲートインターフェースになります。 これ以外を使用するのがばかばかしくなるほどです。 偏見無しによいので試してみてください。

ただし、 Hibernate またはそれ以外のものいずれを使用するかに限らず、 いずれは Seam コンポーネントでデリゲートを使用したくなる場合がくるでしょう。 以下にその一例を示します。

@In EntityManager entityManager;

@Create
public void init() {
    ( (Session) entityManager.getDelegate() ).enableFilter("currentVersions");
}

typecast は Java 言語の中でも間違いなく繁雑な構文になるため、 できる限り避けるのが一般的です。 デリゲートで取得する別の方法を次に示します。 まず、 以下の行を components.xml に追加します。

<factory name="session" 
         scope="STATELESS" 
         auto-create="true" 
         value="#{entityManager.delegate}"/>

これでセッションを直接インジェクトできるようになります。

@In Session session;

@Create
public void init() {
    session.enableFilter("currentVersions");
}

8.5. EJB-QL/HQL で EL を使用する方法

Seam 管理の永続コンテキストを使用する場合や @PersistenceContext を使ってコンテナ管理の永続コンテキストをインジェクトする場合、 Seam は EntityManager または Session オブジェクトをプロキシします。 これにより、 EL 式をクエリ文字列内で安全且つ効果的に使用することができるようになります。 たとえば、 次を見てください。

User user = em.createQuery("from User where username=#{user.username}")
         .getSingleResult();

上記の例は、 以下の例と同等になります。

User user = em.createQuery("from User where username=:username")
         .setParameter("username", user.getUsername())
         .getSingleResult();

当然、 次のようには絶対に記述しないでください。

User user = em.createQuery("from User where username=" + user.getUsername()) //BAD!
         .getSingleResult();

(効率が悪く、 SQL インジェクション攻撃に対して脆弱となります。)

8.6. Hibernate フィルタを使用する

Hibernate 固有の斬新な機能が filters になります。 フィルタによりデータベース内のデータ表示に制限を与えることができるようになります。 フィルタについては Hibernate のドキュメントで詳細に説明されています。 ここでは、 フィルタを Seam アプリケーションに統合する簡単な方法を記載しておくのがよいだろうと思います。 特に Seam Application Framework でうまく動作する方法を説明します。

Seam 管理の永続コンテキストには、 EntityManager や Hibernate Session がはじめて作成されたときに有効にされるよう定義されたフィルタの一覧がある場合があります。 (当然、 Hibernate が永続を実現する構成要素である場合にのみ使用できます。)

<core:filter name="regionFilter">
    <core:name>region</core:name>
    <core:parameters>
        <key>regionCode</key>
        <value>#{region.code}</value>
    </core:parameters>
</core:filter>

<core:filter name="currentFilter">
    <core:name>current</core:name>
    <core:parameters>
        <key>date</key>
        <value>#{currentDate}</value>
    </core:parameters>
</core:filter>

<core:managed-persistence-context name="personDatabase"
    persistence-unit-jndi-name="java:/EntityManagerFactories/personDatabase">
    <core:filters>
        <value>#{regionFilter}</value>
        <value>#{currentFilter}</value>
    </core:filters>
</core:managed-persistence-context>

第9章 Seam での JSF 形式検証

プレーンの JSF では、 検証はビューで定義されます。

<h:form>
    <h:messages/>

    <div>
        Country:
        <h:inputText value="#{location.country}" required="true">
            <my:validateCountry/>
        </h:inputText>
    </div>
    
    <div>
        Zip code:
        <h:inputText value="#{location.zip}" required="true">
            <my:validateZip/>
        </h:inputText>
    </div>

    <h:commandButton/>
</h:form>

実際には、 データモデルの一部であり、 またデータベーススキーマの定義全体にわたって存在する制約をほとんどの「検証」が強制実行するため、 この方法は通常、 DRY に違反してしまいます。 Seam は Hibernate Validator を使って定義されるモデルベースの制約に対するサポートを提供しています。

Location クラスで制約を定義するところから始めてみます。

public class Location {
    private String country;
    private String zip;
    
    @NotNull
    @Length(max=30)
    public String getCountry() { return country; }
    public void setCountry(String c) { country = c; }

    @NotNull
    @Length(max=6)
    @Pattern("^\d*$")
    public String getZip() { return zip; }
    public void setZip(String z) { zip = z; }
}

たしかに上記が正当ですが、 実際には Hibernate Validator にビルトインのものを使わずにカスタムな制約を使う方がスマートかもしれません。

public class Location {
    private String country;
    private String zip;
    
    @NotNull
    @Country
    public String getCountry() { return country; }
    public void setCountry(String c) { country = c; }

    @NotNull
    @ZipCode
    public String getZip() { return zip; }
    public void setZip(String z) { zip = z; }
}

いずれの方法をとるにしても、 JSF ページ内で使用される検証のタイプを指定する必要がなくなります。 かわりに、 <s:validate> を使ってモデルオブジェクトで定義される制約に対して検証を行うことができます。

<h:form>
    <h:messages/>

    <div>
        Country:
        <h:inputText value="#{location.country}" required="true">
            <s:validate/>
        </h:inputText>
    </div>
    
    <div>
        Zip code:
        <h:inputText value="#{location.zip}" required="true">
            <s:validate/>
        </h:inputText>
    </div>
    
    <h:commandButton/>

</h:form>

注記: このモデルで @NotNull を指定してもコントロールに出現させるのに required="true" が必要なくなるというわけではありません。これは JSF 検証アーキテクチャの限界によるものです。

この方法はモデル上の制約を定義し、 ビューで制約違反を 表示 します — デザイン性に優れている。。

しかし、 最初の例と比べてそれほど冗長性が軽減されているわけではないので、 <s:validateAll> を使ってみます。

<h:form>
    
    <h:messages/>

    <s:validateAll>

        <div>
            Country:
            <h:inputText value="#{location.country}" required="true"/>
        </div>

        <div>
            Zip code:
            <h:inputText value="#{location.zip}" required="true"/>
        </div>

        <h:commandButton/>

    </s:validateAll>

</h:form>

このタグは単純に <s:validate> をフォーム内での各入力に追加します。 フォームが大きくなる場合は、 入力の手間をかなり省くことができることになります。

ここで、 検証が失敗した場合にユーザーに対してフィードバックを表示させるために何らか手を打たなければなりません。 現在、 すべてのメッセージはフォームの冒頭で表示しています。 実際に行いたいのは、 エラーを付けてフィールドのとなりにメッセージを表示 (プレーン JSF で可能)、 フィールドとラベルをハイライトさせて (これは不可能)、 ついでにフィールドのとなりに何かイメージを表示させる (これも不可能) ことです。 また、 必須事項の各フィールドにはラベルのとなりに色の付いたアスタリスクを表示させたいとします。

フォームの各フィールドに対してかなり多くの機能を必要としています。 フォームにあるすべてのフィールドそれぞれに対してイメージ、メッセージ、入力フィールドのレイアウトやハイライトを指定したいとは思わないでしょうから、 代わりに facelets テンプレートで一般的なレイアウトを指定します。

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:s="http://jboss.com/products/seam/taglib">
                 
    <div>
    
        <s:label styleClass="#{invalid?'error':''}">
            <ui:insert name="label"/>
            <s:span styleClass="required" rendered="#{required}">*</s:span>
        </s:label>
        
        <span class="#{invalid?'error':''}">
            <h:graphicImage src="img/error.gif" rendered="#{invalid}"/>
            <s:validateAll>
                <ui:insert/>
            </s:validateAll>
        </span>
        
        <s:message styleClass="error"/>
        
    </div>
    
</ui:composition>

<s:decorate> を使って各フォームフィールドにこのテンプレートを含ませることができます。

<h:form>

    <h:messages globalOnly="true"/>

    <s:decorate template="edit.xhtml">
        <ui:define name="label">Country:</ui:define>
        <h:inputText value="#{location.country}" required="true"/>
    </s:decorate>
    
    <s:decorate template="edit.xhtml">
        <ui:define name="label">Zip code:</ui:define>
        <h:inputText value="#{location.zip}" required="true"/>
    </s:decorate>

    <h:commandButton/>

</h:form>

最後に、 ユーザーがフォーム内を行ったり来たりするのに応じて Ajax4JSF を使って検証メッセージを表示させることができます。

<h:form>

    <h:messages globalOnly="true"/>

    <s:decorate id="countryDecoration" template="edit.xhtml">
        <ui:define name="label">Country:</ui:define>
        <h:inputText value="#{location.country}" required="true">
            <a:support event="onblur" reRender="countryDecoration"/>
        </h:inputText>
    </s:decorate>
    
    <s:decorate id="zipDecoration" template="edit.xhtml">
        <ui:define name="label">Zip code:</ui:define>
        <h:inputText value="#{location.zip}" required="true">
            <a:support event="onblur" reRender="zipDecoration"/>
        </h:inputText>
    </s:decorate>

    <h:commandButton/>

</h:form>

ページ上の重要なコントロールに対して、 特に Selenium のようなツールキットを使って UI の自動化テストを行いたい場合などは、 明示的な ID を定義するスタイルの方がよいでしょう。 明示的な ID を与えないと JSF によって ID が生成されますが、 ページに何らかの変更を加えるとこの生成された値も変更されます。

<h:form id="form">

    <h:messages globalOnly="true"/>

    <s:decorate id="countryDecoration" template="edit.xhtml">
        <ui:define name="label">Country:</ui:define>
        <h:inputText id="country" value="#{location.country}" required="true">
            <a:support event="onblur" reRender="countryDecoration"/>
        </h:inputText>
    </s:decorate>
    
    <s:decorate id="zipDecoration" template="edit.xhtml">
        <ui:define name="label">Zip code:</ui:define>
        <h:inputText id="zip" value="#{location.zip}" required="true">
            <a:support event="onblur" reRender="zipDecoration"/>
        </h:inputText>
    </s:decorate>

    <h:commandButton/>

</h:form>

第10章 Seamアプリケーションフレームワーク

Seamは特別のインターフェースやスーパークラスを拡張することなく、純粋なJavaクラスにアノテーションを付記することにより簡単にアプリケーションを作成することが出来ます。 しかし、components.xmlの設定 (簡単な場合には) や機能の拡張により再利用する事が出来る既成のコンポーネントを提供することにより、お決まりのプログラムについて更に簡単に作成できるようにすることが出来ます。

SeamアプリケーションフレームワークはJPAやHibernateを使ったデータベースへのアクセスに関わる基本的なプログラムのコード量を削減することが出来ます。

このフレームワークは理解も容易で、拡張し易く簡単ないくつかのクラスから構成されています。 コード量を減らしたり、コードの再利用を可能にする「マジック」はSeamそのものにあります — このフレームワークを使わなくても Seam アプリケーションを作成するときには同様のマジックを使用しているのです。

10.1. イントロダクション

このフレームワークの提供するコンポーネントは、二つの使い方のいずれかで利用することが出来ます。 一つは、他のSeamの組み込みコンポーネントで行っているように、components.xmlでインスタンスを設定して使用する方法です。 下のcomponents.xmlの設定 (部分) ではContactエンティティに対する基本的なCRUD操作をインストールしています。

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       entity-manager="#{personDatabase}">
    <framework:id>#{param.personId}</framework:id>
</framework:entity-home>

もし、これが「XMLプログラミング」に偏重しているように思えて、好みに合わなければ、代りに機能を拡張して行うことも出来ます。

@Stateful
@Name("personHome")
public class PersonHome extends EntityHome<Person> implements LocalPersonHome {
    @RequestParameter String personId;
    @In EntityManager personDatabase;
    
    public Object getId() { return personId; }
    public EntityManager getEntityManager() { return personDatabase; }
    
}

第二の方法 (機能の拡張を使う) は大きなメリットとして、簡単に拡張したり、内蔵された機能をオーバーライドすることが出来ます。 (このフレームワークの提供するクラスは、拡張や、カスタム化に対応できるよう、注意深く作成されています。)

また、第2のメリットとして、クラスをEJBのステートフルセッションビーンとすることが出来ます。 (必ずEJBにする必要はなく、好みで、プレーンなJavaビーンとすることも出来ます。)

現時点で、Seamアプリケーションフレームワークは、CRUD 用にEntityHomeHibernateEntityHome、それにQueryの為のEntityQueryHibernateEntityQueryの4つのコンポーネントを提供しています。

HomeとQueryはセッション、イベント、それに対話スコープで機能するように作成されています。 どのスコープを使用するかは、アプリケーションのステートモデルに依存します。

SeamアプリケーションフレームワークはSeamが管理している永続性コンテキストでのみ動作します。 デフォルトで、entityManagerという名前の永続性コンテキストを探します。

10.2. Homeオブジェクト

Homeオブジェクトは、特定のエンティティクラスに対する永続性操作を提供します。Personクラスについて考えてみましょう。

@Entity
public class Person {
    @Id private Long id;
    private String firstName;
    private String lastName;
    private Country nationality;
    
    //getters and setters...
}

コンフィギュレーションで、下のようにpersonHomeコンポーネントを定義することが出来ます。

<framework:entity-home name="personHome" entity-class="eg.Person" />

また、機能を拡張して下のように、同様のことが出来ます。

@Name("personHome")
public class PersonHome extends EntityHome<Person> {}

Homeオブジェクトはpersist()remove()update() それに getInstance() の 4 つの操作を提供します。

HomeはJSFページから、下のように直接利用することが出来ます。

<h1>Create Person</h1>
<h:form>
    <div>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
    <div>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form>

通常、Personpersonで参照出来た方が便利ですので、components.xmlに下のように一行加えて、そのようにしましょう。

<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" />

(コンフィギュレーションを使用している場合、) PersonHome@Factory を追加します。

@Name("personHome")
public class PersonHome extends EntityHome<Person> {
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
}

(機能を拡張している場合) これで、下のように JSF ページの記述が簡単になります。

<h1>Create Person</h1>
<h:form>
    <div>First name: <h:inputText value="#{person.firstName}"/></div>
    <div>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}"/>
    </div>
</h:form>

これで、Personの新しいエントリーを作成することが出来るようになります。 はい、これで全てです。 次に、表示、更新、それに削除機能を既存のデータ−ベースの Personエントリー操作に追加するためには、PersonHomeに対象のエントリーを特定する識別子を伝える必要があります。 下のように、ページパラメータを使って、これを行います。

<pages>
    <page view-id="/editPerson.jsp">
        <param name="personId" value="#{personHome.id}"/>
    </page>
</pages>

これで、JSFページにこれらの機能を追加することが出来ます。

<h1>
    <h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
    <h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
</h1>
<h:form>
    <div>First name: <h:inputText value="#{person.firstName}"/></div>
    <div>Last name: <h:inputText value="#{person.lastName}"/></div>
    <div>
        <h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
        <h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
        <h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
    </div>
</h:form>

リクエストパラメータ無しでページにリンクした場合、「Person作成」としてページが表示され、personId をリクエストパラメータとして渡した場合には、「Person編集」としてページが表示されます。

Personをその国籍を初期化して作成しなければならない場合を考えてみましょう。これも簡単に出来ます。 コンフィギュレーションを使う場合は;

<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome" 
                       entity-class="eg.Person" 
                       new-instance="#{newPerson}"/>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality">#{country}</property>
</component>

また、機能を拡張して行う場合は下の様になります。

@Name("personHome")
public class PersonHome extends EntityHome<Person> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
}

勿論、Countryオブジェクトは、例えばCountryHomeというHomeオブジェクトの管理下のオブジェクトとすることも出来ます。

アソシエーションの管理など、 より洗練された操作を実現するのも PersonHome にメソッドを追加するだけで出来るようになります。

@Name("personHome")
public class PersonHome extends EntityHome<Person> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    public void migrate()
    {
        getInstance().setCountry(country);
        update();
    }
    
}

Homeオブジェクトは操作が成功したときに自動的にフェースメッセージを表示します。 これを、カスタマイズするには、下のようにコンフィギュレーションを設定します。

<factory name="person" 
         value="#{personHome.instance}"/>

<framework:entity-home name="personHome"
                       entity-class="eg.Person"
                       new-instance="#{newPerson}">
    <framework:created-message>New person #{person.firstName} #{person.lastName} created</framework:created-message>
    <framework:deleted-message>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>
    <framework:updated-message>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>
</framework:entity-home>

<component name="newPerson" 
           class="eg.Person">
    <property name="nationality">#{country}</property>
</component>

あるいは、機能を拡張して下のようにすることも出来ます。

@Name("personHome")
public class PersonHome extends EntityHome<Person> {
    
    @In Country country;
    
    @Factory("person")
    public Person initPerson() { return getInstance(); }
    
    protected Person createInstance() {
        return new Person(country);
    }
    
    protected String getCreatedMessage() { return "New person #{person.firstName} #{person.lastName} created"; }
    protected String getUpdatedMessage() { return "Person #{person.firstName} #{person.lastName} updated"; }
    protected String getDeletedMessage() { return "Person #{person.firstName} #{person.lastName} deleted"; }
    
}

しかし、メッセージ定義における最良の方法は (デフォルトで messages という名前の) Seam に対して既知のリソースバンドルに定義することでしょう。

Person_created=New person #{person.firstName} #{person.lastName} created
Person_deleted=Person #{person.firstName} #{person.lastName} deleted
Person_updated=Person #{person.firstName} #{person.lastName} updated

この方法を使えば、国際化に対応することが出来ますし、コードやコンフィギュレーションファイルとプレゼンテーション層とを切り離すことが出来ます。

最後のステップは<s:validateAll><s:decorate>を使って、ページにバリデーション機能を追加することですが、これは皆さんへの宿題としておきましょう。

10.3. Queryオブジェクト

データベース中のPersonの全てのインスタンスのリストが必要な場合、Queryオブジェクトを使って、下のようにすることが出来ます。

<framework:entity-query name="people" 
                        ejbql="select p from Person p"/>

また、これ (この結果) をJSFページから使うことが出来ます。

<h1>List of people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable>

量の多いページを処理する為にページングも必要でしょう。

<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20"/>

表示するページを決める為にページパラメータを使います。

<pages>
    <page view-id="/searchPerson.jsp">
        <param name="firstResult" value="#{people.firstResult}"/>
    </page>
</pages>

ページングを管理するJSFのコードは若干繁雑ですが、許容範囲内です。

<h1>Search for people</h1>
<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
    <f:param name="firstResult" value="0"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
    <f:param name="firstResult" value="#{people.previousFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
    <f:param name="firstResult" value="#{people.nextFirstResult}"/>
</s:link>

<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
    <f:param name="firstResult" value="#{people.lastFirstResult}"/>
</s:link>

実用的な検索スクリーンでは、絞りこんだ検索結果を得るために、多くの検索のクライテリアをユーザに入力してもらう必要があります。 この重要なユースケースをサポートする為に、Queryオブジェクトはオプションとして制約を設定することが出来ます。

<component name="examplePerson" class="Person"/>
        
<framework:entity-query name="people" 
                        ejbql="select p from Person p" 
                        order="lastName" 
                        max-results="20">
    <framework:restrictions>
        <value>lower(firstName) like lower( #{examplePerson.firstName} + '%' )</value>
        <value>lower(lastName) like lower( #{examplePerson.lastName} + '%' )</value>
    </framework:restrictions>
</framework:entity-query>

上記の例ではexampleオブジェクトの使用について留意してください。

<h1>Search for people</h1>
<h:form>
    <div>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
    <div>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
    <div><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>

<h:dataTable value="#{people.resultList}" var="person">
    <h:column>
        <s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
            <f:param name="personId" value="#{person.id}"/>
        </s:link>
    </h:column>
</h:dataTable>

ここの例ではコンフィギュレーションによる再利用を示していますが、Queryオブジェクトの再利用は機能を拡張して行う事も同様に可能です。

10.4. Controllerオブジェクト

Seamアプリケーションフレームワークのオプショナルなクラスとして、Controllerと、そのサブクラスとして、 EntityControllerHibernateEntityControllerBusinessProcessControllerがあります。 これらのクラスは、一般に利用されるコンポーネントのメソッドに対する、少し便利なアクセス方法を提供しています。 これらは、新しいユーザがSeamに組み込まれた豊富な機能を探検するための出発点を提供し、また若干のコード量の削減に貢献します。

例として、SeamのRegistrationの例のRegisterActionをSeamアプリケーションフレームワークで書き直すと以下のようになります。

@Stateless
@Name("register")
public class RegisterAction extends EntityController implements Register
{

   @In private User user;
   
   public String register()
   {
      List existing = createQuery("select u.username from User u where u.username=:username")
         .setParameter("username", user.getUsername())
         .getResultList();
      
      if ( existing.size()==0 )
      {
         persist(user);
         info("Registered new user #{user.username}");
         return "/registered.jspx";
      }
      else
      {
         addFacesMessage("User #{user.username} already exists");
         return null;
      }
   }

}

ご覧のように、びっくりするような改善にはなりません、、、。

第11章 Seam と JBoss Rules

Seam では、Seam コンポーネントあるいは、 jBPM プロセス定義から JBoss ルール (Drools) を容易に呼び出せます。

11.1. ルールの初期化

最初のステップは、Seam コンテキスト変数で org.drools.RuleBase インスタンスを有効化することです。 多くのルール駆動型アプリケーションでは、 ルールは、動的にデプロイ可能である必要があります。 従って、ルールのデプロイと Seam での有効化を可能とするソリューションを実装する必要があるでしょう。 (将来版の Drools は、この問題を解決するルールサーバを提供するでしょう。) テスト目的で、Seam はクラスパスから静的なルール一式をコンパイルする、組み込みコンポーネントを提供しています。 このコンポーネントは、components.xml によりインストール可能です。

<drools:rule-base name="policyPricingRules">
    <drools:rule-files>
        <value>policyPricingRules</value>
    </drools:rule-files>
</drools:rule-base>

このコンポーネントは、.drl ファイル一式からルールをコンパイルし、 Seam アプリケーションコンテキスト中に org.drools.RuleBase をキャッシュします。 複数のルールベースをルール駆動型アプリケーションにインストールする必要もあり得ることに留意してください。

Drools DSLを利用するのであれば、DSL定義を指定しなければなりません:

<drools:rule-base name="policyPricingRules" dsl-file="policyPricing.dsl">
    <drools:rule-files>
        <value>policyPricingRules</value>
    </drools:rule-files>
</drools:rule-base>

次に、各対話に対して org.drools.WorkingMemory インスタンスを有効化する必要があります。 (各 WorkingMemory は、現在の対話に関連する fact を蓄積します。)

<drools:managed-working-memory name="policyPricingWorkingMemory" auto-create="true" rule-base="#{policyPricingRules}"/>

policyPricingWorkingMemory に、 ruleBase 設定プロパティにより、 ルールベースへの参照を与えていることに留意してください。

11.2. Seam コンポーネントからのルールの使用

WorkingMemory を、 任意の Seam コンポーネントにインジェクトし、 fact をアサートし、そしてルールを実行することができます。

@In WorkingMemory policyPricingWorkingMemory;

@In Policy policy;
@In Customer customer;

public void pricePolicy() throws FactException
{
    policyPricingWorkingMemory.assertObject(policy);
    policyPricingWorkingMemory.assertObject(customer);
    policyPricingWorkingMemory.fireAllRules();
}

11.3. jBPM プロセス定義からルール使用

ルールベースが jBPM アクションハンドラとして、あるいはデシジョンハンドラ、割り当てハンドラなどとして動作するようにさせることも可能です — ページフローまたはビジネスプロセス定義のどちらでも可。

<decision name="approval">
         
    <handler class="org.jboss.seam.drools.DroolsDecisionHandler">
        <workingMemoryName>orderApprovalRulesWorkingMemory</workingMemoryName>
        <assertObjects>
            <element>#{customer}</element>
            <element>#{order}</element>
            <element>#{order.lineItems}</element>
        </assertObjects>
    </handler>
    
    <transition name="approved" to="ship">
        <action class="org.jboss.seam.drools.DroolsActionHandler">
            <workingMemoryName>shippingRulesWorkingMemory</workingMemoryName>
            <assertObjects>
                <element>#{customer}</element>
                <element>#{order}</element>
                <element>#{order.lineItems}</element>
            </assertObjects>
        </action>
    </transition>
    
    <transition name="rejected" to="cancelled"/>
    
</decision>

<assertObjects> 要素は、 WorkingMemory 中に、fact としてアサーションされるオブジェクト、 または、オブジェクトのコレクションを返す EL 式を指定します。

jBPM タスク割り当てのために Drools の使用もサポートしています。

<task-node name="review">
    <task name="review" description="Review Order">
        <assignment handler="org.jboss.seam.drools.DroolsAssignmentHandler">
            <workingMemoryName>orderApprovalRulesWorkingMemory</workingMemoryName>
            <assertObjects>
                <element>#{actor}</element>
                <element>#{customer}</element>
                <element>#{order}</element>
                <element>#{order.lineItems}</element>
            </assertObjects>
        </assignment>
    </task>
    <transition name="rejected" to="cancelled"/>
    <transition name="approved" to="approved"/>
</task-node>

あるオブジェクトは、ルールでは Drools グローバルとして扱われます。 すなわち、 jBPM Assignableassignable として 、 そしてSeam Decision オブジェクトは decision として扱われます。 デシジョンを扱うルールは、デシジョンの結果を決定するために、 decision.setOutcome("result") を呼ぶ必要があります。 割り当てを実行するルールは、 Assignable を使ってアクターIDを設定する必要があります。

package org.jboss.seam.examples.shop

import org.jboss.seam.drools.Decision

global Decision decision

rule "Approve Order For Loyal Customer"
  when
    Customer( loyaltyStatus == "GOLD" )
    Order( totalAmount <= 10000 )
  then
    decision.setOutcome("approved");
end
package org.jboss.seam.examples.shop

import org.jbpm.taskmgmt.exe.Assignable

global Assignable assignable

rule "Assign Review For Small Order"
  when
    Order( totalAmount <= 100 )
  then
    assignable.setPooledActors( new String[] {"reviewers"} );
end

第12章 セキュリティ

SeamのセキュリティAPIはシームプロジェクトのドメイン或はページに対して認証と承認機能を提供するAPIです。

12.1. 概要

Seamのセキュリティ機能は下の二つの操作モードを提供します。

  • 単純なモード - 認証と、ロールに基づくセキュリティチェックを提供します

  • より高度なモード - 上記の機能に加えてJBoss Ruleを利用したルールベースのセキュリティチェック機能を提供します。

12.1.1. どちらのモードを使うのが適切か?

アプリケーションの必要性に応じて、ログインしたユーザ、或は特定のロールを有するユーザに対してのみページ閲覧や操作の実行を許可したい場合のような、単純なセキュリティ機能が必要な場合には、「単純なモード」で十分でしょう。 このモデルでは、設定が簡単であることや、必要なライブラリが少なくメモリの消費量も少ないメリットがあります。

一方、複雑なビジネスルールや、コンテクストの状態によるセキュリティのチェックが必要となる場合には、「より高度なモード」を使う必要があるでしょう。

12.2. 要求条件

Seamセキュリティの「より高度なモード」を利用する場合には、下記のjarファイルをモジュールとしてapplication.xmlに設定する必要があります。 一方、「単純なモード」であれば、これらのファイルは必要ありません。

  • drools-compiler-3.0.5.jar

  • drools-core-3.0.5.jar

  • commons-jci-core-1.0-406301.jar

  • commons-jci-janino-2.4.3.jar

  • commons-lang-2.1.jar

  • janino-2.4.3.jar

  • stringtemplate-2.3b6.jar

  • antlr-2.7.6.jar

  • antlr-3.0ea8.jar

webベースのセキュリティを実装する場合にはjboss-seam-ui.jarがアプリケーションの warファイルに含まれている必要があります。 また、EL式でセキュリティを利用する場合にはSeamFaceletViewHandlerの設定が必要となりますので、faces-config.xml に下の様に設定してください。

<application>
    <view-handler>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</view-handler>
</application>

12.3. 認証

Seam セキュリティの提供する認証機構は JAAS (Java Authentication and Authorization Service) の上に構築されており、 ユーザ認証の為の堅牢で設定の自由度の高い API を提供しています。 しかしながら、 Seamで は JAAS の複雑さを隠蔽したより単純化された認証機構も提供しています。

12.3.1. 設定

単純な認証機構ではSeamアプリケーションのコンポーネントに認証を委ねるSeamLoginModule (これは、Seamに内蔵されているJAASのログインモジュールです) を使います。 このログインモジュールはSeamのデフォルトのアプリケーションポリシーとして予め設定されていますので、新たに設定に追加する事なく使用することでき、作成されているアプリケーションのエンティティクラスを利用して、認証メソッドを記述することができます。 この「単純な認証機構」を利用するためにはcomponents.xmlに下記のようにidentityコンポーネントを設定する必要があります。

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:security="http://jboss.com/products/seam/security"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-1.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-1.2.xsd"
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-1.2.xsd">                
        
    <security:identity authenticate-method="#{authenticator.authenticate}"/>
    
</components>

ルールに基づく承認のような、「より高度なセキュリティ機構」を実装す場合には、Drools (JBoss Rules) のjarファイル群をクラスパスに含め、後述のようにコンフィグレーションファイルに若干の設定を加えることが必要となります。

EL式 #{authenticator.authenticate}authenticatorコンポーネントのauthenticateメソッドを使って、ユーザの認証を行うことを示しています。

12.3.2. 認証メソッドの記述

components.xml中で、identityに特定されている (紐付けされている) authenticate-methodSeamLoginModuleがユーザの認証をするときに使用するメソッドを規定します。このメソッドはパラメータをとらず、認証の可否を示すブール値を返すよう期待されています。ユーザのusernameやpasswordは其々、Identity.instance().getUsername()Identity.instance().getPassword()で取得することができます。 また、ユーザが属するロールについてはIdentity.instance().addRole()により、付与することができます。 下に、Java Beanコンポーネント中の認証メソッドの完全な例を示します。

@Name("authenticator")
public class Authenticator {
   @In EntityManager entityManager;
   
   public boolean authenticate() {
      try
      {
         User user = (User) entityManager.createQuery(
            "from User where username = :username and password = :password")
            .setParameter("username", Identity.instance().getUsername())
            .setParameter("password", Identity.instance().getPassword())
            .getSingleResult();

         if (user.getRoles() != null)
         {
            for (UserRole mr : user.getRoles())
               Identity.instance().addRole(mr.getName());
         }

         return true;
      }
      catch (NoResultException ex)
      {
         FacesMessages.instance().add("Invalid username/password");
         return false;
      }
      
   }
   
}

上記の例では、UserUserRoleはアプリケーション独自のエンティティビーンとなっています。 パラメータ roles は "admin", "user" の様に文字列として、Setに追加されてゆく必要があります。 この例の場合、userが見付からずにNoResultExceptionが投げられた場合には、認証メソッドはfalseを返して、認証が失敗したことを示します。

12.3.3. ログインフォームの記述

Identityコンポーネントはusernamepasswordプロパティを提供することにより、ほとんどの認証プロセスに対応することができます。 これらのプロパティはログインフォームのusernameとpasswordのフィールドに直接バインドすることができますので、これらのプロパティがセットされた後にidentity.login()メソッドを呼び出すことにより、設計者が組み込んだ認証システムに基ずきユーザの認証をすることができます。 簡単なログインフォームの例を示します。

<div>
    <h:outputLabel for="name" value="Username"/>
    <h:inputText id="name" value="#{identity.username}"/>
</div>

<div>
    <h:outputLabel for="password" value="Password"/>
    <h:inputSecret id="password" value="#{identity.password}"/>
</div>

<div>
    <h:commandButton value="Login" action="#{identity.login}"/>
</div>

同様にログアウトも、#{identity.logout}を呼び出すことにより実行されます。ログアウトを実行することにより、現在まで認証されていたユーザのセキュリティの状態は破棄されます。

12.3.4. 簡単な設定 ー まとめ

まとめとして、認証システムを設定するためには、以下の3つのステップが必要となります。

  • 認証メソッドをcomponents.xmlに設定する。

  • 認証メソッドを記述する。

  • ユーザを認証するためのログインフォームを記述する。

12.3.5. セキュリティ例外の処理

セキュリティエラーが出た場合にユーザがデフォルトのエラーページを見ることのない様 pages.xmlを設定して、もう少しましなセキュリティエラーページにリダイレクトすることが推奨されます。 セキュリティAPIが投げる主だった例外には下の2種類があります。

  • NotLoggedInException - ユーザがログインすることなく、特定のページ閲覧、或は特定の操作を実行しようとしたときに投げられます。

  • AuthorizationException - ユーザが既にログインしていて、当該ユーザが許可されていないページの閲覧、或は操作を行おうとしたときに投げられます。

NotLoggedInExceptionが投げられた場合には、ユーザはログインページ或は登録ページにリダイレクトされる事が推奨されます。 一方、AuthorizationExceptionが投げられた場合には、 エラーページにリダイレクトされた方が良いでしょう。 これらのセキュリティエラーに対応するリダイレクトを記述したpages.xmlの例を以下に示します。

<pages>

    ...
    
    <exception class="org.jboss.seam.security.NotLoggedInException">
        <redirect view-id="/login.xhtml">
            <message>You must be logged in to perform this action</message>
        </redirect>
    </exception>
    
    <exception class="org.jboss.seam.security.AuthorizationException">
        <end-conversation/>
        <redirect view-id="/security_error.xhtml">
            <message>You do not have the necessary security privileges to perform this action.</message>
        </redirect>
    </exception>
  
</pages>

ほとんどのwebアプリケーションでは、より洗練されたログインリダイレクトを必要としますが、Seamではこの様なケースに対応出来るような機能も持たせています。

12.3.6. ログインリダイレクト

以下のようにして、 認証されていないユーザが特定のビュー (或はワイルドカードで指定されたビュー) の閲覧をしようとした時に、 Seam がユーザをログイン画面にリダイレクトするようにすることができます。

<pages login-view-id="/login.xhtml">

    <page view-id="/members/*" login-required="true"/>
    
    ...
    
</pages>

(これは、前項までの例外処理に比べて少々ぶっきらぼうさを抑えた処理ですが、例外処理によるリダイレクトと組み合わせて使用すると良いでしょう)

ユーザがログインした後で、再度ログインし直したい場合に自動的に最初のページ (ユーザが入ってきたページ) 戻したいような状況を考えてみましょう。 下の様にevent listenerをcomponents.xmlに記述すると、ログインせずに制限されたページの閲覧した (閲覧に失敗した) ことを記憶させておいて、ユーザが再ログインして成功したときに、当初の要求時のページパラメータを基に当該ページにリダイレクトさせることができます。

<event type="org.jboss.seam.notLoggedIn">
    <action expression="#{redirect.captureCurrentView}"/>
</event>
    
<event type="org.jboss.seam.postAuthenticate">
    <action expression="#{redirect.returnToCapturedView}"/>
</event>

ログインリダイレクトは対話スコープの中で実装されていますので、当該の対話スコープをauthenticate()メソッドで終了させないように注意してください。

12.3.7. 高度な認証機能

ここでは、より高度なセキュリティ要求に答えられる、セキュリティAPIで提供されている更に高度な機能について紹介します。

12.3.7.1. コンテナのJAAS設定を利用する

Seam のセキュリティ API が提供する単純化された JAAS の設定を使用しないのであれば、 components.xmljaasConfigName プロパティを設定して、 デフォールトの JAAS の設定にセキュリティを任せることができます。 例えば、 JBossAS を使用していて、 other ポリシー (このポリシーは JBossAS 提供の UsersRolesLoginModule ログインモジュールを使用する) を利用したいのであれば、 以下のように components.xml を記述してください。

<security:identity authenticate-method="#{authenticator.authenticate}" 
                      jaas-config-name="other"/>

12.4. エラーメッセージ

セキュリティAPIはセキュリティに関するイベントについて、多くのデフォルトのフェースメッセージを生成します。下のメッセージキーの一覧表は、リソースファイルのmessage.propertiesで特定のメッセージを置き換えるのに利用できます。

表 12.1. セキュリティメッセージキー

org.jboss.seam.loginSuccessful

セキュリティAPIを通して、無事ログイン出来たときに生成されます。

org.jboss.seam.loginFailed

ユーザネーム、パスワードの組み合わせ、或は何らかの認証のエラーにより、ユーザがログインに失敗したときに生成されます。

org.jboss.seam.NotLoggedIn

ユーザが認証されずにセキュリティチェックが必要な操作、あるいはページへのアクセスを試みたときに生成されます。

12.5. 認可

SeamのセキュリティAPIは、コンポーネント、コンポーネントのメソッド、それにページに対して多くの認可機能を提供します。 ここでは、其々の機能について説明します。 ここで説明するような高度なセキュリティ機能 (ルールベースの認可のような) を使用する場合にはcomponents.xmlに前述のような設定を記述しておかなければならない、ということに留意してください。

12.5.1. 核となる概念

Seamの提供するセキュリティAPIによる認可のメカニズムは、ロールと許可の組み合わせに基づいてユーザに認可を与えるというコンセプトに基づいて作られています。 ロールとは、ユーザの属するgroupあるいはtypeを意味し、そこに属していることにより、アプリケーション中で特定の操作をすることが認められます。 それに対し、パーミッション (許可) とは特定の一つの操作を、時には1回だけ、許可される事を意味します。 勿論、パーミッションだけを使って、アプリケーションを組むことは可能ですが、ロールはユーザの特定のグループに対する特権を、より簡単に、高次元のレベルで与えることができます。

ロールとは単純に、admin、userとかcustomer といったような名前で与えられます。 一方、パーミッションは、名前と操作の組み合わせで与えられ、ここの説明ではname:action (例えば、customer:delete, customer.insert) といった形式で表現しています。

12.5.2. セキュリティコンポーネント

それでは、@Restrictアノテーションを使った、簡単なコンポーネントのセキュリティから始めましょう。

12.5.2.1. @Restrictアノテーション

Seamのコンポーネントは、@Restrictアノテーションを使って、メソッドあるいはクラスのレベルでセキュリティを確保します。 もし、クラスと、そこに含まれるメソッドの両方に@Restrictアノテーションがあった場合には、メソッドの制約が優先され、クラスに対する制約は適用されません。 もし、メソッドがセキュリティのチェックで失敗した場合には、Idetity.checkRestriction()により、例外エラーが投げられます。 (Inline Restriction参照)。 クラスに対する@Restrictは、そこに含まれる全てのメソッドに@Restrictをつけた事と同しことです。

空の@Restrictcomponent:methodNameを意味します。 下のようなコンポーネントの例を見てみましょう。

@Name("account")
public class AccountAction {
    @Restrict public void delete() {
      ...
    }
}

この例の場合、delete()の実行に必要なパーミッションはacount:deleteとなります。同じ事は@Restrict("#{s:hasPermission('account','delete',null)}")と明示的に記述することができます。 更に、別の例を見てみましょう。

@Restrict @Name("account")
public class AccountAction {
    public void insert() {
      ...
    }
    @Restrict("#{s:hasRole('admin')}") 
    public void delete() {
      ...
    }
}

ここでは、コンポーネントクラスが@Restrictとアノテーションが付記されています。これは、`Restrictがオーバーライドされない限り、パーミッションのチェックが暗示的に要求されることを示しています。この例の場合、insert()account:insertのパーミッションを必要とし、delete()はユーザがadminロールに属していることが必要な事を示しています。

先に進む前に、上記の例にある#{s:hasRole()}について見ておきましょう。s:hasRoles:hasPermissionもEL関数で、対応するIdentityクラスのメソッドを示しています。 これらの関数は全てのセキュリティAPIのEL式で使用することができます。

EL式とすることで、@Restrictアノテーションは、Seamコンテキスト中のどのようなオブジェクトの値でも参照することが出来るようになります。 これは、特定のオブジェクトのインスタンスをチェックしてパーミッションを決定する場合に非常に有効な方法です。下の例を見てみましょう。

@Name("account")
public class AccountAction {
    @In Account selectedAccount;
    @Restrict("#{s:hasPermission('account','modify',selectedAccount)}")
    public void modify() {
        selectedAccount.modify();
    }
}

ここで興味深いのは、hasPermission()というファンクション中でselectedAccoutを参照している事です。 この変数の値はSeamのコンテキスト中で検索され、IdentityhasPermission()に渡され、この例の場合、特定のAccountのオブジェクトに対する変更許可を持っているかを決定しています。

12.5.2.2. インラインによる制約

時として、@Restrictアノテーションを使わずに、コードでセキュリティチェックを行いたい場合があるかもしれません。この様な場合には、下のようにIdentity.checkRestriction()を使って、セキュリティ式を評価することができます。

public void deleteCustomer() {
    Identity.instance().checkRestriction("#{s:hasPermission('customer','delete',selectedCustomer)}");
}

もし、評価の結果がtrueでなかった場合、

  • ユーザがログインしていなかったのであれば、NotLoggedInExceptionが投げられ、

  • ユーザがログインしていた場合には、AuthorizationExceptionが投げられます。

また、下のようにJavaコードから直接hasRole()hasPermission()メソッドを呼ぶこともできます。

if (!Identity.instance().hasRole("admin"))
     throw new AuthorizationException("Must be admin to perform this action");

if (!Identity.instance().hasPermission("customer", "create", null))
     throw new AuthorizationException("You may not create new customers");

12.5.3. ユーザインターフェースのセキュリティ

うまくデザインされたユーザインターフェースでは、当該ユーザが利用する権限を持たない物は見せないようにします。 Seamのセキュリティ機構は、コンポーネントのセキュリティで使用したのと同様のEL式を使って、ページの部分あるいは個別の操作について、表示/非表示をコントロールすることができます。

インターフェースのセキュリティの例についてみてみましょう。 まず、ユーザがログインしていない場合に限ってログイン画面が表示される場合を考えてみます。 identity.isLoggedIn()プロパティを使って、下のように記述することができます。

<h:form class="loginForm" rendered="#{not identity.loggedIn}">

もし、ユーザがログインしていなければログインフォームが描画されます。 それでは、managerのロールを持つユーザに対してのみ操作が認められている項目を含むようなメニューの場合を考えてみましょう。 (マネージャー以外にはその項目が表示されないことが必要) この様な場合、下のように記述することができます。

<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">
    Manager Reports
</h:outputLink>

これも、大変シンプルで、ユーザがmanagerロールを持っていなければ、outputLinkは描画されません。rendered属性は一般に制御そのものに使われたり、<s:div><s:span>の中で制御の目的に使われます。

さて、それではもう少し複雑な例として、h:dataTableで、ユーザの権限によりアクションリンクの表示、非表示を管理したい場合を考えてみましょう。 EL関数、s:hasPermissionはオブジェクトをパラメータとして受け付ける機能を持っており、それにより特定のオブジェクトに対するユーザの権限を評価することが可能となります。 下に、dataTableに対し保護されたリンクを設定する例を示します。

<h:dataTable value="#{clients}" var="cl">
    <h:column>
        <f:facet name="header">Name</f:facet>
        #{cl.name}
    </h:column>
    <h:column>
        <f:facet name="header">City</f:facet>
        #{cl.city}
    </h:column>   
    <h:column>
        <f:facet name="header">Action</f:facet>
        <s:link value="Modify Client" action="#{clientAction.modify}"
                rendered="#{s:hasPermission('client','modify',cl)"/>
        <s:link value="Delete Client" action="#{clientAction.delete}"
                rendered="#{s:hasPermission('client','delete',cl)"/>
    </h:column>
</h:dataTable>

12.5.4. ページ単位のセキュリティ

ページ単位のセキュリティのコントロールはアプリケーションがpages.xmlを利用していないと使えませんが、設定は非常に簡単です。 pages.xml中で、セキュリティを確保したいpageエレメントに対し<restrict/><restrict></restrict>要素を加えるだけです。 restrict要素に対し値が与えられていない場合には、{viewId}:render がデフォルトとして評価され、アクセスの可否を決定します。 値が与えれれた場合には、通常のセキュリティの評価と同様に評価されます。下に、いくつかの例を示します。

<page view-id="/settings.xhtml">
    <restrict/>
</page>
        
<page view-id="/reports.xhtml">    
    <restrict>#{s:hasRole('admin')}</restrict>
</page>

上の1番目の例では、/settings.xhtml:renderという暗黙の制約がかけられており、また、2番目の例ではユーザがadminのロールに属するか否かのチェックが行われています。

12.5.5. エンティティのセキュリティ

Seamのセキュリティは、エンティティ単位でのread,insert,update及びdelete操作に対してのセキュリティ制約をかけることを可能にしています。

エンティティクラスのアクション全部に対してセキュリティをかけたいのであれば、下のようにクラスに@Restrictアノテーションを付記します。

@Entity
@Name("customer")
@Restrict
public class Customer {
  ...
}

もし、@Restrictが評価式無しで付記されていれば、デフォルトとしてentityName:actionがチェックされます。ここで、entityNameは当該エンティティの名前 (あるいは@Entityアノテーションがなければ、クラスの名前) で、actionread, insert, update あるいは deleteのいずれかです。

また、下のようにエンティティのライフサイクルに@Restrict アノテーションを付記することにより、特定の操作だけに制約を課すことが出来ます。

  • @PostLoad - エンティティのインスタンスがデータベースからロードされた後に呼び出される。このメソッドはread パーミッションの設定に使用する。

  • @PrePersist - エンティティの新規のインスタンスが (データベースに) 挿入される前に呼び出される。 このメソッドはinsert パーミッションの設定に使用する。

  • @PreUpdate - エンティティが更新される前に呼び出される。 このメソッドはupdateパーミッションの設定に使用する。

  • @PreRemove - エンティティが削除される前に呼び出される。 このメソッドはdeleteパーミッションの設定に使用する。

以下に、あるエンティティへの全てのinsert操作に対してセキュリティチェックを実行する為の設定の例を示します。この例では、セキュリティについて「どのようにアノテーションを付記するのか」を示しているだけですので、メソッドは何もしていない事に留意してください。

  @PrePersist @Restrict
  public void prePersist() {}      
   

ここでは、seamspaceサンプルコードから、認証されたユーザが新規のMemberBlogを挿入することが許可されているのか、をチェックするエンティティパーミッションルールの例を見てみます。 セキュリティチェックの対象となるエンティティ (この例の場合) MemberBlogが自動的にワーキングメモリにアサートされます。

rule InsertMemberBlog
  no-loop
  activation-group "permissions"  
when
  check: PermissionCheck(name == "memberBlog", action == "insert", granted == false)
  Principal(principalName : name)
  MemberBlog(member : member -> (member.getUsername().equals(principalName)))
then
  check.grant();
end;

このルールではPrincipalで示される認証されているユーザが、ブログのエントリを作成したメンバーと同じ名前であれば、memberBlog:insertのパーミッションを与えます。"name : name"という構造は、Principalファクトやそのほかの所でも見られる変数結合で、name変数にPrincipalnameプロパティを結合します。 変数結合は、次の行の、メンバーのusernameとPrincipalの比較 (マッチング) の様な所で、値を参照することを可能にします。 詳細については、JBoss Rules のドキュメンテーションを参照してください。

最後に、JPAプロバイダーをSeamセキュリティと統合するために、リスナークラスをインストールします。

12.5.5.1. JPAでのエンティティセキュリティ

EJB3エンティティビーンのセキュリティチェックはEntityListenerにより行われ、下記のようなMETA-INF/orm.xmlの設定でリスナーをインストールすることが出来ます。

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
                 version="1.0">
                 
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
    
</entity-mappings>

12.5.5.2. Hibernateでのエンティティセキュリティ

SeamでHibernateのSessionFactoryを使っているのであれば、エンティティセキュリティの為に特別な設定をする必要はありません。

12.6. セキュリティルールの記述

今まで、いろいろとパーミッションについて述べてきましたが、このパーミッションについて、どのように定義するのか、また受容されるのかについては述べてきませんでした。 この項ではどのようにパーミッションのチェックが行われるのか、またSeamアプリケーションにどのようにパーミッションを実装するのかについて説明し、セキュリティの章の最後とします。

12.6.1. パーミッションについての概要

特定のユーザがcustomer:modifyのパーミッションを持っているのか否か、セキュリティAPIはどのようにして知るのでしょうか? SeamのセキュリティはJBoss Ruleに基づいた大変ユニークな方法で、ユーザのパーミッションを決定しています。 ルールエンジンを使うことのメリットは、1) 個々のユーザのパーミッションを決定しているビジネスロジックを集中管理することが出来る、2) 複雑な条件の基においてもJBossRuleは効率的にスピーディに処理することが出来る、事にあります。

12.6.2. ルールファイルの設定

Seamセキュリティはパーミッションのチェックに使用するためのsecurityRulesと呼ばれるRuleBaseを検索して使用します。 このファイルはcomponents.xmlに下のように記述、設定します。

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:security="http://jboss.com/products/seam/security"
            xmlns:drools="http://jboss.com/products/seam/drools"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-1.2.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-1.2.xsd"
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-1.2.xsd">                 
        
   <drools:rule-base name="securityRules">
       <drools:rule-files>
           <value>/META-INF/security.drl</value>
       </drools:rule-files>
   </drools:rule-base>    
   
</components>

RuleBaseが設定できましたら、セキュリティルールの記述に進みましょう。

12.6.3. セキュリティルールファイルの作成

まづ、アプリケーションの/META-INFの下にsecurity.drlというファイルを作成してください。 実際には、このファイル名はsecurity.drlである必要は無く、components.xmlの記述と一致していれば、何でもかまいません。

次に設定ファイルの内容について。この時点では、JBoss Ruleのドキュメントから拝借してくるのが手っ取り早いでしょうが、ここでは、下の様な最も単純な例から始めてみましょう。

package MyApplicationPermissions;

import org.jboss.seam.security.PermissionCheck;
import org.jboss.seam.security.Role;

rule CanUserDeleteCustomers
when
  c: PermissionCheck(name == "customer", action == "delete")
  Role(name == "admin")
then
  c.grant();
end;

このサンプルについて一行づつ見てゆきましょう。 まず、パッケージ宣言。JBossRuleのパッケージはルールの集まりで、ルールベース以外の所には関係してきませんので、どのようなパッケージ名であってもかまいません。

次に、PermissionCheckとRoleクラスのインポートで、これらは、ルールエンジンに対して、ルールの中でこれらのクラスを参照することを表わしています。

そして、ルールの記述コード。其々のルールは、ルールごとにユニークな名前が与えられている必要があります (通常は、ルールの目的をルールの名前にします。) この例の場合、CanUserDeleteCustomersがルールの名前で、読んで字の如く、顧客レコードの削除を出来るか出来ないかのチェックに使用します。

ルールの記述が2つの部分から成っている事がわかります。ルールは左部分 (LHS) と右部分 (RHS) として知られている部分から成り立っています。LHSは条件 (即ち、この条件を満たしていれば、、、) を規定しています。 LHSはwhenで表されるセクションにあり、また、RHSはLHSが満たされた場合に実行されるアクション、あるいは結果を記述しています。 RHSはthen以降の部分に記述します。 また、ルールの最後はend;で終了します。

ルールのLHS部分を見てみると、二つの条件が記述されています。 まづ、最初の部分について詳細に見てみましょう。

c: PermissionCheck(name == "customer", action == "delete")

この条件は、ワーキングメモリ中でnameプロパティがcustomerであり、actionプロパティがdeleteであるPermissionCheckオブジェクトが存在していなければならない事を示しています。 ワーキングメモリとは、セッションスコープのオブジェクトで、ルールエンジンがパーミッションチェックをするために必要なコンテキスト情報を所有している物の事を言います。hasPermission()メソッドが呼ばれる度にPermissionCheckオブジェクト、 あるいは Fact がワーキングメモリ中に保持されます。 このPermissionCheckはチェックされるパーミッションに完全に対応します。 即ち、haPermission("account", "create", null)が呼ばれると、パーミッションのチェックが終了するまでの間、nameがaccountでactionがcreateであるPermissionCheckオブジェクトがワーキングメモリに保持されます。

ワーキングメモリには、パーミッションチェックが実行される間保持されるPermissionCheck以外に、認証されたユーザが存在する間、存在する小さなオブジェクトがあります。これらには、認証プロセスで生成されたjava.security.Principalや、当該ユーザが属するロールについてのorg.jboss.seam.security.Role (複数) があります。 また、パラメータとしてIdentity.instance().getSecurityContext().assertObject()にオブジェクトを渡すことにより、他のオブジェクトをワーキングメモリ中に保持することもできます。

先の例に戻り、LHSがc:で始まっていることに気がつくと思います。 これは、変数結合を表しており、条件のマッチングに利用されるオブジェクトへの参照を意味しています。 LHSの2行目には下の記述があります。

Role(name == "admin")

これは、単純にワーキングメモリ中のRoleオブジェクトにadminという名前のnameがなければならない事を示しています。 先に述べたように、ユーザのロールはワーキングメモリ中に保持されています。 従って、これらの条件を合わせると「貴方が、adminロールのメンバーで、且つcustomer:deleteについての許可を持っているかチェックします」と言っていることになります。

そして、ルールが適用された結果を、ルールのRHSの部分に見てゆきましょう。

c.grant()

RHSはJavaコードから成っており、この例の場合はcというオブジェクト (既に述べたように、PermissionCheckオブジェクトへの変数結合) のgrant()メソッドが起動されます。PermissionCheckオブジェクトのnameactionプロパティ以外にfalseに初期設定されたgrantedプロパティが存在します。PermissionCheckgrant()を呼ぶことにより、grantedプロパティはtrueにセットされ、パーミッションのチェックが成功し、ユーザはパーミッションで決められたアクションについて実行することが出来るようになります。

12.6.3.1. ワイルドカードのパーミッションチェック

ワイルドカードを使ってパーミッションチェックを設定することも可能で、これはあるパーミッション名に対して全ての操作を許可します。下のようにルールのPermissionCheckaction制約を省略することにより、実装出来ます。

rule CanDoAnythingToCustomersIfYouAreAnAdmin
when
  c: PermissionCheck(name == "customer")
  Role(name == "admin")
then
  c.grant();
end;        
        

上記のルールでは、adminロールを持つユーザは、どのcustomerに対しても、any操作が可能なパーミッションチェックになっています。

12.7. SSLによるセキュリティ

SeamはHTTPSプロトコルによるpageのセキュリティを基本的な部分についてサポートしています。 この機能は、pages.xmlで必要なページについてschemeを指定することにより簡単に設定することができます。 下の例では/login.xhtmlでHTTPSを使う様に設定しています。

  <page view-id="/login.xhtml" scheme="https">

また、この設定は自動的にJSFのs:links:buttonにも引き継がれ (viewで指定した場合)、リンクも正しいプロトコルで描画されます。前述の例の場合、下のようなリンクも/login.xhtmlがHTTPSを使うように設定されているために、s:link先のlogin.xhmtlにもHTTPSがプロトコルとして使用されます。

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

指定されたプロトコル以外 (正しくないプロトコル) を使って、ページを見ようとすると、正しいプロトコルを使って、指定のページへリダイレクトされます。 schema="https"が指定されているページにhttpでアクセスしようとすると、そのページにhttpsを使ってリダイレクトされます。

全てのページに対して schema をデフォルトとして設定することも可能です。 限られたページにだけ HTTPS を設定したい場合などに特に重要となります。 デフォルトのスキーム設定がされていないと、 現在のスキーマを使用して継続するのがデフォルトの動作になります。 つまり、 HTTPS でページを閲覧すると、 他の HTTPS ページではないページに移動しても HTTPS が継続して使用されることになります(よくない動作)。 このため、 デフォルト ("*") ビューで設定を行いデフォルトの scheme を含むようにすることを推奨します。 (デフォルトの scehma をワイルドカードを使って、"*"として設定しておくことをお勧めします。)

  <page view-id="*" scheme="http"> 

勿論、HTTPSを使う必要がなければ、デフォルトのschemaを指定する必要もありません。

12.8. キャプチャテストの実装

厳密にはセキュリティAPIの一部ではありませんが、アプリケーションを自動ロボットとの不必要なインターアクションから守るために、Captcha (Completely Automated Public Turing test to tell Computers and Humans Apart) を実装することは、新規ユーザ登録や、公開ブログ、フォーラム等の場合には意味のあることです。 Seamは非常に優れた、キャプチャチャレンジを生成するライブラリーJCaptchaとの統合を実現しています。 キャプチャ機能を利用する場合には、Seamのライブラリのディレクトリから、jcaptcha-*.jarをアプリケーションにコピーして、application.xmlにJava モジュールとして登録してください。

12.8.1. キャプチャサーブレットの設定

キャプチャを起動して走らせる為には、Seamの Resourece Servlet を下のように、web.xmlに設定する必要があります。これにより、アプリケーションのページにキャプチャチャレンジのイメージを提供するようになります。

<servlet>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>
</servlet>
    
<servlet-mapping>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>

12.8.2. ページにキャプチャを追加する

ページにキャプチャチャレンジを追加するのはいたって簡単です。 Seamはcaptchaというページスコープのコンポーネントを提供しており、キャプチャのバリデーションを含むキャプチャ処理に必要な全ての機能を提供しています。

<div>
    <h:graphicImage value="/seam/resource/captcha?#{captcha.id}"/>
</div>
  
<div>
    <h:outputLabel for="verifyCaptcha">Enter the above letters</h:outputLabel>
    <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true"/>
    <div class="validationError"><h:message for="verifyCaptcha"/></div>
</div>

上の様に設定することにより、graphicImageがキャプチャチャレンジの表示をコントロールし、inputTextがユーザからの回答を受け取り、フォームが送信された時に自動的にキャプチャと照合されます。

第13章 国際化とテーマ (Internationalization and themes)

多言語 UI メッセージを扱う組み込みコンポーネントの提供により、 Seam では容易に国際化されたアプリケーションを作成できます。

13.1. ロケール

各ユーザログインのセッションは、 java.util.Locale インスタンスと関連しています。 (アプリケーションでは、 locale という名前の セッションスコープのコンポーネントとして扱えます ) 通常の環境では、 ロケールのための特別な設定は不要です。 Seam ではアクティブはロケールの決定は単純に JSF に委譲しています。

  • HTTP リクエストで指定されるロケール (ブラウザのロケール) があり、 そして、faces-config.xml により対応可能なロケールの組み合わせの中にそのロケールがある場合、 その後のセッションの期間、そのロケールを使用します。
  • それ以外で、デフォルトロケールが faces-config.xml 中に指定されていれば、 その後のセッションの期間、そのロケールを使用します。
  • それ以外では、サーバのデフォルトロケールを使用します。

Seam 設定プロパティによるマニュアルでロケールの設定が 可能 です。 ( org.jboss.seam.core.localeSelector.languageorg.jboss.seam.core.localeSelector.country、 そして、org.jboss.seam.core.localeSelector.variant ) でも、これを行う妥当な理由は考え付きません。

しかし、アプリケーションユーザインタフェースを介して、 ユーザにマニュアルでロケール設定を可能とさせることは有用です。 Seam は上記のアルゴリズムによって決定されるロケールをオーバライドする組み込み機能も提供しています。 すべきことは、JSP または、Facelet ページのフォームに以下の断片を追加するだけです。

<h:selectOneMenu value="#{localeSelector.language}">
    <f:selectItem itemLabel="English" itemValue="en"/>
    <f:selectItem itemLabel="Deutsch" itemValue="de"/>
    <f:selectItem itemLabel="Francais" itemValue="fr"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}" value="#{messages['ChangeLanguage']}"/>

あるいは、faces-config.xml に対応されたすべてのロケールの組み合わせが欲しければ、 以下を使ってください。

<h:selectOneMenu value="#{localeSelector.localeString}">
    <f:selectItems value="#{localeSelector.supportedLocales}"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}" value="#{messages['ChangeLanguage']}"/>

これを使用してドロップダウンからアイテムを選択し、ボタンをクリックすれば、 その後のセッションの期間、Seam と JSF のロケールはオーバライドされます。

13.2. ラベル

JSF は、ユーザインタフェースのラベルや説明用テキストの国際化を、 <f:loadBundle /> を使用によって対応しています。 Seam アプリケーションでもこのアプローチが使用可能です。 別途、組み込みの EL 式を利用したテンプレート化されたラベルを表示するために、 Seam メッセージ コンポーネントを利用することも可能です。

13.2.1. ラベルを定義する

各ログインセッションは、 関連付けられた java.util.ResourceBundle のインスタンスを持ちます。 (アプリケーションでは、 org.jboss.seam.core.resourceBundle という名前の セッションスコープのコンポーネントとして扱えます ) この特別なリソースバンドルを通して、 国際化されたラベルを有効化する必要があります。 デフォルトで、 Seam によって使用されるリソースバンドルは、 メッセージと呼ばれ、 messages.propertiesmessages_en.propertiesmessages_en_AU.properties などの名前のファイルでラベルを定義する必要があります。 これらのファイルは通常、 WEB-INF/classes ディレクトリに配置します。

従って、messages_en.properties では、

Hello=Hello

そして、messages_en_AU.properties では、

Hello=G'day

org.jboss.seam.core.resourceBundle.bundleNames と呼ばれる Seam 設定プロパティによって、 リソースバンドルとして異なる名前を選択することが可能です。 リソースバンドル名のリストを指定してメッセージの検索をさせる (深さ優先) こともできます。

<core:resource-bundle>
    <core:bundle-names>
        <value>mycompany_messages</value>
        <value>standard_messages</value>       
    </core:bundle-names>
</core:resource-bundle>

特定のページだけにメッセージを定義したいのであれば、 そのJSFビューIDと同じ名前でリソースバンドルに指定します。 このときIDの最初の / と最後の拡張子を除去します。 つまり /welcome/hello.jsp にメッセージを表示したいだけであれば、 そのメッセージを welcome/hello_en.properties に書きます。

pages.xml に明確なバンドル名を指定することもできます:

<page view-id="/welcome/hello.jsp" bundle="HelloMessages"/>

これで HelloMessages.properties に定義されたメッセージを /welcome/hello.jsp で使うことができます。

13.2.2. ラベルを表示する

もし、Seamのリソースバンドルにラベルを定義したならば、 すべてのページに <f:loadBundle ... /> をタイプすることなく、 これらが使用可能です。 その代わりに、以下のように単純にタイプします:

<h:outputText value="#{messages['Hello']}"/>

あるいは、

<h:outputText value="#{messages.Hello}"/>

さらに良いことに、メッセージそのものには EL 式を含むことも可能です。

Hello=Hello, #{user.firstName} #{user.lastName}
Hello=G'day, #{user.firstName}

コードの中にメッセージを使用することも可能です。

@In private Map<String, String> messages;
@In("#{messages['Hello']}") private String helloMessage;

13.2.3. Faces メッセージ

facesMessages コンポーネントはユーザに成功か失敗かを表示するとても便利な方法です。 上述した機能は、faces messages にも有効です。

@Name("hello")
@Stateless
public class HelloBean implements Hello {
    @In FacesMessages facesMessages;
    
    public String sayIt() {
        facesMessages.addFromResourceBundle("Hello");
    }
}

これは、ユーザのロケールに応じて、Hello, Gavin King あるいは、 G'day, Gavin と表示されます。

13.3. タイムゾーン

org.jboss.seam.core.timezone という名の セッションスコープの java.util.Timezone インスタンスと、 org.jboss.seam.core.timezoneSelector という名の タイムゾーン変更のためのSeamコンポーネントもあります。 デフォルトではタイムゾーンはサーバのデフォルトタイムゾーンです。 あいにくJSF仕様では、 <f:convertDateTime>を使って明示的にタイムゾーンを指定しない限り、 全ての日付と時刻はUTCであると想定され、UTCとして表示されます。 これは非常に不便なデフォルト動作です。

Seamはこの動作をオーバーライドし、全ての日付と時刻をデフォルトでSeamタイムゾーンにします。 さらにSeamは、Seamタイムゾーンにおける変換をいつでも行えるよう、 <s:convertDateTime> タグを提供します。

13.4. テーマ

Seamアプリケーションはまた、とても簡単にスキン変更ができます。 テーマAPIはローカライゼーションAPIにとても似ていますが、 もちろんこれら2つの関心事は直交しており、 ローカライゼーションとテーマの両方をサポートするアプリケーションもあります。

まず、サポートされるテーマのセットを設定します:

<theme:theme-selector cookie-enabled="true">
    <theme:available-themes>
        <value>default</value>
        <value>accessible</value>
        <value>printable</value>
    </theme:available-themes>
</theme:theme-selector>

最初にリストされたテーマがデフォルトテーマであることに注意してください。

テーマは、テーマと同じ名前でプロパティファイルに定義されます。 例えば、 default テーマは default.properties に一連のエントリとして定義されます。 例えば、default.properties は以下のように定義します:

css ../screen.css
template template.xhtml

通常、 テーマリソースバンドルのエントリは CSS スタイルや画像へのパスや facelet テンプレートの名前になるでしょう (通常はテキストであるローカライゼーションリソースバンドルとは違って)。

これでJSPやfaceletページにおいてこれらのエントリを使えます。 例えば、faceletページでスタイルシートを適用するには:

<link href="#{theme.css}" rel="stylesheet" type="text/css" />

最も強力な使い方として、 faceletでは <ui:composition> によってテンプレートを適用できます。

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    template="#{theme.template}">

ちょうどロケールセレクタのように、 ユーザが自由にテーマを変更できるよう、組み込みのテーマセレクタがあります。

<h:selectOneMenu value="#{themeSelector.theme}">
    <f:selectItems value="#{themeSelector.themes}"/>
</h:selectOneMenu>
<h:commandButton action="#{themeSelector.select}" value="Select Theme"/>

13.5. ロケールとテーマ設定のクッキーによる永続化

ロケールセレクタ、テーマセレクタ、タイムゾーンセレクタは全て、 ロケールとテーマ設定をクッキーに永続化することをサポートしています。 単に cookie-enabled プロパティを設定してください。

<theme:theme-selector cookie-enabled="true">
    <theme:available-themes>
        <value>default</value>
        <value>accessible</value>
        <value>printable</value>
    </theme:available-themes>
</theme:theme-selector>

<core:locale-selector cookie-enabled="true"/>

第14章 Seam Text

コラボレーション志向のウェブサイトでは、 フォーラムへの投稿、wikiページ、blog、コメントなどに利用するための、 人間のためのマークアップ言語が必要になります。 SeamにはSeam Textと呼ばれる言語に従ったフォーマット済みテキストを表示するための <s:formattedText/> コントロールがあります。 Seam TextはANTLRベースのパーサを利用して実装されていますが、 Seam Textを利用するにあたってANTLRについての知識は不要なので安心してください。

14.1. フォーマットの基本

シンプルな例:

It's easy to make *bold text*, /italic text/, |monospace|, 
~deleted text~, super^scripts^ or _underlines_.

これを <s:formattedText/> で表示させると、 以下のHTMLが生成されます:

<p>
It's easy to make <b>bold text</b>, <i>italic text</i>, <tt>monospace</tt>
<del>deleted text</del>, super<sup>scripts</sup> or <u>underlines</u>.
</p>

空行は新しいパラグラフを作成するときに使用します。 また、+ は見出しに使用します:

+This is a big heading
You /must/ have some text following a heading!
 
++This is a smaller heading
This is the first paragraph. We can split it across multiple 
lines, but we must end it with a blank line.

This is the second paragraph.

(段落内の改行は単に無視されます。 新しい段落としてテキストを記述したい場合は空行が必要なことを忘れないでください。) これが結果の HTML です:

<h1>This is a big heading</h1>
<p>
You <i>must</i> have some text following a heading!
</p>
 
<h2>This is a smaller heading</h2>
<p>
This is the first paragraph. We can split it across multiple 
lines, but we must end it with a blank line.
</p>

<p>
This is the second paragraph.
</p>

順序付きリストは # 文字で作成できます。順序なしリストは = 文字を使います:

An ordered list:
        
#first item
#second item
#and even the /third/ item

An unordered list:

=an item
=another item
<p>
An ordered list:
</p>
 
<ol>       
<li>first item</li>
<li>second item</li>
<li>and even the <i>third</i> item</li>
</ol>

<p>
An unordered list:
</p>

<ul>
<li>an item</li>
<li>another item</li>
</ul>

引用ブロックはダブルクオートで囲む必要があります:

The other guy said:
        
"Nyeah nyeah-nee 
/nyeah/ nyeah!"

But what do you think he means by "nyeah-nee"?
<p>
The other guy said:
</p>
        
<q>Nyeah nyeah-nee
<i>nyeah</i> nyeah!</q>

<p>
But what do you think he means by <q>nyeah-nee</q>?
</p>

14.2. プログラムコードや特殊文字を含むテキストの記述

*, |, # などの特殊文字や、<, >, & などのHTMLで利用される文字は \ でエスケープします:

You can write down equations like 2\*3\=6 and HTML tags
like \<body\> using the escape character: \\.
<p>
You can write down equations like 2*3=6 and HTML tags
like &lt;body&gt; using the escape character: \.
</p>

また、 バックティック (`) を使ってコードのブロックを囲むことができます。

My code doesn't work:

`for (int i=0; i<100; i--)
{
    doSomething();
}`

Any ideas?
<p>
My code doesn't work:
</p>

<pre>for (int i=0; i&lt;100; i--)
{
    doSomething();
}</pre>

<p>
Any ideas?
</p>

14.3. リンク

リンクを作るには以下の構文を利用します:

Go to the Seam website at [=>http://jboss.com/products/seam].

または、リンクテキストを指定したい場合:

Go to [the Seam website=>http://jboss.com/products/seam].

上級者向けですが、この構文を利用したwikiワードのリンクを解釈できるようSeam Textパーサをカスタマイズすることも可能です。

14.4. HTMLの記述

テキストには限定されたHTMLのサブセットを含めることが出来ます (心配しないでください、 クロスサイトスクリプティング攻撃には利用できないようなサブセットを選んでいます)。 これはリンクを作成する際などに便利です。

You might want to link to <a href="http://jboss.com/products/seam">something
cool</a>, or even include an image: <img src="/logo.jpg"/>

テーブルも作れます:

<table>
    <tr><td>First name:</td><td>Gavin</td></tr>
    <tr><td>Last name:</td><td>King</td></tr>
</table>

あなたがやろうと思えば他にもいろいろできるでしょう!

第15章 iText PDF 生成

Seam には iText を使ったドキュメント生成用のコンポーネントセットが含まれるようになります。 Seam の iText ドキュメントサポートで主に焦点を置いているのは PDF の生成ですが、 Seam は RTF ドキュメントの生成についても基本的なサポートを提供します。

15.1. PDF サポートを使う

iText サポートは jboss-seam-pdf.jar で提供されます。 この JAR には、 PDF にレンダリングが可能となるビューの作成に使用される iText JSF 制御、 およびレンダリングされたドキュメントをユーザーに提供する DocumentStore コンポーネントが含まれています。 PDF サポートをご使用のアプリケーションに含ませるには、 iText JAR ファイルと一緒に WEB-INF/lib ディレクトリに jboss-seam-pdf.jar を含ませます。 Seam の iText サポートを使用するのに必要な設定はこれだけで他にはありません。

Seam iText モジュールにはビューテクノロジーとして Facelets を使用する必要があります。 ライブラリの今後のバージョンは JSP の使用にも対応する可能性があります。 また、 seam-ui パッケージの使用も必要となります。

examples/itext プロジェクトには実行可能なデモ用 PDF サポートのサンプルが含まれています。 正確なパッケージ化の導入を行い、 現在サポートされている PDF 生成の主要な機能を実際に示すサンプルがいくつか含まれています。

15.2. ドキュメントを作成する

ドキュメントは http://jboss.com/products/seam/pdf 名前空間にあるタグを使って facelets documents で生成されます。 ドキュメントには必ずドキュメントの root に document タグがなければなりません。 document タグは Seam がドキュメントを DocumentStore に生成し HTML リダイレクトをその格納コンテントにレンダリングする準備を行います。 次は 1 行のみのテキストから成る小規模の PDF ドキュメントです。

<p:document xmlns:p="http://jboss.com/products/seam/pdf">                                                      
   The document goes here.                                                                                             
</p:document>
        

15.2.1. p:document

p:document タグは次の属性をサポートしています。

type

生成されるドキュメントのタイプです。 有効な値は、 PDFRTFHTML の各モードになります。 Seam のデフォルト設定は PDF 生成で、 多くの機能が正常に動作するのは PDF ドキュメントを生成している場合に限られます。

pageSize

生成されるページのサイズです。 もっとも一般的に使用される値は LETTERA4 でしょう。 対応ページサイズの一覧は com.lowagie.text.PageSize クラスをご覧ください。 代わりに、 pageSize はページ幅と高さを直接提供することができます。 例えば、 "612 792" という値は LETTER のページサイズに相当します。

orientation

ページ位置です。 有効な値は portraitlandscape です。 横モードでは、 ページ幅と高さのサイズ値が逆転します。

margins

右、 左、上部、 下部 の余白部分の値です。

marginMirroring

余白の設定が交互のページで逆転されることを示します。

ドキュメントのメタデータもドキュメントタグの属性として設定されます。 次のメタデータフィールドがサポートされています。

title

subject

keywords

author

creator

15.3. 基本的なテキストのエレメント

役に立つドキュメントにはテキスト以外のものも含まれる必要があります。 ただし、 標準 UI コンポーネントは HTML 生成に対して適合されるので、 PDF コンテントの生成には役に立ちません。 Seam は特殊な UI コンポーネントを適切な PDF コンテントの生成に対して提供しています。 <p:image><p:paragraph> のようなタグはシンプルなドキュメントのベーシックな基盤となります。 <p:font> のようなタグはその回りのコンテントすべてに対してスタイル情報を提供します。

<p:document xmlns:p="http://jboss.com/products/seam/pdf"> 
    <p:image alignment="right" wrap="true" resource="/logo.jpg" />
    <p:font size="24">
        <p:paragraph spacingAfter="50">My First Document</p:paragraph>
    </p:font>

    <p:paragraph alignment="justify">
        This is a simple document.  It isn't very fancy.
    </p:paragraph>
</p:document>
        

15.3.1. p:paragraph

テキストでの使用が多く、 文章をひとかたまりごとに区切るため理にかなったグループでテキストの断片の流れを作り、形成、スタイル化することができます。

firstLineIndent

extraParagraphSpace

leading

multipliedLeading

spacingBefore

エレメントの前に挿入される空白スペースです。

spacingAfter

エレメントの後に挿入される空白スペースです。

indentationLeft

indentationRight

keepTogether

15.3.2. p:text

text タグによりテキストの断片が通常の JSF コンバータのメカニズムを使用してアプリケーションデータから生成されるようになります。 HTML ドキュメントをレンダリングする場合に使用される outputText タグに非常に似ています。 次に例を示します。

<p:paragraph>
    The item costs <p:text value="#{product.price}">
        <f:convertNumber type="currency" currencySymbol="$"/>
    </p:text>
</p:paragraph>
            
value

表示される値です。 一般的には値バインディング表現になります。

15.3.3. p:font

フォントの宣言にはダイレクトがありません。

familyName

フォントファミリーです。 COURIERHELVETICATIMES-ROMANSYMBOLZAPFDINGBATS のいずれかになります。

size

フォントのポイントサイズです。

style

フォントのスタイルです。 NORMALBOLDITALICOBLIQUEUNDERLINELINE-THROUGH などの組み合わせになります。

15.3.4. p:newPage

p:newPage は改ページを挿入します。

15.3.5. p:image

p:image はドキュメントにイメージを挿入します。 イメージはクラスパスから、またはresource 属性を使ってウェブアプリケーションコンテキストからロードすることができます。

<p:image resource="/jboss.jpg" />

リソースはアプリケーションコードで動的に生成することもできます。 imageData 属性は値バインディング表現を指定することができ、 この値は java.awt.Image オブジェクトになります。

<p:image imageData="#{images.chart}" />
resource

含ませるイメージリソースの場所です。 リソースはウェブアプリケーションのドキュメントルートに対して相対的でなければなりません。

imageData

アプリケーション生成のイメージにバインドするメソッド表現です。

rotation

度数によるイメージの回転です。

height

イメージの高さです。

width

イメージの幅です。

alignment

イメージの位置です。 (可能な値については 項15.8.2. 「位置調整の値」 を参照)

alt

イメージに対して代替として表示されるテキストです。

indentationLeft

indentationRight

spacingBefore

エレメントの前に挿入される空白スペースです。

spacingAfter

エレメントの後に挿入される空白スペースです。

widthPercentage

initialRotation

dpi

scalePercent

イメージに使用する倍率です (百分率) 。 1 つの値または x と y 別々の倍率を示す 2 つの値で表すことができます。

wrap

underlying

15.3.6. p:anchor

p:anchor はドキュメントからクリックできるリンクを定義します。 次の属性をサポートしています。

name

ドキュメント内のアンカー目的地の名前です。

reference

リンクの参照先です。 ドキュメント内の別のポイントへのリンクは「#」で開始してください。 たとえば、 link1 という name を持つアンカー位置を参照するには「#link1」を使います。 リンクはドキュメントの外にあるリソースをポイントする完全 URL でも構いません。

15.4. ヘッダとフッタ

15.4.1. p:header and p:footer

p:headerp:footer コンポーネントは、 最初のページを除き生成されたドキュメントの各ページにヘッダとフッタのテキストを配置する機能を提供します。 ヘッダとフッタの宣言はドキュメント上部付近に表されます。

alignment

ヘッダ/フッタのボックスセクションの位置です。 (位置の値は 項15.8.2. 「位置調整の値」 を参照)

backgroundColor

ヘッダ/フッタボックスの背景色です。 (色の値は 項15.8.1. 「Color Values」 を参照)

borderColor

ヘッダ/フッタボックスの境界線の色です。 borderColorLeftborderColorRightborderColorTopborderColorBottom を使って各線は別々に設定することができます。 (色の値は 項15.8.1. 「Color Values」 を参照)

borderWidth

境界線の幅です。 borderWidthLeftborderWidthRightborderWidthTopborderWidthBottom を使って各線は別々に指定することができます。

15.4.2. p:pageNumber

現在のページ番号は p:pageNumber タグを使うとヘッダまたはフッタの内側に配置することができます。 このページ番号タグはヘッダまたはフッタのコンテキスト内で 1 度だけ使用することができます。

15.5. 章とセクション

生成されるドキュメントが本または論文の構造をとる場合、 p:chapterp:section の各タグを使用して必要となる構造を構成することができます。 セクションは章の内側でのみ使用できますが、 適宜、 ネストさせることができます。 ほとんどの PDF ビューワはドキュメント内の章とセクション間を簡単に移動できる機能を提供しています。

<p:document xmlns:p="http://jboss.com/products/seam/pdf"
            title="Hello">

   <p:chapter number="1">
      <p:title><p:paragraph>Hello</p:paragraph></p:title>
      <p:paragraph>Hello #{user.name}!</p:paragraph>
   </p:chapter>

   <p:chapter number="2">
      <p:title><p:paragraph>Goodbye</p:paragraph></p:title>
      <p:paragraph>Goodbye #{user.name}.</p:paragraph>
   </p:chapter>

</p:document>    
        

15.5.1. p:chapter と p:section

number

章の番号です。 各章は章番号が割り当てられなければなりません。

numberDepth

セクション番号付けの奥行きです。 セクションはすべて回りの章/セクションに相対的に番号付けされます。 デフォルトの奥行き 3 で表示される場合は、 第 3 章の セクション 1 の小セクション 4 はセクション 3.1.4 と表されるでしょう。 章番号を省略するには、 奥行き 2 を使用してください。 この場合、 セクション番号は 1.4 と表されます。

15.5.2. p:title

いずれの章やセクションも p:title を含むことができます。 タイトルは章/セクション番号のとなりに表示されます。 タイトルのボディは、 生テキストを含ませることも、 p:paragraph にすることもできます。

15.6. 一覧

一覧の構成は p:listp:listItem のタグを使って表示させることができます。 一覧には適宜、 ネストされたサブリストを含ませることもできます。 一覧のアイテムは一覧の外側では使用できません。 次のドキュメントは ui:repeat タグを使って Seam コンポーネントから取得した値の一覧を表示しています。

<p:document xmlns:p="http://jboss.com/products/seam/pdf"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            title="Hello">
   <p:list style="numbered">
      <ui:repeat value="#{documents}" var="doc">
         <p:listItem>#{doc.name}</p:listItem>
      </ui:repeat>
   </p:list>
</p:document>                
        

15.6.1. p:list

p:list は次の属性をサポートしています。

style

一覧の順序付けと箇条書きのスタイルです。 NUMBEREDLETTEREDGREEKROMANZAPFDINGBATSZAPFDINGBATS_NUMBER のいずれかを使用します。 スタイルが指定されていないと一覧のアイテムは箇条書きスタイルになります。

listSymbol

箇条書きの一覧用で、 箇条書きの記号を指定します。

indent

一覧のインデントレベルです。

lowerCase

文字を使った一覧スタイル用で、 この文字を小文字にするかどうかを指示します。

charNumber

ZAPFDINGBATS 用で、 箇条書きの記号の文字コードを指示します。

numberType

ZAPFDINGBATS_NUMBER 用で、 番号付けのスタイルを指示します。

15.6.2. p:listItem

p:listItem は次の属性をサポートしています。

alignment

一覧のアイテムの位置です。 (可能な値は 項15.8.2. 「位置調整の値」 を参照)

indentationLeft

左インデントの量です。

indentationRight

右インデントの量です。

listSymbol

この一覧アイテムのデフォルト一覧記号を上書きします。

15.7. 表

表の構成は p:tablep:cell のタグを使って作成することができます。 多くの表構成とは異なり明示的な列の宣言はありません。 表にコラムが 3 つある場合は、 3 セルすべてが自動的に列を形成します。 表の構成が複数ページに渡る場合、 ヘッダとフッタは繰り返されます。

<p:document xmlns:p="http://jboss.com/products/seam/pdf"   
            xmlns:ui="http://java.sun.com/jsf/facelets"
            title="Hello">   
   <p:table columns="3" headerRows="1">
      <p:cell>name</p:cell>
      <p:cell>owner</p:cell>
      <p:cell>size</p:cell>
      <ui:repeat value="#{documents}" var="doc">
         <p:cell>#{doc.name}</p:cell>
         <p:cell>#{doc.user.name}</p:cell>
         <p:cell>#{doc.size}</p:cell>
      </ui:repeat>
   </p:table>
</p:document>            
        

15.7.1. p:table

p:table は次の属性をサポートしています。

columns

表の列を構成するコラム (セル) の数です。

widths

各コラムに関連する幅です。 各コラムに 1 つの値があるはずです。 たとえば、 widths="2 1 1" なら、 コラムが 3 つあり 1 番目のコラムのサイズは 2 番目と 3 番目のコラムの 2 倍になっているはずです。

headerRows

ヘッダまたはフッタの列とみなされる最初の列番号で、 表が複数ページにわたる場合には繰り返されるはずです。

footerRows

フッタの列とみなされる列の番号です。 この値は headerRows 値から差し引かれます。 ドキュメントにヘッダを構成する列が 2 列ありフッタを構成する列が 1 列の場合、 headerRows は 3 に設定され footerRows は 1 に設定されているはずです。

widthPercentage

表が表示されるページ幅の割合です。

horizontalAlignment

表の横方向位置修正です。 (可能な値については 項15.8.2. 「位置調整の値」 を参照)

skipFirstHeader

runDirection

lockedWidth

splitRows

spacingBefore

エレメントの前に挿入される空白スペースです。

spacingAfter

エレメントの後に挿入される空白スペースです。

extendLastRow

headersInEvent

splitLate

keepTogether

15.7.2. p:cell

p:cell は次の属性をサポートしています。

colspan

colspan を 1 越える値に宣言するとセルが 1 コラムを越えることができます。 表を複数の列にわたって広げる機能はありません。

horizontalAlignment

セルの横方向位置調整です。 (可能な値は 項15.8.2. 「位置調整の値」 を参照)

verticalAlignment

セルの縦方向位置調整です。 (可能な値は 項15.8.2. 「位置調整の値」 を参照)

padding

paddingLeftpaddingRightpaddingToppaddingBottom を使って特定の側面を詰めることを指定することもできます。

useBorderPadding

leading

multipliedLeading

indent

verticalAlignment

extraParagraphSpace

fixedHeight

noWrap

minimumHeight

followingIndent

rightIndent

spaceCharRatio

runDirection

arabicOptions

useAscender

grayFill

rotation

15.8. ドキュメントの定数

本セクションでは複数のタグで属性により共有される定数のいくつか説明します。

15.8.1. Color Values

Seam のドキュメントはまだフルカラー仕様をサポートしていません。 現在、 次の指定色のみサポートされています。 whitegraylightgraydarkgrayblackredpinkyellowgreenmagentacyanblue です。

15.8.2. 位置調整の値

位置調整の値が使用される場所で、 Seam PDF は次の横方向の位置調整値、 leftrightcenterjustifyjustifyall をサポートしています。 縦方向の値は topmiddlebottombaseline です。

15.9. iText を設定する

ドキュメントの生成は特に設定を必要とすることなく、 そのまま動作させることができます。 ただし、 本格的なアプリケーションには必要となる設定ポイントがいくつかあります。

デフォルトの実装は汎用 URL /seam-doc.seam から PDF ドキュメントを提供します。 多くのブラウザ (そしてユーザー) は、 /myDocument.pdf などのように実際の PDF 名を含む URL を好みます。 これにはいくつか設定が必要になってきます。 PDF ファイルを提供するには、 すべての *.pdf リソースが Seam Servlet Filter および DocumentStoreServlet にマッピングされなければなりません。

<filter>
    <filter-name>Seam Servlet Filter</filter-name>
    <filter-class>org.jboss.seam.servlet.SeamServletFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Seam Servlet Filter</filter-name>
    <url-pattern>*.pdf</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>Document Store Servlet</servlet-name>
    <servlet-class>org.jboss.seam.pdf.DocumentStoreServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Document Store Servlet</servlet-name>
    <url-pattern>*.pdf</url-pattern>
</servlet-mapping>

document store コンポーネントにある useExtensions オプションは、 document store に生成されているドキュメントタイプに対して正しいファイル名の拡張子を付けて URL を生成するよう指示することによりこの機能を補います。

        
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pdf="http://jboss.com/products/seam/pdf">
    <pdf:documentStore useExtensions="true" />
</components>

生成されるドキュメントは対話スコープに格納され、 対話が終了するときに期限が切れます。 この時点で、 ドキュメントへの参照は無効になります。 ドキュメントが存在しない場合、 documentStore の errorPage プロパティを使って表示されるデフォルトのビューを指定できます。

        
<pdf:documentStore useExtensions="true" errorPage="/pdfMissing.seam" />

15.10. iText のリンク

iText に関する詳細は、 次を参照してください。

第16章 Email

Seam には email の送信およびテンプレート作成用のオプションコンポーネントが含まれるようになります。

Email のサポートは jboss-seam-mail.jar により提供されます。 この JAR にはメールの作成に使用されるメール JSF コントロールおよび mailSession 管理コンポーネントが含まれます。

examples/mail プロジェクトには実行可能なデモ用 email サポートのサンプルが含まれています。 正しいパッケージ化の方法を行い、 また現在サポートされている主要な機能を実際に示すサンプルがいくつか含まれています。

16.1. メッセージを作成する

Seam Mail を使うためにテンプレート作成用の新しい言語を丸ごと学ぶ必要はありません — email は単純に facelet になります。

<m:message xmlns="http://www.w3.org/1999/xhtml"
    xmlns:m="http://jboss.com/products/seam/mail"
    xmlns:h="http://java.sun.com/jsf/html">
  
    <m:from name="Peter" address="peter@example.com" />
    <m:to name="#{person.firstname} #{person.lastname}">#{person.address}</m:to>
    <m:subject>Try out Seam!</m:subject>
    
    <m:body>
        <p><h:outputText value="Dear #{person.firstname}" />,</p>
        <p>You can try out Seam by visiting 
        <a href="http://labs.jboss.com/jbossseam">http://labs.jboss.com/jbossseam</a>.</p>
        <p>Regards,</p>
        <p>Peter</p>
    </m:body>
    
</m:message>

<m:message> タグはメッセージ全体を包み、 Seam に email のレンダリングを開始するよう指示します。 <m:message> タグ内では、 メッセージの送信元の設定に <m:from> タグ、 送信者の指定に <m:to> タグ (通常のfacelet 内にいるのでそれに応じた EL の使用方法に注意してください)、 また <m:subject> タグを使用します。

<m:body> は email のボディを包みます。 HTML 正規タグをボディ内や JSF コンポーネント内に使用することができます。

これで email テンプレートのできあがりです。 送信方法についてですが、 m:message のレンダリングの最後に、 mailSession が email を送信するようコールされるので、 ユーザーがすべきことは Seam にそのビューをレンダリングするよう支持するだけです。

@In(create=true)
private Renderer renderer;
   
public void send() {
    try {
       renderer.render("/simple.xhtml");
       facesMessages.add("Email sent successfully");
   } 
   catch (Exception e) {
       facesMessages.add("Email sending failed: " + e.getMessage());
   }
}

たとえば、 無効な email アドレスを入力すると例外が投げられ、 その例外がキャッチされてユーザーに表示されます。

16.1.1. 添付

Seam では email へのファイル添付が容易になっています。 ファイルを操作する際に使用される標準 java タイプのほとんどに対応しています。

jboss-seam-mail.jar に email 送信をしたい場合、

<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar"/>

Seam はファイルをクラスパスからロードして、 email にそのファイルを添付します。 デフォルトでは、 jboss-seam-mail.jar という名前で添付されます。 別の名前にしたい場合は fileName 属性を追加するだけです。

<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar" fileName="this-is-so-cool.jar"/>

java.io.File, java.net.URL を添付することもできます。

<m:attachment value="#{numbers}"/>

または、 byte[] あるいは java.io.InputStream

<m:attachment value="#{person.photo}" contentType="image/png"/>

byte[]java.io.InputStream に対しては添付の MIME タイプを指定する必要があるのがわかります (この情報はファイルの一部として扱われないため)。

さらに便利なことに、 使用する通常のタグの前後を <m:attachment> でラップするだけで Seam 生成 PDF や標準 JSF ビュー を添付することができます。

<m:attachment fileName="tiny.pdf">
    <p:document>                                                      
        A very tiny PDF                                                                                                
    </p:document>
</m:attachment>

添付したいファイル一式が手元にある場合 (例、 データーベースからロードした写真一式)、 <ui:repeat> を使うだけで添付できます。

<ui:repeat value="#{people}" var="person">
    <m:attachment value="#{person.photo}" contentType="image/jpeg" fileName="#{person.firstname}_#{person.lastname}.jpg"/>
</ui:repeat>

16.1.2. HTML/Text 代替部分

今日では、 ほとんどのメールユーザーは HTML に対応しているなか、 HTML に対応していないユーザーもいます。 このため、 email のボディに代替となるプレーンテキストを追加することができます。

<m:body>
    <f:facet name="alternative">Sorry, your email reader can't show our fancy email, 
please go to http://labs.jboss.com/jbossseam to explore Seam.</f:facet>
</m:body>

16.1.3. 複数の受信者

email を複数の送信者グループに送信する必要があることも少なくありません (ユーザーグループなど 。 受信者メールタグはすべて <ui:repeat> 内に配置することができます。

<ui:repeat value="#{allUsers} var="user">
    <m:to name="#{user.firstname} #{user.lastname}" address="#{user.emailAddress}" />
</ui:repeat>

16.1.4. 複数のメッセージ

ただし、 若干異なる内容のメッセージを各受信hさに送信する必要がある場合もあります (パスワードのリセットなど)。 最適な方法としては、 メッセージ全体を <ui:repeat> 内に配置することです。

<ui:repeat value="#{people}" var="p">
    <m:message>
        <m:from name="#{person.firstname} #{person.lastname}">#{person.address}</m:from>
        <m:to name="#{p.firstname}">#{p.address}</m:to>
            ...
    </m:message>
</ui:repeat>

16.1.5. テンプレートの作成

メールのテンプレート作成例では、 facelets テンプレートは Seam メールタグで正しく動作することを示しています。

jboss.org の template.xhtml には次が含まれています。

<m:message>
   <m:from name="Seam" address="do-not-reply@jboss.com" />
   <m:to name="#{person.firstname} #{person.lastname}">#{person.address}</m:to>
   <m:subject>#{subject}</m:subject>
   <m:body>
       <html>
           <body>
               <ui:insert name="body">This is the default body, specified by the template.</ui:insert>
           </body>
       </html>
   </m:body>
</m:message>

jboss.org の templating.xhtml には次が含まれています。

<ui:param name="subject" value="Templating with Seam Mail"/>
<ui:define name="body">
    <p>This example demonstrates that you can easily use <i>facelets templating</i> in email!</p>
</ui:define>

16.1.6. 国際化

Seam は国際化メッセージの送信に対応しています。 デフォルトでは、 JSF で提供されるエンコーディングが使用されますが、 テンプレートで上書きすることができます。

<m:message charset="UTF-8">
   ...
</m:message>

ボディ、 件名、 そして受信者名と送信者名はコード化されます。 テンプレートのエンコーディングを設定して facelets が正しい文字セットをページ解析に使用するよう確認する必要があります。

<?xml version="1.0" encoding="UTF-8"?>

16.1.7. その他のヘッダ

その他のヘッダを email に追加したいことがあります。 Seam が対応しているものがいくつかあります (項16.4. 「タグ」 を参照)。 たとえば、 email の重要度を設定することができます。

<m:message xmlns:m="http://jboss.com/products/seam/mail"
           importance="low"
           requestReadReceipt="true"/>

これ以外、 <m:header> タグを使ってメッセージにあらゆるヘッダを追加することができます。

<m:header name="X-Sent-From" value="JBoss Seam"/>

16.2. email を受信する

EJB を使用している場合、 MDB (Message Driven Bean) を使用して email を受信することができます。 Seam は mail-ra.rar の改良バージョンと共に JBoss AS で配信されます。 改良点が JBoss AS のリリースバージョンに参入するまでのあいだ、 デフォルトの rar を Seam で配信されるものに置き換えることを推奨します。

次のように設定することができます。

@MessageDriven(activationConfig={
         @ActivationConfigProperty(propertyName="mailServer", propertyValue="localhost"),
         @ActivationConfigProperty(propertyName="mailFolder", propertyValue="INBOX"),
         @ActivationConfigProperty(propertyName="storeProtocol", propertyValue="pop3"),
         @ActivationConfigProperty(propertyName="userName", propertyValue="seam"),
         @ActivationConfigProperty(propertyName="password", propertyValue="seam")
})
@ResourceAdapter("mail-ra.rar")
@Name("mailListener")
public class MailListenerMDB implements MailListener {

   @In(create=true)
   private OrderProcessor orderProcessor;

   public void onMessage(Message message) {
      // Process the message
      orderProcessor.process(message.getSubject());
   }
   
}

受信する各メッセージにより、 onMessage(Message message) がコールされることになります。 ほとんどの seam アノテーションは MDB 内で動作しますが、 永続コンテキストにはアクセスしないでください。

デフォルトの mail-ra.rar についての詳細は http://wiki.jboss.org/wiki/Wiki.jsp?page=InboundJavaMail をご覧ください。 Seam と共に配信されているバージョンには、 JavaMail のデバッグ機能を有効にする debug プロパティ、 使用している MDB へのメッセージ配信が正常に完了すると POP3 メールボックスをフラッシュする機能を無効にする flush プロパティ (デフォルトでは ture)、 デフォルトの TCP ポートを上書きする port プロパティも含まれています。 これに対する api は今後 JBoss AS に加えられる変更に準じて変わる可能性があるので注意してください。

JBoss AS を使用していない場合は mail-ra.rar を使用することができます (メールディレクトリ内の Seam に含まれている)。 または、 同様のアダプタを含む独自のアプリケーションサーバーを思考しても構いません。

16.3. 設定

アプリケーションに Email サポートを含ませるには、 WEB-INF/lib ディレクトリ内に jboss-seam-mail.jar を含ませます。 JBoss AS を使用している場合は、 Seam の email サポートを使用するのに特別な設定は必要ありません。 これ以外は、 JavaMail API があるか、 JavaMail API が実装されているか (JBoss AS で使用される API と impl は lib/mail.jar として seam と共に配信される)、 Java Activation Framework のコピーがあるか (lib/activation.jar として seam と共に配信される) を確認する必要があります。

Seam Email モジュールにはビューテクノロジーとして Facelets を使用する必要があります。 ライブラリの今後のバージョンでは JSP の使用にも対応する可能性があります。 また、 seam-ui パッケージの使用も必要となります。

mailSession コンポーネントは JavaMail を使って「実際の」 SMTP サーバーと交信します。

16.3.1. mailSession

JEE 環境で作業している、 または Seam 設定の Session を使用できる場合、 JavaMail Session が JNDI ルックアップから使用できる可能性があります。

mailSession コンポーネントのプロパティについての詳細は 項23.8. 「メール関連のコンポーネント」 で説明されています。

16.3.1.1. JBoss AS の JNDI ルックアップ

JBossAS deploy/mail-service.xml は JNDI にバインドする JavaMail セッションを設定します。 デフォルトのサービス設定は使用しているネットワークに応じて変更が必要となります。 サービスについての詳細は http://wiki.jboss.org/wiki/Wiki.jsp?page=JavaMail で説明されています。

<components xmlns="http://jboss.com/products/seam/components"
        xmlns:core="http://jboss.com/products/seam/core"
        xmlns:mail="http://jboss.com/products/seam/mail">
        
    <mail:mail-session session-jndi-name="java:/Mail"/>
    
</components>

ここで Seam に JNDI から java:/Mail にバウンドするメールセッションを取得するよう指示します。

16.3.1.2. Seam 設定のセション

メールセッションは components.xml から設定することができます。 ここで Seam に smtp サーバーとして smtp.example.com を使用するよう指示します。

<components xmlns="http://jboss.com/products/seam/components"
        xmlns:core="http://jboss.com/products/seam/core"
        xmlns:mail="http://jboss.com/products/seam/mail">
        
    <mail:mail-session host="smtp.example.com"/>
    
</components>

16.4. タグ

Email は http://jboss.com/products/seam/mail 名前スペース内でタグを使って生成されます。 ドキュメントには常にメッセージのルートに message タグがあるはずです。 メッセージタグは Seam による email 生成の準備を行います。

facelets の標準テンプレート作成タグは通常通りに使用できます。 ボディ内では、 外部リソース (スタイルシート、 javascript) にアクセスを必要としない JSF タグを使用できます。

<m:message>

メールメッセージのルートタグ

  • importance — low (低)、 normal (中)、 high (高) のいずれかになります。 デフォルトでは normal (中) になり、 これがメールメッセージの重要度を設定します。

  • precedence — メッセージの優先度を設定します (例、 bulk)。

  • requestReadReceipt — デフォルトでは false です。 設定されると、 受信通知のリクエストが追加されて From: のアドレスに受信通知が送信されます。

  • urlBase — 設定されると、 その値が requestContextPath の先頭に追加され email 内で <h:graphicImage> などのコンポーネントを使用できるようになります。

<m:from>

email の From: アドレスを設定します。 1 email に対して次のうちいずれか 1 つのみ使用できます。

  • name — email の送信元の名前です。

  • address — email の送信元の email アドレスです。

<m:replyTo>

email の Reply-to: アドレスを設定します。 1 email に対して次のうちいずれか 1 つのみ使用できます。

  • address — email の送信元の email アドレスです。

<m:to>

email に受信者を追加します。 受信者が複数の場合は複数の <m:to> タグを使用します。 このタグは <ui:repeat>. などの繰り返しタグ内に配置しても安全です。

  • name — 受信者の名前です。

  • address — 受信者の email アドレスです。

<m:cc>

email に CC の受信者を追加します。 CC が複数の場合は複数の <m:cc> タグを使用します。 このタグは <ui:repeat> などの繰り返しタグ内に配置しても安全です。

  • name — 受信者の名前です。

  • address — 受信者の email アドレスです。

<m:bcc>

email に BCC の受信者を追加します。 BCC が複数の場合は複数の <m:bcc> タグを使用します。 このタグは <ui:repeat> などの繰り返しタグ内に配置しても安全です。

  • name — 受信者の名前です。

  • address — 受信者の email アドレスです。

<m:header>

email にヘッダを追加します (例、 X-Sent-From: JBoss Seam)。

  • name — 追加するヘッダ名です (例、 X-Sent-From)。

  • value — 追加するヘッダ値です (例、 JBoss Seam)。

<m:attachment>

email に添付を追加します。

  • value — 添付するファイルです。

    • StringString はクラスパス内のファイルへのパスとして解釈されます。

    • java.io.File — EL 式が File オブジェクトを参照できます。

    • java.net.URL — EL 式が URL オブジェクトを参照できます。

    • java.io.InputStream — EL 式が InputStream を参照できます。 この場合、 fileNamecontentType の両方が指定されなければなりません。

    • byte[] — EL 式が byte[] を参照できます。 この場合、 fileNamecontentType の両方が指定されなければなりません。

    値属性が省略される場合、

    • このタグが <p:document> タグを含んでいるなら、 記述されるドキュメントが生成されて email に添付されます。 fileName が指定されなければなりません。

    • このタグがその他の JSF タグをを含んでいると、 HTML ドキュメントがそこから生成されて email に添付されます。 fileName が指定されていなければなりません。

  • fileName — 添付するファイルに使用するファイル名を指定します。

  • contentType — 添付されるファイルの MIME タイプを指定します。

<m:subject>

email の件名を設定します。

<m:body>

email のボディを設定します。 HTML email が生成される場合に html に対応していない受け取り側に提供する代替テキストを含む alternative ファセットをサポートしています。

  • typeplain に設定するとプレーンテキストの email が生成されます。 これ以外は HTML email が生成されます。

第17章 非同期性とメッセージング

Seamでは、Webリクエストに対して非同期に処理を行うことが非常に簡単にできます。 Java EEでの非同期性といえば、多くの人はJMSを思い浮かべるでしょう。 これは確かにSeamでこの問題にアプローチするひとつの方法です。そして厳密で明確なサービス品質の要件があるならば、これは適切な方法です。 Seamでは、Seamコンポーネントを使って簡単にJMSメッセージを送受信できます。

しかし多くのユースケースではJMSは過剰です。 Seamはシンプルな非同期メソッドとイベント機関をEJB 3.0タイマーサービス上にレイヤ化します。

17.1. 非同期性

非同期イベントとメソッド呼び出しは、コンテナのEJBタイマーサービスと同程度のサービス品質を期待できます。 タイマーサービスに親しんでいなくても心配は不要です。Seamで非同期メソッドを使うなら、タイマーサービスを直接触る必要はありません。

非同期メソッドとイベントを使うには、components.xmlに以下の行を追加する必要があります:

<core:dispatcher/>

EJB 3.0をサポートしない環境では、この機能は使えないことに注意して下さい。

17.1.1. 非同期メソッド

最も単純なかたちでは、非同期呼び出しは、メソッド呼び出しを呼び出し側に対して非同期に (異なるスレッドで) 処理させるだけです。 我々は通常、クライアントに即座にレスポンスを返し、重い仕事をバックグラウンドで処理させたい場合に、非同期呼び出しを使います。 このパターンは、クライアントが処理結果をサーバへ自動的にポーリングできるような、AJAXを使用するアプリケーションでとても効果的です。

EJBコンポーネントでは、ローカルインターフェースをアノテートしてメソッドが非同期に処理されるよう指定します。

@Local
public interface PaymentHandler
{
    @Asynchronous
    public void processPayment(Payment payment);
}

(JavaBean コンポーネントでは、 望むならコンポーネントの実装クラスをアノテートすることができます)

非同期性の使用はbeanクラスに透過的です。

@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    public void processPayment(Payment payment)
    {
        //do some work!
    }
}

そしてクライアントに対しても透過的です。

@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String pay()
    {
        paymentHandler.processPayment( new Payment(bill) );
        return "success";
    }
}

非同期メソッドは完全に新規のイベントコンテキストで処理され、 呼び出し側のセッションまたは対話コンテキストの状態にはアクセスできません。 しかしビジネスプロセスコンテキストは伝播されます

非同期メソッド呼び出しは@Duration@Expiration@IntervalDurationアノテーションを使って、 後の実行のためにスケジューリングできます。

@Local
public interface PaymentHandler
{
    @Asynchronous
    public void processScheduledPayment(Payment payment, @Expiration Date date);

    @Asynchronous
    public void processRecurringPayment(Payment payment, @Expiration Date date, @IntervalDuration Long interval)'
}
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
        return "success";
    }

    public String scheduleRecurringPayment()
    {
        paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(), ONE_MONTH );
        return "success";
    }
}

クライアントとサーバの両者は、呼び出しに関連付けられたTimerオブジェクトに アクセスできます。

@Local
public interface PaymentHandler
{
    @Asynchronous
    public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}
@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
    @In Timer timer;
    
    public Timer processScheduledPayment(Payment payment, @Expiration Date date)
    {
        //do some work!
        
        return timer; //note that return value is completely ignored
    }

}
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
    @In(create=true) PaymentHandler paymentHandler;
    @In Bill bill;
    
    public String schedulePayment()
    {
        Timer timer = paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
        return "success";
    }
}

非同期メソッドは呼び出し側に他のどんな値も返すことができません。

17.1.2. 非同期イベント

コンポーネント駆動イベントもまた、非同期になることができます。非同期処理のためのイベントを起こすには、 単にEventsクラスのraiseAsynchronousEvent()メソッドを呼ぶだけです。 時間指定イベントをスケジュールするには、raiseTimedEvent()メソッドのひとつを呼びます。 コンポーネントは通常の方法で非同期イベントを観察できますが、ビジネスプロセスコンテキストだけが 非同期スレッドに伝播されることを覚えておいてください。

17.2. Seam でのメッセージング

Seam は Seam コンポーネントの JMS メッセージの送受信を容易にしています。

17.2.1. 設定

JMS メッセージ送信のための Seam インフラストラクチャの設定として、 Seam に対しメッセージ送信する Topic または、Queue を指示する必要があります。 また、 QueueConnectionFactory あるいは、 TopicConnectionFactory の場所を Seam に指示する必要もあります。

Seam はデフォルトで、JBossMQ 用の通常のコネクションファクトリ UIL2ConnectionFactory を使用します。 もし、その他の JMS プロバイダを使用する場合には、 seam.propertiesweb.xml あるいは、components.xml 中の queueConnection.queueConnectionFactoryJndiNametopicConnection.topicConnectionFactoryJndiName の一方あるいは両方を設定する必要があります。

Seam 管理の TopicPublisher または、 QueueSender をインストールするために、 components.xml 中に topic または queue を記入する必要もあります。

<jms:managed-topic-publisher name="stockTickerPublisher" auto-create="true" topic-jndi-name="topic/stockTickerTopic"/>

<jms:managed-queue-sender name="paymentQueueSender" auto-create="true" queue-jndi-name="queue/paymentQueue"/>

17.2.2. メッセージ送信

JMS TopicPublisher や、 TopicSession をコンポーネントにインジェクトすることが可能です。

@In 
private TopicPublisher stockTickerPublisher;   
@In 
private TopicSession topicSession;

public void publish(StockPrice price) {
      try
      {
         topicPublisher.publish( topicSession.createObjectMessage(price) );
      } 
      catch (Exception ex)
      {
         throw new RuntimeException(ex);
      } 
}

あるいは、Queue 連携することも可能です。

@In
private QueueSender paymentQueueSender;   
@In
private QueueSession queueSession;

public void publish(Payment payment) {
      try
      {
         paymentQueueSender.send( queueSession.createObjectMessage(payment) );
      } 
      catch (Exception ex)
      {
         throw new RuntimeException(ex);
      } 
}

17.2.3. メッセージ駆動型 Bean を使用したメッセージの受信

EJB3 メッセージ駆動型 Bean を利用してメッセージ処理が可能です。 メッセージ駆動型 Bean は Seam コンポーネントとすることも可能です。 この場合、イベントまたはアプリケーションスコープの Seam コンポーネントのインジェクトが可能です。

17.2.4. クライアントでのメッセージの受信

Seam Remoting は、クライアント側の JavaScript から JMS Topic に購読を可能にします。 これは次の章に記述します。

第18章 キャッシュ

ほとんどの場合、 企業アプリケーションにおける主なボトルネックはデータベースです。 そして、 データベースは実行環境の中ではもっともスケーラブルしにくい部分です。 PHP や Ruby の人々は、 いわゆる "shared nothing" アーキテクチャにすれば拡張があると言うでしょう。 確かにそれは事実かもしれませんが、 クラスタ構成の複数ノード間で何もリソースを共有しないで設計できるアプリケーションなど、 あまり見たことがありません。 愚かな彼らが本当に考えているのは「データベース以外は」 "shared nothing" というアーキテクチャでしょう。 マルチユーザ用アプリケーションを拡張しにくくしているのは、 もちろんデータベース共有です。 したがって、このアーキテクチャに高い拡張性があると主張するのは無理がありますが、 彼らは数々のアプリケーションにたくさんの時間を割いています。

データベースの共有をなるべく少なくできる方法があれば、すべて実行する価値があります。

そこでキャッシュの登場です。しかも1種類ではありません。 Seamアプリケーションを正しく設計すれば、何層にもわたる豊富なキャッシング戦略をアプリケーションのすべての層で利用することができるのです。

  • データベースは当然ながら独自のキャッシュを持っています。 このことは大変重要ですが、アプリケーションのキャッシュのような拡張性はありません。

  • ORMソリューション (Hibernateやその他のJPA実装など) はデータベースからデータを2次キャッシュに置きます。 これはとても強力な機能なのですが、間違った使われ方をされがちです。 トランザクションのキャッシュ・データをクラスタ環境の全ノードで一貫性を持たせ、データベースとも同期させると、非常に重い処理になります。 複数ユーザで共有され、更新がまれなデータには良いかも知れません。 一般的なステートレスなアーキテクチャでは、2次キャッシュに対話状態をキャッシュしようとしますが、これは良いことではありません。 特にSeamの場合は、誤りです。

  • Seamの対話コンテキストは、対話状態のキャッシュです。 対話コンテキストに保存したコンポーネントは、ユーザのインタラクションに関連した状態をキャッシュし、保持します。

  • 特に、Seamが管理する永続コンテキスト (あるいは対話スコープのステートフルセッションビーンに関連付けられたEJBコンテナ管理の拡張永続コンテキスト) は、 現在の対話に読み込まれたデータのキャッシュとして振舞います。 このキャッシュのヒット率は通常はとても高くなります! クラスタ環境では、Seamが管理する永続コンテキストはSeamによってレプリケーションが最適化され、データベースのトランザクションの一貫性を気にする必要はありません (楽観的ロックで充分です) 。 ひとつの永続コンテキストに何千ものオブジェクトを読み込まない限り、このキャッシュの効率についてあまり気にする必要はありません。

  • トランザクションに関連しない状態をSeamのアプリケーション・コンテキストにキャッシュすることもできます。 アプリケーション・コンテキストに保持された状態は、クラスタ内の他のノードにはもちろん見えません。

  • トランザクションの状態は、JBossCacheを使ったSeamのpojoCacheコンポーネントにキャッシュできます。 JBossCacheをクラスタで使用していれば、この状態は他のノードにも見えます。

  • 最後に、レンダリングされたJSFページの断片をキャッシュすることができます。 ORMソリューションの2次キャッシュと違い、データが変更されても自動的に無効になることはないので、 明示的に無効化するアプリケーション・コードを書くか、適切な有効期限ポリシーを設定する必要があります。

2次キャッシュは非常に混み入った概念ですので、詳細についてはお使いのORMソリューションの文書を参照してください。 この章では、pojoCacheコンポーネントや、<s:cache>によるページ断片のキャッシュなど、JBossCacheを直接利用する方法について説明します。

18.1. SeamでJBossCacheを利用する

ビルトインの pojoCache コンポーネントは org.jboss.cache.aop.PojoCache のインスタンスを管理します。 不変の Java オブジェクトであれば安全にキャッシュに置くことができ、 オブジェクトはクラスタ内でレプリケーションされます (レプリケーションが有効な場合)。 変更の可能性があるオブジェクトをキャッシュに持ちたい場合は、 JBossCache のバイトコード・プロセッサを実行し、 オブジェクトの変更が自動的に検知され、 レプリケーションされるようにする必要があります。

pojoCacheを使うには、クラスパスにJBossCacheのjarを置き、treecache.xmlというリソースに適切なcacheの設定を記述するだけです。 JBossCacheには恐ろしく厄介で紛らわしい設定がたくさんあるので、ここでは説明しません。 詳細はJBossCacheの文書を参照してください。

SeamにEARをデプロイする場合は、JBossCacheのjarと設定ファイルをEARに直接含めることをお勧めします。 application.xmlにjarを記述することを忘れないでください。

次のように、Seamコンポーネントにキャッシュをインジェクトします。

@Name("chatroom")
public class Chatroom {
    @In PojoCache pojoCache;
    
    public void join(String username) {
      try
      {
         Set<String> userList = (Set<String>) pojoCache.get("chatroom", "userList");
         if (userList==null) 
         {
            userList = new HashSet<String>();
            pojoCache.put("chatroom", "userList", userList);
         }
         userList.put(username);
      }
      catch (CacheException ce)
      {
         throw new RuntimeException(ce);
      }
    }
}

JBossCacheを複数設定する場合は、components.xmlを使用してください。

<core:pojo-cache name="myCache" cfg-resource-name="myown/cache.xml"/>

18.2. ページ断片のキャッシュ

SeamのJBossCache の利用でもっとも興味深いのは、 JSFにおけるページ断片のキャッシュ問題を解決する、 <s:cache> タグです。 <s:cache> は内部的に pojoCache を使うので、 使用する場合は前述の手順を行ってください (EAR に jar を入れる、 やっかいな設定を切り抜ける、などの手順です。)

<s:cache>は、あまり変更されないレンタリング・コンテンツに使用してください。 たとえば、最新のblogエントリを表示するblogのウェルカムページです。

<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
   <h:dataTable value="#{blog.recentEntries}" var="blogEntry">
      <h:column>
         <h3>#{blogEntry.title}</h3>
         <div>
            <s:formattedText value="#{blogEntry.body}"/>
         </div>
      </h:column>
   </h:dataTable>
</s:cache>

key を指定することによって、 各ページ断片のキャッシュバージョンを複数持つことができます。 この例では、 ひとつの blog に対してひとつのキャッシュバージョンが存在します。 region には、 すべてのバージョンを保存する JBossCache のノードを指定します。 異なるノードは異なる有効期限ポリシーを持つ場合があります。 (前述の厄介な設定で指定できます。)

そして、 <s:cache> の大きな問題は、 対象のデータがいつ変更されるか (たとえば、 新しい blog がいつ投稿されるか) を知り得ないということです。 つまり、 キャッシュされた断片は、 明示的にキャッシュから排除する必要があります。

public void post() {
    ...
    entityManager.persist(blogEntry);
    pojoCache.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}

あるいは、変更を即座にユーザに見せる必要がないのであれば、JBossCacheノードの有効期限を短く設定しても良いでしょう。

第19章 Remoting

Seam は、 Web ページから AJAX (Asynchronous Javascript and XML) を使用してコンポーネントにリモートアクセスする便利な方法を提供します。 この機能を実現するフレームワークでは、 開発時に労力がかからないようになっています - コンポーネントに必要なものは、 AJAX を通じてアクセス可能とするための単純なアノテーションだけです。 この章では、 AJAX 可能な Web ページを作るために必要なステップについて述べ、 そしてSeam Remoting フレームワークの機能についても詳しく説明します。

19.1. 設定

Remoting の機能を使用するには、 まず web.xml ファイル内で Seam Resource サーブレットを設定する必要があります。

          
  <servlet>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Seam Resource Servlet</servlet-name>
    <url-pattern>/seam/resource/*</url-pattern>
  </servlet-mapping>
        
        

次のステップは、 Web ページに必要な JavaScript をインポートすることです。 インポートすべきスクリプトが最低二つあります。 最初の一つは、 Remoting の機能を有効にするクライアントサイドフレームワークの全てのコードを含みます。

          
  <script type="text/javascript" src="seam/resource/remoting/resource/remote.js"></script>
            
        

二つ目のスクリプトは、 呼び出したいコンポーネントに対するスタブと型定義を含みます。 それはコンポーネントのローカルインターフェースを基にして動的に生成され、 インターフェースのリモートメソッドを呼び出すのに使用される全てのクラスに対する型定義を内包しています。 スクリプトの名前はコンポーネントの名前が反映されます。 例えば、 @Name("customerAction") というアノテーション付きのステートレスセッション Bean を持つなら、 スクリプトタグは以下のようになります。

          
  <script type="text/javascript" src="seam/resource/remoting/interface.js?customerAction"></script>
        
        

同じページから一つ以上のコンポーネントにアクセスしたい場合は、 スクリプトタグのパラメータとしてそれらを全て含めます。

          
  <script type="text/javascript" src="seam/resource/remoting/interface.js?customerAction&accountAction"></script>
        
        

19.2. "Seam" オブジェクト

コンポーネントとのクライアントサイドのインタラクションは、 すべて Seam Javascript オブジェクト経由で行われます。 このオブジェクトは remote.js に定義され、 コンポーネントに対する非同期呼び出しにそれを使用します。 それは、2 つの機能に区分されます。 コンポーネントと連携するメソッドを含む Seam.Component そして、リモートリクエストを実行するメソッドを含む Seam.Remoting です。 このオブジェクトに精通する一番容易な方法は、 簡単なサンプルから始めることです。

19.2.1. Hello World サンプル

Seam オブジェクトがどのように動作するかを見るために、 簡単なサンプルを通じて一歩を踏み出してみましょう。 まず最初に、helloAction と呼ばれる新しい Seam コンポーネントを作成しましょう。

          
  @Stateless
  @Name("helloAction")
  public class HelloAction implements HelloLocal {
    public String sayHello(String name) {
      return "Hello, " + name;
    }
  }
          
        

新しいコンポーネントのために、ローカルインタフェースも生成する必要があります。 @WebRemote アノテーションに特に注意してください。 Remoting を通じてのメソッドへのアクセスを可能とするために必要です。

          
  @Local
  public interface HelloLocal {
    @WebRemote
    public String sayHello(String name);
  }
          
        

書く必要があるサーバサイドのコードはこれだけです。 それでは、WEB ページのために - 新しいページを作成して、 以下のスクリプトをインポートしましょう。

          
  <script type="text/javascript" src="seam/resource/remoting/resource/remote.js"></script>
  <script type="text/javascript" src="seam/resource/remoting/interface.js?helloAction"></script>
          
        

完全にインタラクティブなユーザエクスペリエンスとするために、ページにボタンを付けましょう。

          
  <button onclick="javascript:sayHello()">Say Hello</button>
          
        

クリックされたとき、実際にボタンに何かを行わせるためには、もう少しスクリプトを追加する必要があります。

          
  <script type="text/javascript">
    //<![CDATA[

    function sayHello() {
      var name = prompt("What is your name?");
      Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
    }

    function sayHelloCallback(result) {
      alert(result);
    }

    // ]]>
  </script>
          
        

作業完了です! アプリケーションをデプロイして、ページをブラウズしましょう。 ボタンをクリックして、プロンプトが出たら名前を入力しましょう。 呼び出しの成功を確認するための hello メッセージが、メッセージボックスに表示されます。 少し時間を節約したいのであれば、 Seam の /examples/remoting/helloworld ディレクトリにこの Hello World サンプルのすべてのソースコードがあります。

ところで、このスクリプトのコードは何をするのでしょうか。 もっと細かく分解してみましょう。手始めに、2 つのメソッドを実装した Javascript コードから見ていきましょう。 最初のメソッドはユーザに対して名前を入力するよう促し、リモートリクエストを行うという責務を持ちます。 以下の行から見てみましょう。

  Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
        

この行の最初の部分 Seam.Component.getInstance("helloAction") は、 helloAction コンポーネントのプロキシ、あるいは"スタブ"を返します。 このスタブに対してコンポーネントのメソッド呼び出しが可能です。 それは、まさにこの行の残りの部分で発生します: sayHello(name, sayHelloCallback);

コード行全体で行っていることは、コンポーネントのsayHelloメソッドの呼び出しと、 パラメータとしてnameを渡すことです。 2 番目のパラメータsayHelloCallbackは、 このコンポーネントの sayHelloメソッドのパラメータではありません。 その代わり、Seam Remoting フレームワークにリクエストへのレスポンスを受けたら、 それを sayHelloCallbackメソッドに渡すべきことを指示します。 このコールバックパラメータは完全にオプションです。 返り値 void のメソッドを呼び出す場合、 あるいは結果を気にしない場合は、 遠慮なくそのままにしてください。

sayHelloCallbackメソッドが、リモートリクエストに対するレスポンスを受信した場合、 メソッド呼び出しの結果を表示するアラートメッセージが現れます。

19.2.2. Seam.Component

Seam.Component Javascript オブジェクトは、 Seam コンポーネントと連携する多くのクライアントメソッドを提供します。 主な 2 つのメソッド、newInstance()getInstance()は、 以降の章にも記述されていますが、 これらの主な違いは、newInstance() は、いつもコンポーネントタイプの新しいインスタンスを生成し、そして、getInstance()は、シングルトンのインスタンスを返すことです。

19.2.2.1. Seam.Component.newInstance()

新しいエンティティ、あるいは JavaBean コンポーネントインスタンスを生成するためにこのメソッドを使用します。 このメソッドにより返されるオブジェクトは、 サーバサイドの対応するものと同じ getter/setter メソッドを持つか、 あるいは、代替として、お望みならば、そのフィールドに直接アクセスが可能です。 例として、以下の Seam エンティティコンポーネントをご覧ください。

  @Name("customer")
  @Entity
  public class Customer implements Serializable
  {
    private Integer customerId;
    private String firstName;
    private String lastName;
    
    @Column public Integer getCustomerId() { 
      return customerId; 
    }
    
    public void setCustomerId(Integer customerId} { 
      this.customerId = customerId; 
    }
    
    @Column public String getFirstName() { 
      return firstName; 
    }
    
    public void setFirstName(String firstName) {
      this.firstName = firstName; 
    }
    
    @Column public String getLastName () {
      return lastName;
    }
    
    public void setLastName(String lastName) {
      this.lastName = lastName;
    }
  }
          

クライアントサイド Customer を生成するために、以下のコードを記述します。

  var customer = Seam.Component.newInstance("customer");
          

そして、ここからは customer オブジェクトのフィールドの設定が可能です。

  customer.setFirstName("John");
  // Or you can set the fields directly
  customer.lastName = "Smith";
          

19.2.2.2. Seam.Component.getInstance()

getInstance()メソッドは、 Seam セッション Bean コンポーネントの、スタブへの参照を取得するために使用されます。 それは、コンポーネントに対してリモートのメソッド実行に使用可能です。 このメソッドは、特定のコンポーネントのシングルトンを返します。 その結果、続けて同じコンポーネント名で 2 回呼び出すと、同じコンポーネントインスタンスが返されます。

前記のサンプルから続けて、 新しいcustomerを生成、保存しようとする場合、customerActionコンポーネントのsaveCustomer()メソッドにそれを渡します。

  Seam.Component.getInstance("customerAction").saveCustomer(customer);          
          

19.2.2.3. Seam.Component.getComponentName()

それがコンポーネントの場合、 オブジェクトをこのメソッドに渡すとコンポーネント名を返します。 また、コンポーネントでない場合、null を返します。

  if (Seam.Component.getComponentName (instance) == "customer")
    alert("Customer");
  else if (Seam.Component.getComponentName (instance) == "staff")
    alert("Staff member");          
          

19.2.3. Seam.Remoting

Seam Remoting の大部分のクライアントサイドの機能は、Seam.Remotingオブジェクトに含まれます。 メソッドを直接呼ぶ必要はほとんどないとはいえ、 言及する価値のある重要なものがあります。

19.2.3.1. Seam.Remoting.createType()

アプリケーションが Seam コンポーネントではない JavaBean を含むか、あるいは、使う場合、 パラメータをコンポーネントメソッドに渡すために、クライアントサイドでこれらのタイプを作成する必要があるかもしれません。 必要なタイプのインスタンスを作成するために、createType()メソッドを使用してください。 パラメータとして、完全修飾の Java クラスを渡してください。

  var widget = Seam.Remoting.createType("com.acme.widgets.MyWidget");          
          

19.2.3.2. Seam.Remoting.getTypeName()

このメソッドは、コンポーネントでない型用であることを別にすれば、Seam.Component.getComponentName()と等価です。 オブジェクトインスタンスにタイプの名前を返します。 また、タイプが既知でない場合、null を返します。 この名前は、タイプの Java クラス完全修飾名です。

19.3. クライアント インターフェース

上記、設定の章では、 インタフェース、あるいは、コンポーネントのための "スタブ" は、seam/remoting/interface.jsを通じてページにインポートされます。

        
  <script type="text/javascript" src="seam/resource/remoting/interface.js?customerAction"></script>
        
      

ページにこのスクリプトをインクルードすることにより、 コンポーネントのためのインタフェース定義に加えて、コンポーネントのメソッドを実行するために必要なその他のコンポーネントとタイプが生成され、 Remoting フレームワークで使用可能になります。

生成されうるクライアントスタブには、二つのタイプがあります。"実行可能" スタブ、そして、"タイプ" スタブです。 実行可能スタブは、振る舞いを持ち、セッション Bean コンポーネントに対するメソッドを実行するために使用されます。 一方、タイプスタブは、状態を保持し、パラメータあるいは結果として返り値として送付可能なタイプを表します。

生成されたクライアントスタブのタイプは、Seamコンポーネントのタイプに依存します。コンポーネントがセッション Bean の場合は実行可能スタブが生成され、 それ以外のエンティティ、またはJavaBeanの場合にはタイプスタブが生成されます。 この規則には 1 つの例外があります。 コンポーネントが JavaBean (つまり、セッション Bean や エンティティ Bean でない場合) で、かつそのメソッドに@WebRemote アノテーションが付けられていた場合は、タイプスタブの代わりに実行可能スタブが生成されます。 これは、セッション Bean にアクセスできない非EJB 環境で、JavaBean コンポーネントのメソッドを呼び出す Remoting を使用可能にします。

19.4. コンテキスト

Seam リモートコンテキストは、 Remoting のリクエスト / レスポンスサイクルの一部として送受信される追加情報を含んでいます。 現段階では対話 ID だけしか含んでいませんが、将来拡張される可能性があります。

19.4.1. 対話 ID の設定と読み込み

対話スコープでリモート呼び出しをしようとする場合、 Seam Remotingコンテキスト内にある対話ID の読み込みと設定が行える必要があります。リモートリクエストの後に対話 ID を読み込むためには、Seam.Remoting.getContext().getConversationId()を呼び出します。 リクエストの前に対話ID を設定するためには、Seam.Remoting.getContext().setConversationId()を呼び出します。

対話ID が明示的に Seam.Remoting.getContext().setConversationId()で設定されない場合、 リモート呼び出しによって返される最初の有効な対話ID が自動的に割り当てられます。 ページ内で複数の対話ID を使用する場合は、それぞれの呼び出しの前に対話IDを明示的に設定する必要があるかもしれません。1つの対話だけを使用する場合は、 特別なことをする必要はありません。

19.5. バッチリクエスト

Seam Remoting は、複数のコンポーネント呼び出しが 1 つのリクエスト内で実行されることを可能にします。 ネットワークトラフィックを減少することが適切であれば、 どこでもこの特徴を使用することを推奨します。

Seam.Remoting.startBatch()メソッド は、 新たなバッチを起動します。 バッチ起動後に実行されたコンポーネント呼び出しは、 即座に送られるのではなく、キューイングされます。 必要とされるすべてのコンポーネント呼び出しがバッチに追加されたとき、Seam.Remoting.executeBatch()メソッドは、 サーバにキューイングされた呼び出しすべてを含む 1 つのリクエストを送信するでしょう。 そして、そこで順番に実行されます。 呼び出しが実行された後、 すべての返り値を含む 1 つのレスポンスは、 クライアントに返され、コールバック機能が (もし、設定されていれば) 実行と同じ順番で起動されます。

startBatch()メソッドを通して新たなバッチを起動したけれど、それを送りたくないと決めたなら、Seam.Remoting.cancelBatch()メソッドは、キューに溜まった全ての呼び出しを破棄して、バッチモードを終了します。

バッチが利用されているサンプルを見るには、/examples/remoting/chatroomを参照ください。

19.6. データタイプの取り扱い

19.6.1. プリミティブ型 / 基本タイプ

この章は、基本データタイプのサポートについて述べています。 サーバサイドではこれらの値は、一般的にプリミティブタイプ、あるいは、対応するラッパクラスと互換性があります。

19.6.1.1. String 型

String パラメータ値を設定する場合は、 単純に Javascript String オブジェクトを使用してください。

19.6.1.2. Number 型

Java でサポートされているすべての数値タイプはサポートされています。 クライアントサイドでは、 数値は常に String 表現としてシリアライズされています。 そして、サーバサイドでは、これらは、正しい目的のタイプに変換されます。 プリミティブ、または、ラッパタイプへの変換は、 Byte、Double、 Float、Integer、Long、 そして、Short タイプをサポートします。

19.6.1.3. Boolean 型

Boolean は、クライアントサイドでは Javascriptの Boolean 値で表現され、サーバサイドでは Java boolean で表現されます。

19.6.2. JavaBeans

一般的に、これらは、Seam エンティティ、JavaBean コンポーネント、または、non-component クラスです。 オブジェクトの新しいインスタンスを生成するためには、適切なメソッドを使用してください。 Seam コンポーネントにはSeam.Component.newInstance()、 また、その他のものにはSeam.Remoting.createType()を使用してください。

パラメータが、このセクションの別の場所で記述されたその他の有効なタイプの 1 つではない場合、 これら 2 つのメソッドのどちらかによって生成されるオブジェクトだけがパラメータ値として使用されるべきであることに気づくことは重要です。 いくつかの状況では、 以下のように厳密にパラメータタイプを決定できないコンポーネントメソッドがあるかもしれません。

  @Name("myAction")
  public class MyAction implements MyActionLocal {
    public void doSomethingWithObject(Object obj) {
      // code
    }
  }
        

この場合、 myWidget コンポーネントのインスタンスを渡したいところですが、 myAction のインターフェースはそのいずれのメソッドからも直接参照されないため myWidget を含みません。 これを回避するには、 MyWidget を明示的にインポートする必要があります。

                  
  <script type="text/javascript" src="seam/resource/remoting/interface.js?myAction&myWidget"></script>
          
        

これにより myWidget オブジェクトが Seam.Component.newInstance("myWidget") で作成されるようになり、 これが myAction.doSomethingWithObject() に渡されます。

19.6.3. 日付と時刻

日付の値は、 ミリ秒単位まで正確な String 表示に連続化されます。 クライアント側では Javascript Date オブジェクトを使って日付値と動作させます。 サーバー側では java.util.Date を使用します (または java.sql.Datejava.sql.Timestamp などの下位クラス)。

19.6.4. Enums

クライアント側では、 enums は Strings と同様に扱われます。 enums パラメータの値を設定する場合は単純に enum の String 表現を使います。 次のコンポーネントを例として参照してください。

  @Name("paintAction")
  public class paintAction implements paintLocal {
    public enum Color {red, green, blue, yellow, orange, purple};

    public void paint(Color color) {
      // code
    }    
  }            
        

paint() メソッドを red の色を使って呼び出すには、 String のままでパラメータ値を渡します。

  Seam.Component.getInstance("paintAction").paint("red");
        

逆も真になります。 つまり、 コンポーネントメソッドが enum パラメータを返す場合 (または返されるオブジェクトグラフのどこかに enum フィールドを含む場合)、 クライアント側では String として表示されます。

19.6.5. 集合

19.6.5.1. Bag

Bag はアレイ、 集合、 一覧、 セットなどすべての集合タイプを対象とし (ただし Map は除く、 これについては次のセクションを参照)、 Javascript アレイとしてクライアント側で実装されます。 パラメータとしてこれらのタイプのいずれかを受け取るコンポーネントメソッドを呼び出す場合、 使用するパラメータは Javascript アレイにします。 コンポーネントメソッドがこれらのタイプのいずれかを返す場合は、 返り値も Javascript アレイになります。 Remoting フレームワークは、 サーバー側で bag をコンポーネントメソッドコールに対して適切なタイプに変換することが可能です。

19.6.5.2. Map

Javascript 内では Map に対するネイティブのサポートはないため、 シンプルな Map 実装が Seam Remoting フレームワークで提供されます。 リモートコールに対するパラメータとして使用できる Map を作成するには、 新たに Seam.Remoting.Map オブジェクトを作成します。

  var map = new Seam.Remoting.Map();          
          

この Javascript 実装では Map と動作することを目的とした基本的なメソッド、 size()isEmpty()keySet()values()get(key)put(key, value)remove(key)contains(key) を提供します。 それぞれのメソッドは Java のそれと同等になります。 メソッドが keySet() 及び values() などの 1 集合を返すと、 そのキーまたは値オブジェクトを含む Javascript Array オブジェクトが返されます。

19.7. デバッグ機能

バグの追跡を支援する目的で、 ポップアップウィンドウ内でクライアントとサーバー間を行ったり来たりするすべてのパケットの内容を表示するデバッグモードを有効にすることができます。 デバッグモードを有効にするには、 次のいずれかを行います。 Javascript 内で setDebug() メソッドを実行する方法は次の通りです。

  Seam.Remoting.setDebug(true);      
      

components.xml を使って設定を行う方法は次のようになります。

<remoting:remoting debug="true"/>

デバッグ機能をオフにするには、 setDebug(false) を呼び出します。 毒のメッセージをデバッグログに書き込みたい場合は、 Seam.Remoting.log(message) を呼び出します。

19.8. メッセージをロードする

画面の上部右端にデフォルトで表示されるローディングメッセージは、 変更、 レンダリングのカスタマイズ、 完全にオフにするなどが可能です。

19.8.1. メッセージを変更する

デフォルトの "Please Wait..." というメッセージを別のメッセージに変更するには、 Seam.Remoting.loadingMessage の値を設定します。

  Seam.Remoting.loadingMessage = "Loading...";        
        

19.8.2. ローディングメッセージを隠す

ローディングメッセージを完全に表示させないようにするには、 displayLoadingMessage() および hideLoadingMessage() を何も行わない機能で上書きします。

  // don't display the loading indicator
  Seam.Remoting.displayLoadingMessage = function() {};
  Seam.Remoting.hideLoadingMessage = function() {};        
        

19.8.3. カスタムのローディングインジケーター

ローディングインジケーターを上書きしてアニメのアイコンの他、 好きなものを表示させることができます。 displayLoadingMessage()hideLoadingMessage() の各メッセージを独自の実装で上書きしてこれを行います。

  Seam.Remoting.displayLoadingMessage = function() {
    // Write code here to display the indicator
  };
  
  Seam.Remoting.hideLoadingMessage = function() {
    // Write code here to hide the indicator
  };
        

19.9. 返されるデータを制御する

リモートメソッドが実行されると、 その結果はクライアントに返される XML レスポンスに連続化されます。 このレスポンスは次にクライアントにより Javascript オブジェクトにアンマーシャルされます。 他のオブジェクトへの参照を含む複雑なタイプの場合 (Javabeans など)、 こした参照されるオブジェクトもすべてレスポンスの一部として連続化されます。 これらのオブジェクトは他のオブジェクトを参照することができ、 またこの他のオブジェクトはその他のオブジェクトを参照できるといった具合になります。 チェックしないままにしておくと、 このオブジェクト「グラフ」はオブジェクト間で存在する関係によっては非常に膨大なものになる可能性があります。 派生的な問題として (レスポンスが冗長となる問題とは別)、 クライアントに対して機密情報が公表されてしまうのを防ぎたい場合もあるかもしれません。

Seam Remoting は、 リモートメソッドの @WebRemote アノテーションの exclude フィールドを指定することでそのオブジェクトグラフを「制約する」シンプルな方法を提供しています。 このフィールドはドット (「.」) 表記を使って指定されるパスまたは複数のパスを含む String アレイを受け取ります。 リモートメソッドを呼び出すと、 これらのパスと一致する結果となるオブジェクトグラフ内のオブジェクトが連続化される結果パケットから除外されます。

すべての例で次の Widget クラスを使用します。

@Name("widget")
public class Widget
{
  private String value;
  private String secret;
  private Widget child;
  private Map<String,Widget> widgetMap;
  private List<Widget> widgetList;
  
  // getters and setters for all fields
}
      

19.9.1. 通常のフィールドを制約する

リモートメソッドが Widget のインスタンスを返すけれど secret フィールドには機密情報が含まれているため公表したくない場合、 次のように制約します。

  @WebRemote(exclude = {"secret"})
  public Widget getWidget();      
        

"secret" の値は返されるオブジェクトの secret フィールドを参照します。 ここで、 クライアントに対してこの特定フィールドが公開されても構わないと仮定します。 返される Widget には child フィールドがあり、 これも Widget になります。 代わりに childsecret 値を隠したい場合はどうしたらよいでしょうか。 ドット表記を使用して結果となるオブジェクトグラフ内のこのフィールドのパスを指定することができます。

  @WebRemote(exclude = {"child.secret"})
  public Widget getWidget();      
        

19.9.2. Map と 集合を制約する

オブジェクトグラフ内にオブジェクトが存在できるその他の場所は Map あるいはなんらかの集合の種類内になります (ListSetArray など)。 集合は簡単で、 その他のフィールドと同様に扱えます。 たとえば、 WidgetwidgetList フィールド内に他の Widget 一覧が含まれていて、 この一覧の Widgetsecret フィールドを制約している場合、 アノテーションは次のようになります。

  @WebRemote(exclude = {"widgetList.secret"})
  public Widget getWidget();      
        

Map のキーまたは値を制約する場合の表記は少し異なります。 Map のフィールド名の後ろに [key] を付け加えると Map のキーオブジェクト値を制約し、 [value] は値オブジェクトの値を制約します。 次の例では widgetMap フィールドに制約された secret フィールドを持たせる方法を示しています。

  @WebRemote(exclude = {"widgetMap[value].secret"})
  public Widget getWidget();      
        

19.9.3. 特定タイプのオブジェクトを制約する

最後に、結果となるオブジェクトグラフ内のどこに出現するかに関係なくオブジェクトタイプのフィールド制約に使用できる表記について説明します。 この表記はコンポーネント名 (オブジェクトが Seam コンポーネントである場合) または完全修飾クラス名 (オブジェクトが Seam コンポーネントではない場合のみ) のいずれかを使用し角括弧を使って表現されます。

  @WebRemote(exclude = {"[widget].secret"})
  public Widget getWidget();      
        

19.9.4. 制約同士を組み合わせる

制約同士はオブジェクトグラフ内で複数のパスからオブジェクトをフィルタするために組み合わせることもできます。

  @WebRemote(exclude = {"widgetList.secret", "widgetMap[value].secret"})
  public Widget getWidget();      
        

19.10. JMS メッセージング

Seam Remoting は JMS メッセージングに対して実験的にサポートを提供しています。 本セクションでは現在実装されている JMS サポートについて記載していますが、 今後、 変更される可能性があるので注意してください。 現在、 この機能を実稼働環境下で使用することは推奨されていません。

19.10.1. 設定

JMS トピックをサブスクライブする前に、 まず Seam Remoting でサブスクライブさせることができるトピック一覧を設定する必要があります。 seam.propertiesweb.xml または components.xmlorg.jboss.seam.remoting.messaging.subscriptionRegistry.allowedTopics 配下にあるトピックを一覧表示させます。

<remoting:remoting poll-timeout="5" poll-interval="1"/>

19.10.2. JMS Topic にサブスクライブする

次の例では JMS Topic へのサブスクライブ方法を示しています。

  function subscriptionCallback(message)
  {
    if (message instanceof Seam.Remoting.TextMessage)
      alert("Received message: " + message.getText ());
  }        
        
  Seam.Remoting.subscribe("topicName", subscriptionCallback);
        

Seam.Remoting.subscribe() メソッドは 2 つのパラメータを受け取ります。 1 つ目はサブスクライブする JMS Topic 名になり、 2 つ目はメッセージが受け取られると呼び出すコールバック機能になります。

サポートされているメッセージは 2 種類で、 テキストメッセージとオブジェクトメッセージです。 コールバック機能に渡されるメッセージタイプのテストを必要とする場合は、 instanceof 演算子を使ってメッセージが Seam.Remoting.TextMessage なのか Seam.Remoting.ObjectMessage であるのかをテストすることができます。 TextMessage はその text フィールドにテキスト値を含み (または代わりに getText() を呼び出す)、 ObjectMessage はその object フィールドにオブジェクト値を含みます (またはその getObject() メソッドを呼び出す)。

19.10.3. トピックのサブスクライブを中止する

トピックのサブスクライブを中止するには、 Seam.Remoting.unsubscribe() を呼び出してトピック名で渡します。

  Seam.Remoting.unsubscribe("topicName");        
        

19.10.4. ポーリングのプロセスを調整する

ポーリングの発生方法を制御するために変更できるパラメータが 2 つあります。 1 つ目は Seam.Remoting.pollInterval で、 新しいメッセージに対して後続ポールが発生する間隔を制御します。 秒単位で表現します、 デフォルト設定は 10 になります。

2 つ目のパラメータは Seam.Remoting.pollTimeout で、 このパラメータも秒単位で表現されます。 サーバーへのリクエストがタイムアウトして空白の応答を送信するまでの新しいメッセージを待機する長さを制御します。 デフォルトは 0秒で、 サーバーがポールされると配信できるメッセージがない場合は空白の応答が直ちに返されます。

pollTimeout 値を高く設定する場合は注意が必要です。 各リクエストがメッセージを待機する必要があるということは、 メッセージが受信されるまでまたはそのリクエストがタイムアウトするまでサーバースレッドが固定されるということになります。 こうしたリクエストが同時に多数発生すると、 大量のスレッドが固定される結果になります。

これらのオプションは components.xml 経由で設定することを推奨しますが、 必要に応じて Javascript で上書きすることができます。 次の例ではポーリングがかなりアグレッシブに発生するよう設定する方法を示しています。 これらのパラメータはご使用のアプリケーションに適切な値を設定してください。

components.xml:

<remoting:remoting poll-timeout="5" poll-interval="1"/>

JavaScript:

  // Only wait 1 second between receiving a poll response and sending the next poll request.
  Seam.Remoting.pollInterval = 1;
  
  // Wait up to 5 seconds on the server for new messages
  Seam.Remoting.pollTimeout = 5;   
        

第20章 Spring Framework 統合

Spring 統合モジュールにより、 SpringベースのプロジェクトをSeamに容易に移行できるようになるため、 対話のみならず Seam の各種の高度な永続コンテキスト管理など Seam の主要な機能を Springアプリケーションが 利用できるようになります。

Spring に対する Seam のサポートにより次のようなことが可能になります。

  • Seam コンポーネントインスタンスを Spring bean にインジェクトする

  • Spring bean を Seam コンポーネントにインジェクトする

  • Spring bean を Seam コンポーネントに変換する

  • Spring bean を Seam コンテキストに配置できるようにする

  • Seam コンポーネント で Spring Web アプリケーションを起動する

20.1. Seam コンポーネントを Spring bean にインジェクトする

Seam コンポーネントインスタンスの Spring bean へのインジェクションは、 <seam:instance/> 名前空間ハンドラを使用して行います。 Seam 名前空間ハンドラを有効にするには、 Seam 名前空間を Spring bean 定義ファイルに追加しなければなりません。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:seam="http://jboss.com/products/seam/spring-seam"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                        http://jboss.com/products/seam/spring-seam 
                        http://jboss.com/products/seam/spring-seam-1.2.xsd">

これで、 Seam コンポーネントはいれの Spring bean にもインジェクション可能となりました。

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty">
        <seam:instance name="someComponent"/>
    </property>
</bean>

コンポーネント名の代わりに EL 式が利用可能です。

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty">
        <seam:instance name="#{someExpression}"/>
    </property>
</bean>

Seam コンポーネントインスタンスは、 Spring bean id で Spring bean へのインジェクションができるようになります。

<seam:instance name="someComponent" id="someSeamComponentInstance"/>

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <property name="someProperty" ref="someSeamComponentInstance">
</bean>

警告!

Seamは複数のコンテキストを持つステートフルなコンポーネントモデルに対応することを基本に設計されました。 Spring はそうではありません。 Seam のバイジェクションと異なり、 Spring のインジェクションはメソッド呼び出し時に発生しません。 その代わり、 Spring bean がインスタンス化されるときだけ、 インジェクションは発生します。 従って、 bean がインスタンス化されるときに利用可能なインスタンスは、 beanのライフサイクル全期間で bean が使用するものと同じインスタンスです。 例えば、 Seam 対話 スコープコンポーネントのインスタンスが 直接、 単一の Spring bean にインジェクトされると、 その単一の Spring bean はその対話が終了した後もずっと同じインスタンスに対する参照を保持します。 この問題をスコープインピーダンスと呼んでいます。 システム全体に呼び出しが流れるように、 Seam バイインジェクションはスコープインピーダンスが自然に維持されるようにします。 Spring では、 Seam コンポーネントのプロキシをインジェクトすることでプロキシが呼ばれた場合に参照を解決する必要があります。

<seam:instance/> タグで自動的に Seam コンポーネントをプロキシできるようになります。

<seam:instance id="seamManagedEM" name="someManagedEMComponent" proxy="true"/>
        
<bean id="someSpringBean" class="SomeSpringBeanClass">
    <property name="entityManager" ref="seamManagedEM">
</bean>

このサンプルは、 Spring bean から の Seam 管理永続コンテキストの使い方の 1 つを示しています。 (Seam 管理永続コンテキストを Spring OpenEntityManagerInView フィルタの代替として利用する場合、 さらに堅牢となる使用方法は今後のリリースで提供します。)

20.2. Spring bean を Seam コンポーネントにインジェクトする

Spring bean を Seam コンポーネントインスタンスにインジェクトするのはさらに簡単です。 実際、 可能な方法は 2 つあります。

  • EL 式を使用して Spring bean をインジェクトする

  • Spring bean を Seam コンポーネントにする

2 番目のオプションについては次のセクションで説明します。 最も簡単な方法は EL 式を使って Spring bean にアクセスする方法です。

Spring DelegatingVariableResolver は、 Spring と JSF を統合するために Spring により提供されるインテグレーションポイントです。 この VariableResolver を使用すると、 bean id ですべての Spring bean を EL 式 で使用できるようにします。 DelegatingVariableResolverfaces-config.xml に追加する必要があります。

<application>
    <variable-resolver>
        org.springframework.web.jsf.DelegatingVariableResolver
    </variable-resolver>
</application>

これを行うと @In を使って Spring bean をインジェクトできるようになります。

@In("#{bookingService}")
private BookingService bookingService;

EL 式でインジェクションを行うために Spring bean を使用することに制限はありません。 Seam で EL 式が使用されるところならどこでも Spring bean を使用することができます。 プロセスとページフロー定義、 ワーキングメモリアサーションなど。

20.3. Spring bean を Seam コンポーネントにする

<seam:component/> 名前空間ハンドラを使用すると、 どんな Spring bean でも Seam コンポーネントにすることができます。 Seam コンポーネントにしたい bean の宣言内に <seam:component/> タグを配置するだけです。

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
    <seam:component/>
</bean>

デフォルトでは、 <seam:component/>は bean 定義で与えられるクラスと名前を付けて STATELESSコンポーネントを生成します。 ときおり、FactoryBean が使用される場合など、 Spring bean のクラス が bean 定義に出てくるクラスではないことがあります。 このような場合、 class は明示的に指定されなければなりません。 名前付けに競合の可能性がある場合、 Seam コンポーネント名を明示的に指定しても構いません。

Spring bean を特定の Seam スコープで管理したい場合、 <seam:component/>scope 属性を使用することができます。 指定される Seam スコープが STATELESS ではない場合、 Spring bean は prototype にスコープされなければなりません。 既にある Spring bean は通常ステートレスな特徴を基本的に持っていますので、 この属性は一般的には不要になります。

20.4. Seam スコープの Spring bean

Seam統合パッケージは、 Spring 2.0 スタイルのカスタムスコープとして Seam コンテキストの利用も可能にします。 どのような Seam コンテキスト中でもすべての Spring bean を宣言することができます。 ただし、 Spring コンポーネントモデルはステートフルに対応するようには構築されていないことを再度思い出してください。このことから、本機能の使用は慎重に行ってください。 特に、 セッションあるいは対話スコープの Spring beanのクラスタリングは根深い問題をはらんでおり、 広い範囲のスコープから狭い範囲のスコープの bean に bean またはコンポーネントをインジェクトする場合は、 十分に注意を払ってください。

Spring bean factory 設定で<seam:configure-scopes/> を一度指定すると、 すべての Seam スコープがカスタムスコープとして Spring bean に利用可能になります。 Spring bean を特定の Seam スコープに関連付けるには、 bean 定義の scope 属性で Seam スコープを指定してください。

<!-- Only needs to be specified once per bean factory-->
<seam:configure-scopes/>

...

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>

configure-scopes 定義内の prefix 属性を指定することによって、 スコープ名のプレフィックスを変更することができます。 (デフォルトのプレフィックスは seam. です。)

この方法で定義された Seam スコープの Spring bean は、 <seam:instance/> を使用することなく他の Spring bean にインジェクト可能です。 ただし、 スコープインピーダンスが必ず維持されるよう十分に注意してください。 Spring で一般的に使用される方法は、 bean 定義内での <aop:scoped-proxy/> の指定です。 しかし、 Seamスコープの Spring bean は <aop:scoped-proxy/> との互換性がありません。 したがって、 単一の bean に Seam スコープ Spring beanをインジェクトする必要がある場合、 <seam:instance/>を使用しなければなりません。

<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>

...

<bean id="someSingleton">
    <property name="someSeamScopedSpringBean">
        <seam:instance name="someSpringBean" proxy="true"/>
    </property>
</bean>

20.5. Seam コンポーネントとしての Spring Application Context

アプリケーションの持つ Spring の ApplicationContext を起動するために Spring の ContextLoaderListener を使用することはできますが、 制約がいくつかあります。

  • Spring ApplicationContext は、 SeamListener後に起動されなければなりません。

  • Seam ユニット及び統合テストでの使用を目的とした Spring ApplicationContext の起動は厄介なことがあります。

これら 2 つの制約を克服するために Spring 統合には Spring ApplicationContext を起動する Seam コンポーネントが含まれています。 この Seam コンポーネントを使用するには、 <spring:context-loader/> の定義を components.xml に配置します。 config-locations 属性で使用する Spring コンテキストファイルの場所を指定します。 複数の設定ファイルが必要な場合は、 ネストされる <spring:config-locations/> エレメントに配置することができます。 これを行うには、 components.xml ファイルに複数の値エレメントを追加する基準に従ってください。

<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:spring="http://jboss.com/products/seam/spring"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                                http://jboss.com/products/seam/spring http://jboss.com/products/seam/spring-1.2.xsd">

	<spring:context-loader context-locations="/WEB-INF/applicationContext.xml"/>

</components>

第21章 Seam の設定と Seam アプリケーションのパッケージング

設定はとても退屈なトピックと極めて退屈な気晴らしです。 あいにく、何行かの XML は、Seam を JSF 実装と サーブレットコンテナに統合するのに必要です。 以降の章から、うんざりする必要はありません。 サンプルアプリケーションから、 簡単にコピー/ペーストできるため これらを直接、打ち込む必要はありません。

21.1. Seam の基本設定

最初に、JSF と Seam を使うときにはいつでも必要な基本的な設定から見てみましょう。

21.1.1. Seam と JSF、servlet コンテナとの統合

Seam は web.xml ファイル中に以下の設定が必要です。

<listener>
    <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>

このリスナは、Seam をブートストラップすることと、セッションあるいはアプリケーションコンテキストの破棄を、担当しています。

JSFのリクエストのライフサイクルでSeamを統合するためにfaces-config.xml中でJSFのPhaseListenerを登録しておく必要があります。

<lifecycle>
    <phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
</lifecycle>

実際、ここでのリスナクラスは、どのようなトランザクション区分 (詳細は以下) を必要とするかに応じて、変更する必要があります。

もしSunの JSF 1.2 参照実装を使用しているのであれば、下の記述もfaces-config.xml に必要になります。

<application>
    <el-resolver>org.jboss.seam.jsf.SeamELResolver</el-resolver>
</application>

(この記述は必ずしも必要とは言えませんが、RIのマイナーなバグを回避するのに役立ちます。)

JSF の実装の一部にはサーバサイドでの状態の保持の実装にバグが有る物がありますが、 Seamは対話の為にこの部分を使用しています。 もし、フォームの送信で問題が有る場合には、クライアント側に 状態を保持するようにしてみてください。この場合、web.xmlに以下を記述します。

<context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
</context-param>

21.1.2. Seam リソース Servlet

Seam リソース Servlet は Seam Remoting 、キャプチャ (セキュリティの章を参照) や JSF の UI の制御で使用されるリソースを提供します。 Seam リソース Servlet の設定には web.xml に以下の記述が必要です。

<servlet>
  <servlet-name>Seam Resource Servlet</servlet-name>
  <servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>
</servlet>
    
<servlet-mapping>
  <servlet-name>Seam Resource Servlet</servlet-name>
  <url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>

21.1.3. Seam servlet フィルター

Seam は基本的な動作には servlet フィルターを必要としません。 しかし、Seamには、フィルターの使用を前提とした 幾つかの機能があり、Seamの組み込みコンポーネントの設定と同様の方法で、servletフィルターを設定することが出来るようになっています。 この機能を使用するためには、まづ、web.xmlに下のように、マスターフィルターを設定する必要があります。

<filter>
    <filter-name>Seam Filter</filter-name>
    <filter-class>org.jboss.seam.web.SeamFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Seam Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

マスターフィルタを組み込むことにより、以下の組み込みフィルタを使用できるようになります。

21.1.3.1. 例外処理

このフィルタは例外をpages.xmlにマッピングする機能があります。 (ほとんどのアプリケーションで必要な機能です。) また、捕捉出来ないエラーがトランスアクションで発生した場合にロールバック 処理を行います。 (Java EE 仕様に従えば、webコンテナがこの処理を自動的に行う必要が有りますが、すべてのアプリケーションサーバが この機能を正しく実装しているとは言えないことが分かっています。 勿論、Tomcatのような単純なsevletエンジンではこの 機能は必要有りません。)

デフォルトで、すべてのリクエストに対して例外処理フィルタが適用されますが、下のように、 components.xml<web:exception-filter>を記述して、 これを変更することもできます。

<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:web="http://jboss.com/products/seam/web"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-1.2.xsd
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
                 http://jboss.com/products/seam/web http://jboss.com/products/seam/web-1.2.xsd">

    <web:exception-filter url-pattern="*.seam"/>

</components>
  • url-pattern — どのリクエストをフィルタするか指定します。 デフォルトではすべてのリクエストになります。

21.1.3.2. リダイレクトによる対話の伝搬

このフィルタはSeamがブラウザのリダイレクトにより対話コンテキストを伝搬する事を可能にします。 ブラウザリダイレクトをインターセプトし、Seamの対話identifierをパラメータに追加することにより、実現しています。

リダイレクトフィルタも、デフォルトですべてのリクエストを対象としますが、 components.xmlの記述を以下のようにして変更することが可能です。

<web:redirect-filter url-pattern="*.seam"/>
  • url-pattern — どのリクエストをフィルタするか指定します。 デフォルトではすべてのリクエストになります。

21.1.3.3. マルチパートフォームの送信

この機能はファイルのアップロードの時のJSF制御に必要となります。マルチパートフォームのリクエストを検出すると、 RFC-2388 (multipart/form-data 仕様) に従い処理を行います。 デフォルトの設定をオーバーライドするためには components.xmlに以下の設定を追加します。

<web:multipart-filter create-temp-files="true" 
        max-request-size="1000000" 
        url-pattern="*.seam"/>
  • create-temp-filestrueにすると、 アップロードされたファイルはメモリ中ではなく、テンポラリファイルに書き込まれます。 大きなファイルのアップロードが考えられるような場合には重要となります。 デフォルトではfalseとなっています。

  • max-request-size — リクエストヘッダー中の Content-Lengthで示されるアップロードファイルのサイズがこの値を越えた場合、リクエストは中断されます。 デフォルトの設定は 0 (ファイルのサイズを制限しません)。

  • url-pattern — どのリクエストをフィルタするか指定します。 デフォルトではすべてのリクエストになります。

21.1.3.4. キャラクターエンコーディング

送信されたフォームデータのキャラクターエンコーディングをセットします。

デフォルトではこのフィルタはインストールされていませんので、components.xml に以下の記述が必要です。

<web:character-encoding-filter encoding="UTF-16" 
        override-client="true" 
        url-pattern="*.seam"/>
  • encoding — 使用するエンコーディングです。

  • override-clienttrue に設定されていた場合、リクエストがエンコーディングを指定しているか否かに関わらず、encodingで 指定されたエンコーディングを使用します。 falseに設定されていた場合には、リクエストがエンコーディングを指定していない場合に限りencodingで指定されたエンコーディングを使用します。 デフォルトでfalseに設定されています。

  • url-pattern — どのリクエストをフィルタするか指定します。 デフォルトではすべてのリクエストになります。

21.1.3.5. カスタム servletsのコンテキスト管理

JSF servlet以外の servletに直接送信されるリクエストは、JSFのライフサイクルでは処理されません。 そこで、Seamは Seamのコンポーネントにアクセスする必要の有る その他の servletに対して servletフィルタを提供しています。

このフィルタを適用することにより、カスタムservletが Seamコンテキストと相互に作用することを可能にします。 個々のリクエストの最初にSeamコンテキストをセットアップし、リクエストの終了畤にこれを破棄します。このフィルタは JSFの FacesServlet には決して適用されない事を銘記してください。 Seam はJSFのリクエストのコンテキスト管理には phase listenerを使用します。

デフォルトではこのフィルタはインストールされていませんので、components.xml に以下の記述が必要です。

<web:context-filter url-pattern="/media/*"/>
  • url-pattern — どのリクエストをフィルタするのか指定します。 デフォルトではすべてのリクエスト。 コンテキストフィルタに url-pattern が指定されていればフィルタが適用されます (明示的に使用不可としていない限り)。

コンテキストフィルタはconversationIdという名前でリクエストパラメータ中に対話 id を探そうとします。必ず、リクエストパラメータに対話 idを含めるようにしてください。

また、新たな対話 idをクライアント側に確実に伝える必要があります。 Seamは組み込みコンポーネント conversationのプロパティとして対話 idを公開しています。

21.1.4. EJB コンテナと Seam の統合

SeamInterceptor を、 Seam コンポーネントに対応させる必要があります。 このための最も簡単な方法は、 次のインターセプタバインディングを、 ejb-jar.xml の中の <assembly-descriptor> に、 追加することです。

<interceptor-binding>
    <ejb-name>*</ejb-name>
    <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>

Seam は、セッション Bean が JNDI でどこにあるかを知る必要があります。 このための方法の 1 つは、 それぞれの Session Bean コンポーネントに、@JndiName を指定することです。 しかし、 これではつまらな過ぎます。 もっと良い方法は、 EJB 名から JNDI 名を判断するために、 Seam が使用するパターンを指定することです。 あいにく、EJB3 標準に定義されたグローバル JNDI をマッピングする標準は存在しないため、 このマッピングはベンダ固有になります。 通常、components.xmlにこのオプションを指定します。

JBossアプリケーションサーバでは、次のパターンは誤りではありません。

<core:init jndi-name="myEarName/#{ejbName}/local" />

ここで、myEarNameは、Bean がデプロイされた EAR の名前です。

EAR のコンテキストの外側では (JBoss 組み込み可能 EJB3 コンテナを使用するとき)、 次のパターンは使い方の 1 つです。

<core:init jndi-name="#{ejbName}/local" />

他のアプリケーションサーバでの正しい記述については実験してみる必要があります。GlassFishのようなアプリケーションサーバ では、すべてのEJBコンポーネントについて明示的にJNDI名が決められている必要があります。 この場合、独自のパターンを決める必要があります。

21.1.5. facelet の使用

我々の意見を聞き入れていただいて、JSPの替わりに faceletを使っていただけるとしたら、faces-config.xmlに以下の設定が必要です。

<application>
    <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>

そして、 web.xmlに下の記述も必要です。

<context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
</context-param>

21.1.6. おっと、もう一つ重要情報

最後にもう一つ理解しておくことがあります。 Seamコンポーネントが配備されるどのようなアーカイブにも、 seam.propertiesMETA-INF/seam.properties あるいは META-INF/components.xmlを作成しておく必要があります (空のファイルであってもかまいません)。 Seamは起動時に seamコンポーネントを探すために、すべてのアーカイブでseam.propertiesをスキャンします。

Seamコンポーネントが有る場合には、webアーカイブ (WAR) のWEB-INF/classesディレクトリに seam.propertiesファイルを作成する必要があります。

これが、Seamのサンプルアプリケーションすべてに、空のseam.propertiesが存在する理由です。 (空であっても) これを削除するとアプリケーションが動作しなくなります。

空のファイルで有っても、存在しないと動かないようなフレームワークを作るようなデザイナーは、ちょっとオカシイと思われるかもしれませんが、これは JVM の制約を回避するための方策です — もし、このようなメカニズムを使わないとすれば、次善の方法は、 他の競合するフレームワークで行われているように、 components.xmlにすべてのコンポーネントを明示的に記述することです。 多分、我々の選択の方を気に入っていただけると思います。

21.2. Java EE 5 で Seam を設定

Java EE 5 環境で実行するならば、 Seam の使用を始めるために必要な設定はこれだけです。

21.2.1. パッケージング

これらすべてを EAR にパッケージングしたならば、 アーカイブの構造は以下のようになります。

my-application.ear/
    jboss-seam.jar
    el-api.jar
    el-ri.jar
    META-INF/
        MANIFEST.MF
        application.xml
    my-application.war/
        META-INF/
            MANIFEST.MF
        WEB-INF/
            web.xml
            components.xml
            faces-config.xml
            lib/
                jsf-facelets.jar
                jboss-seam-ui.jar
        login.jsp
        register.jsp
        ...
    my-application.jar/
        META-INF/
            MANIFEST.MF
            persistence.xml
        seam.properties
        org/
            jboss/
                myapplication/
                    User.class
                    Login.class
                    LoginBean.class
                    Register.class
                    RegisterBean.class
                    ...

EARのクラスパスに jboss-seam.jar, el-api.jar それにel-ri.jar を含めておく必要があります。また、これらすべての jar ファイルが application.xmlから参照できるようにしておく必要があります。

もし、jBPM や Droolsを使用するのであれば、それらに必要な jarファイルを EARのクラスパスに含めて おかなければなりません。また、これらすべての jar ファイルがapplication.xmlから参照できるように しておく必要があります。

facelets を使用する場合 (推奨) は WARのWEB-INF/libディレクトリに jsf-facelets.jarを含める必要があります。

Seam のタグライブラリを使用する場合には (ほとんどの Seam アプリケーションで使用される)、 WAR ファイルの WEB-INF/lib ディレクトリに jboss-seam-ui.jar を含める必要があります。 PDFや email のタグライブラリを使用する場合には、 WEB-INF/libjboss-seam-pdf.jar または jboss-seam-mail.jar を含める必要があります。

Seam デバッグページを使用する (facelets を使用している場合のみ利用可能) 場合には WARの WEB-INF/libディレクトリにjboss-seam-debug.jarを含めます。

サンプルアプリケーションには EJB 3.0をサポートする Java EEコンテナに配備可能な幾つかの Seam アプリケーションがふくまれています。

「さー、ここまでで設定は終わりですよ」と言えれば良いのですが、また3分の一が終わったところです。もし、 もう飽き飽きしておられたら、残りは飛ばして、必要な時にまた読み返すようにしていただいてもかまいません。

21.3. JBoss 組み込み可能 EJB3 コンテナ の Seam 設定

JBoss 組み込み可能 EJB3 コンテナは、 Java EE 5 アプリケーションサーバのコンテキストの外側で EJB3 コンポーネントを稼動させることができ、特にアプリケーションのテストを行うとき (それだけに限ることは有りませんが) に有用です。

Seam 予約サンプルアプリケーションは、 組み込み可能 EJB3 コンテナの上でも動作可能な、 TestNG 統合テストスイートを含んでいます。

この予約サンプルアプリケーションは Tomcat にもデプロイ可能です。

21.3.1. 組み込み可能 EJB3 コンテナのインストール

Seam は、embedded-ejb ディレクトリに、 組み込み可能 EJB3 コンテナの構造を付けて 出荷しました。 Seam で組み込み可能 EJB3 コンテナを使うためには、 embedded-ejb/conf ディレクトリ、 そして、libembedded-ejb/lib ディレクトリのすべての JAR に、クラスパスを 通してください。 その次に、components.xml に以下の行を追加してください。

<core:ejb />

この設定は、org.jboss.seam.core.ejb と呼ばれる組み込みコンポーネントをインストールします。 このコンポーネントは、 Seam が起動されるときに EJB コンテナのブートストラップを、 WEB アプリケーションがアンデプロイされるときに シャットダウンを担当します。

21.3.2. 組み込み可能 EJB3 コンテナのデータソース設定

コンテナ設定に関する詳細な情報は、 組み込み可能 EJB3 コンテナのドキュメントを、 参照してください。 少なくともデータソースの設定は必要になるでしょう。 組み込み可能 EJB3 は JBoss のマイクロコンテナを使用して実装されているため、 デフォルトで提供された最小限のサービス一式によって、 簡単に新しいサービスを追加することができます。 例えば、この jboss-beans.xml ファイルをクラスパスに通すことで、 新しいデータソースを追加することが可能です。

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
            xmlns="urn:jboss:bean-deployer">

   <bean name="bookingDatasourceBootstrap"
        class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
      <property name="driverClass">org.hsqldb.jdbcDriver</property>
      <property name="connectionURL">jdbc:hsqldb:.</property>
      <property name="userName">sa</property>
      <property name="jndiName">java:/bookingDatasource</property>
      <property name="minSize">0</property>
      <property name="maxSize">10</property>
      <property name="blockingTimeout">1000</property>
      <property name="idleTimeout">100000</property>
      <property name="transactionManager">
        <inject bean="TransactionManager"/>
      </property>
      <property name="cachedConnectionManager">
        <inject bean="CachedConnectionManager"/>
      </property>
      <property name="initialContextProperties">
        <inject bean="InitialContextProperties"/>
      </property>
   </bean>

   <bean name="bookingDatasource" class="java.lang.Object">
      <constructor factoryMethod="getDatasource">
         <factory bean="bookingDatasourceBootstrap"/>
      </constructor>
   </bean>

</deployment>

21.3.3. パッケージング

Tomcat のような サーブレットエンジンへの WAR ベースのデプロイメントのアーカイブの構造は、以下のようになります。

my-application.war/
    META-INF/
        MANIFEST.MF
    WEB-INF/
        web.xml
        components.xml
        faces-config.xml
        lib/
            jboss-seam.jar
            jboss-seam-ui.jar
            el-api.jar
            el-ri.jar
            jsf-facelets.jar
            myfaces-api.jar
            myfaces-impl.jar
            jboss-ejb3.jar
            jboss-jca.jar
            jboss-j2ee.jar
            ...
            mc-conf.jar/
                ejb3-interceptors-aop.xml
                embedded-jboss-beans.xml
                default.persistence.properties
                jndi.properties
                login-config.xml
                security-beans.xml
                log4j.xml
            my-application.jar/
                META-INF/
                    MANIFEST.MF
                    persistence.xml
                    jboss-beans.xml
                log4j.xml
                seam.properties
                org/
                    jboss/
                        myapplication/
                            User.class
                            Login.class
                            LoginBean.class
                            Register.class
                            RegisterBean.class
                            ...
    login.jsp
    register.jsp
    ...

mc-conf.jarは、 組み込み可能 EJB3 用の 標準のJBoss マイクロコンテナ 設定ファイルを含みます。 通常、これらのファイルを編集する必要はないでしょう。

ほとんどの Seam サンプルアプリケーションは、ant deploy.tomcat を実行することによって、 Tomcat にデプロイすることも可能です。

21.4. J2EEでの Seam の設定

Seam は、EJB 3.0 を始める準備ができていなくても有用です。 この場合、 EJB 3.0 永続性の代わりにHibernate3 もしくは JPAを、 セッション Bean の代わりに plain JavaBeans を使用します。 セッションBeanのすばらしい特徴のいくらかは 失うかもしれませんが、 準備ができたとき、EJB 3.0 に移行することができ、 それまでの間、Seam のユニークな宣言的状態管理 アーキテクチャを利用が可能となります。

Seam JavaBean コンポーネントはセッション Bean がするような宣言的トランザクション区分は提供しません。 JTA UserTransactionを使用して、 マニュアルでトランザクションを管理することもできるかもしれません。 (Seam インターセプタで独自の宣言的トランザクション管理もできるかもしれません。) しかし、JavaBean といっしょに Hibernate を 使用する場合、 ほとんどのアプリケーションは、 Seam 管理トランザクションを使用するでしょう。 SeamExtendedManagedPersistencePhaseListenerを有効にするためには、 永続性の章の指示に従ってください。

Seam の配布には、EJB3 の代わりに Hibernate や JavaBean を使用した 予約サンプルアプリケーションが含まれています。 このサンプルアプリケーションはどんなJ2EEアプリケーションサーバでも すぐにデプロイ可能です。

21.4.1. Seam での Hibernateのブートストラップ

Seam は組み込みコンポーネント (org.jboss.seam.core.hibernate ) がインストールされていれば、 hibernate.cfg.xml ファイルから、HibernateのSessionFactoryをブートストラップします。

<core:hibernate-session-factory name="hibernateSessionFactory"/>

この時、インジェクションを使って、Seamの管理する HibernateのSessionを利用するのであれば、 managed sessionを設定する必要があります。

21.4.2. Seam での JPAのブートストラップ

Seam はもし組み込みコンポーネントがインストールされていれば、persistence.xml からEntityManagerFactory JPAをブートストラップします。

<core:entity-manager-factory name="entityManagerFactory"/>

インジェクションを使って、Seamの管理する JPA, EntityManagerを利用するためには、 managed persistencece contextを設定する必要があります。

21.4.3. パッケージング

アプリケーションは WARとしてパッケージすることが出来、その構成は以下の様になります。

my-application.war/
    META-INF/
        MANIFEST.MF
    WEB-INF/
        web.xml
        components.xml
        faces-config.xml
        lib/
            jboss-seam.jar
            jboss-seam-ui.jar
            el-api.jar
            el-ri.jar
            jsf-facelets.jar
            hibernate3.jar
            hibernate-annotations.jar
            ...
            my-application.jar/
                META-INF/
                   MANIFEST.MF
                seam.properties
                hibernate.cfg.xml
                org/
                    jboss/
                        myapplication/
                            User.class
                            Login.class
                            Register.class
                            ...
    login.jsp
    register.jsp
    ...

Tomcatや TestNGのような非 Java EE環境下で Hibernate を配備する場合には、もう少し設定が必要です。

21.5. JBoss マイクロコンテナを使い Seamを Java SE環境下で設定する

Seamは JTAと JCAデータソースを要求する Hibernate および JPAをサポートしています。tomcat や TestNGのような、非 Java EE環境下でJBossマイクロコンテナを使用して、これらのサービスや Hibernateを走らせる事ができます。

予約システムサンプルアプリケーションの Hibernateと JPAのバージョンを tomcat上に配備することも出来ます。

Seam のサンプルファイルのmicrocontainer/conf/jboss-beans.xmlにあるマイクロコンテナ設定には、非 Java EE環境下でHibernateと共にSeamを走らせる為に必要な設定のすべてがあります。 microcontainer/confディレクトリを作り、サンプルのlibmicrocontainer/lib ディレクトリに有る jar ファイルをクラスパスに追加してください。 詳細は、JBoss マイクロコンテナのドキュメンテーションを参照してください。

21.5.1. JBoss マイクロコンテナと Hibernateを使用する

Seamの組み込みコンポーネントorg.jboss.seam.core.microcontainerが、マイクロンテナをブートストラップします。 ここでも、Seamの管理するセッションとして使用します。

<core:microcontainer/>

<core:managed-hibernate-session name="bookingDatabase" auto-create="true"
    session-factory-jndi-name="java:/bookingSessionFactory"/>

ここで java:/bookingSessionFactoryhibernate.cfg.xmlで指定された Hibernaeteのファクトリの名前です。

JNDI、JTA、JCAデータソース、それに Hibernateをマイクロコンテナにインストールするためにjboss-beans.xml ファイルが必要です。

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
            xmlns="urn:jboss:bean-deployer">

   <bean name="Naming" class="org.jnp.server.SingletonNamingServer"/>

   <bean name="TransactionManagerFactory" class="org.jboss.seam.microcontainer.TransactionManagerFactory"/>
   <bean name="TransactionManager" class="java.lang.Object">
      <constructor factoryMethod="getTransactionManager">
         <factory bean="TransactionManagerFactory"/>
      </constructor>
   </bean>

   <bean name="bookingDatasourceFactory" class="org.jboss.seam.microcontainer.DataSourceFactory">
      <property name="driverClass">org.hsqldb.jdbcDriver</property>
      <property name="connectionUrl">jdbc:hsqldb:.</property>
      <property name="userName">sa</property>
      <property name="jndiName">java:/hibernateDatasource</property>
      <property name="minSize">0</property>
      <property name="maxSize">10</property>
      <property name="blockingTimeout">1000</property>
      <property name="idleTimeout">100000</property>
      <property name="transactionManager"><inject bean="TransactionManager"/></property>
   </bean>
   <bean name="bookingDatasource" class="java.lang.Object">
      <constructor factoryMethod="getDataSource">
         <factory bean="bookingDatasourceFactory"/>
      </constructor>
   </bean>

   <bean name="bookingSessionFactoryFactory" class="org.jboss.seam.microcontainer.HibernateFactory"/>
   <bean name="bookingSessionFactory" class="java.lang.Object">
      <constructor factoryMethod="getSessionFactory">
         <factory bean="bookingSessionFactoryFactory"/>
      </constructor>
      <depends>bookingDatasource</depends>
   </bean>

</deployment>

21.5.2. パッケージング

WAR は下のような構成になります。

my-application.war/
    META-INF/
        MANIFEST.MF
    WEB-INF/
        web.xml
        components.xml
        faces-config.xml
        lib/
            jboss-seam.jar
            jboss-seam-ui.jar
            el-api.jar
            el-ri.jar
            jsf-facelets.jar
            hibernate3.jar
            ...
            jboss-microcontainer.jar
            jboss-jca.jar
            ...
            myfaces-api.jar
            myfaces-impl.jar
            mc-conf.jar/
                jndi.properties
                log4j.xml
            my-application.jar/
                META-INF/
                    MANIFEST.MF
                    jboss-beans.xml
                seam.properties
                hibernate.cfg.xml
                log4j.xml
                org/
                    jboss/
                        myapplication/
                            User.class
                            Login.class
                            Register.class
                            ...
    login.jsp
    register.jsp
    ...

21.6. SeamでのjBPM設定

SeamのjBPMインテグレーションはデフォルトでは、設定されませんので、 組み込みコンポーネントを設定することで、jBPMを有効化します。 また、プロセスとページフローをcomponents.xmlに 明示的に指定しなければいけません:

<core:jbpm>
    <core:pageflow-definitions>
        <value>createDocument.jpdl.xml</value>
        <value>editDocument.jpdl.xml</value>
        <value>approveDocument.jpdl.xml</value>
    </core:pageflow-definitions>
    <core:process-definitions>
        <value>documentLifecycle.jpdl.xml</value>
    </core:process-definitions>
</core:jbpm>

ページフローのみの指定であれば、これ以上の設定は不要です。プロセス定義を 利用する場合、jBPM設定を用意しなければなりません、あわせて、jBPMで利用する Hibernate設定も用意する必要があります。Seam DVD Store demoは Seam で機能する jbpm.cfg.xmlhibernate.cfg.xmlを含めた サンプルです:

<jbpm-configuration>

  <jbpm-context>
    <service name="persistence">
       <factory>
          <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
             <field name="isTransactionEnabled"><false/></field>
          </bean>
       </factory>
    </service>
    <service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
    <service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
    <service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
    <service name="authentication" factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" />
  </jbpm-context>

</jbpm-configuration>

ここでのもっとも重要なことは、jBPMトランザクション制御は無効になっているということです。 Seam、あるいはEJB3がJTAトランザクションを制御するべきです。

21.6.1. パッケージング

まだ、jBPM設定とプロセス/ページフロー定義を含めた場合での整理されたパッケージングフォーマット ではありません。このSeamサンプルでは、EARのルートにすべてのファイルを単純に パッケージングすることにしました。将来、標準パッケージングフォーマットを 提供しようとおもっています。EARはこのような感じになります:

my-application.ear/
    jboss-seam.jar
    el-api.jar
    el-ri.jar
    jbpm-3.1.jar
    META-INF/
        MANIFEST.MF
        application.xml
    my-application.war/
        META-INF/
            MANIFEST.MF
        WEB-INF/
            web.xml
            components.xml
            faces-config.xml
            lib/
                jsf-facelets.jar
                jboss-seam-ui.jar
        login.jsp
        register.jsp
        ...
    my-application.jar/
        META-INF/
            MANIFEST.MF
            persistence.xml
        seam.properties
        org/
            jboss/
                myapplication/
                    User.class
                    Login.class
                    LoginBean.class
                    Register.class
                    RegisterBean.class
                    ...
    jbpm.cfg.xml
    hibernate.cfg.xml
    createDocument.jpdl.xml
    editDocument.jpdl.xml
    approveDocument.jpdl.xml
    documentLifecycle.jpdl.xml

jbpm-3.1.jarをEJB-JARとWARのマニフェストファイルに 追加することを忘れないでください。

21.7. ポータルでの Seamの設定

Seamのアプリケーションをポートレットとして実行する場合には、通常の Java EE のメタデータに加えて、特定のポートレットのメタデータ (portlet.xml など) を与える必要があります。 JBoss Portal 上で稼働するよう事前設定されたブッキングデモのサンプルは examples/portal ディレクトリにあります。

また、SeamPhaseListenerTransactionalSeamPhaseListener の替わりにポートレットに固有のリスナーを使う必要があります。 SeamPortletPhaseListener 及び TransactionalSeamPortletPhaseListener はポートレットライフサイクルに適合します。 この2番目のクラスの名前については適切な物が思いつかず、このようになってしまいましたことご諒解ください。

21.8. JBoss ASでの SFSBとセッションタイムアウトの設定

Stateful Session Beans のタイムアウトは HTTP Sessions のタイムアウトより高くセットしておくことが非常に重要です。 これを行わないと、 SFSB はユーザーの HTTP セッションが終了する前にタイムアウトしてしまうことがあります。 JBoss Application Server のデフォルトのセッション bean タイムアウトは 30 分になっています。 これは server/default/conf/standardjboss.xml 内で設定されています (default はご使用の設定に置き換えてください)。

デフォルトの SFSB タイムアウトは LRUStatefulContextCachePolicy キャッシュ設定内の max-bean-life の値を変更して調整することができます。

      <container-cache-conf>
        <cache-policy>org.jboss.ejb.plugins.LRUStatefulContextCachePolicy</cache-policy>
        <cache-policy-conf>
          <min-capacity>50</min-capacity>
          <max-capacity>1000000</max-capacity>
          <remover-period>1800</remover-period>

          <!-- SFSB timeout in seconds; 1800 seconds == 30 minutes -->
          <max-bean-life>1800</max-bean-life>  
          
          <overager-period>300</overager-period>
          <max-bean-age>600</max-bean-age>
          <resizer-period>400</resizer-period>
          <max-cache-miss-period>60</max-cache-miss-period>
          <min-cache-miss-period>1</min-cache-miss-period>
          <cache-load-factor>0.75</cache-load-factor>
        </cache-policy-conf>
      </container-cache-conf>

デフォルトの HTTP セッションタイムアウトは、 JBoss 4.0.x なら server/default/deploy/jbossweb-tomcat55.sar/conf/web.xml で、 JBoss 4.2.x なら server/default/deploy/jboss-web.deployer/conf/web.xml でそれぞれ変更することができます。 このファイルの次のエントリで、 すべてのウェブアプリケーションのデフォルトセッションタイムアウトを制御します。

   <session-config>
      <!-- HTTP Session timeout, in minutes -->
      <session-timeout>30</session-timeout>
   </session-config>        
        

ご使用のアプリケーションの値を上書きするには、 そのアプリケーション自体の web.xml 内にこのエントリを含ませるだけで上書きできます。

第22章 Seam アノテーション

Seam アプリケーションを記述する場合、数多くのアノテーションを使用することになります。 Seam ではアノテーションを使用して宣言的なプログラミングを実現することができます。使用するアノテーションのほとんどは EJB 3.0 仕様で定義されています。データ検証用のアノテーションは Hibernate Validator パッケージで定義されています。そして、 Seam は Seam 独自のアノテーションセットを持っており、これについてはこの章で説明します。

すべての Seam のアノテーションはパッケージ org.jboss.seam.annotations で定義されます。

22.1. コンポーネント定義用アノテーション

アノテーションの最初のグループは、 Seam コンポーネントを定義するものです。これらのアノテーションはコンポーネントクラスで使われます。

@Name
@Name("componentName")

クラスに対して Seam コンポーネント名を定義します。 Seam のコンポーネントにはこのアノテーションが必須です。

@Scope
@Scope(ScopeType.CONVERSATION)

コンポーネントのデフォルトコンテキストを定義します。 選択可能な値は ScopeType 列挙型で定義されています: EVENT, PAGE, CONVERSATION, SESSION, BUSINESS_PROCESS, APPLICATION, STATELESS

スコープが明示的に指定されていない場合、 デフォルトはコンポーネントタイプにより異なってきます。 ステートレスセッション Bean の場合、 デフォルトは STATELESS になります。 エンティティ Bean およびステートフルセッション Bean なら、 デフォルトは CONVERSATION になり、 JavaBeans のデフォルトは EVENT になります。

@Role
@Role(name="roleName", scope=ScopeType.SESSION)

Seam コンポーネントを複数のコンテキスト変数にバインドできるようにします。 @Name/@Scope アノテーションは「デフォルトロール」を定義します。 各 @Role アノテーションは「追加ロール」の定義となります。

  • name — コンテキスト変数名です。

  • scope — コンテキスト変数スコープです。 スコープが明示的に指定されない場合、 デフォルトは上記のとおりコンポーネントタイプにより異なります。

@Roles
@Roles({
        @Role(name="user", scope=ScopeType.CONVERSATION),
        @Role(name="currentUser", scope=ScopeType.SESSION)
    })

複数の追加ロールを指定することができるようになります。

@Intercept
@Intercept(InterceptionType.ALWAYS)

Seam インターセプタをいつアクティブにするかを指定します。 選択可能な値は InterceptionType 列挙型で定義されます: ALWAYS, AFTER_RESTORE_VIEW, AFTER_UPDATE_MODEL_VALUES, INVOKE_APPLICATION, NEVER

インターセプタタイプが明示的に指定されない場合、 デフォルトはコンポーネントタイプにより異なります。 エンティティ Bean の場合、 デフォルトは NEVER になります。 セッション Bean、 メッセージ駆動型 Bean および JavaBeans ならデフォルトは ALWAYS になります。

@JndiName
@JndiName("my/jndi/name")

Seam が EJB コンポーネントのルックアップに使用する JNDI 名を指定します。 JNDI 名が明示的に指定されない場合、 Seam は org.jboss.seam.core.init.jndiPattern で指定される JNDI パターンを使用します。

@Conversational
@Conversational(ifNotBegunOutcome="error")

対話スコープのコンポーネントが対話形式であることを指定します。 つまり、 このコンポーネントで開始される長期の対話がアクティブでない限り (メソッドが新規の長期対話を開始しない限り) コンポーネントのメソッドは呼び出すことができないということです。

@Startup
@Startup(depends={"org.jboss.core.jndi", "org.jboss.core.jta"})

アプリケーションスコープのコンポーネントが、初期化時に直ちに開始されることを指定します。 主に、 JNDI、 データソースなどの重要なインフラストラクチャをブートストラップする特定の組み込みコンポーネントに使用されます。

@Startup

セッションスコープのコンポーネントが、セッション作成時に直ちに開始されることを指定します。

  • depends — 指定されたコンポーネントがインストールされている場合はそのコンポーネントを先に開始されなければならないことを指定します。

@Install
@Install(false)

コンポーネントがデフォルトでインストールされる必要があるかどうかを指定します。 @Install アノテーションが無い場合、コンポーネントはインストールが必要であるという意味になります。

@Install(dependencies="org.jboss.seam.core.jbpm")

コンポーネントが、依存関係として指定されるコンポーネントがインストールされる場合にのみインストールされることを指定します。

@Install(genericDependencies=ManagedQueueSender.class)

コンポーネントが、 特定のクラスにより実装されるコンポーネントがインストールされる場合にのみインストールされることを指定します。 依存するコンポーネントの名前が不定である場合に便利です。

@Install(classDependencies="org.hibernate.Session")

コンポーネントが、指定されたクラスがクラスパス内にある場合にのみインストールされることを指定します。

@Install(precedence=BUILT_IN)

そのコンポーネントの優先度を指定します。 同じ名前のコンポーネントが複数存在する場合、 より高い優先度を持つコンポーネントがインストールされます。 定義される優先度の値は次のとおりです (昇順) :

  • BUILT_IN — すべての組み込み Seam コンポーネントが持つ優先度

  • FRAMEWORK — Seam を拡張するフレームワークのコンポーネントに対して使用する優先度

  • APPLICATION — アプリケーションコンポーネントの優先度 (デフォルトの優先度)

  • DEPLOYMENT — 特定のデプロイメントにおいてアプリケーションコンポーネントを上書きするコンポーネントに対して使用する優先度

  • MOCK — テストで使用される Mock オブジェクトの優先度

@Synchronized
@Synchronized(timeout=1000)

コンポーネントが複数のクライアントによって同時にアクセスされること、 Seam はリクエストをシリアライズすることを指定します。 リクエストが特定のタイムアウト期間内にコンポーネントでロックを取得できないと例外が発生します。

@ReadOnly
@ReadOnly

JavaBean コンポーネントまたはコンポーネントメソッドが呼び出しの終わりで状態の複製を必要としないことを指定します。

22.2. バイジェクション用アノテーション

次の 2 つのアノテーションはバイジェクションを制御します。 これらの属性はコンポーネントインスタンス変数またはプロパティのアクセサメソッドに指定できます。

@In
@In

コンポーネントの属性が各コンポーネント呼び出しの開始時にコンテキスト変数からインジェクトされることを指定します。 コンテキスト変数が null の場合、 例外が発生します。

@In(required=false)

コンポーネントの属性が各コンポーネント呼び出しの開始時にコンテキスト変数からインジェクトされることを指定します。 コンテキスト変数は null でも構いません。

@In(create=true)

コンポーネント属性がコンポーネント呼び出しの開始時にコンテキスト変数からインジェクトされることを指定します。

@In(value="contextVariableName")

アノテーションを付けられたインスタンス変数名を使用せず、 コンテキスト変数名を明示的に指定します。

@In(value="#{customer.addresses['shipping']}")

コンポーネント属性が各コンポーネント呼び出しの開始時に JSF EL 式を評価することでインジェクトされることを指定します。

  • value — コンテキスト変数名を指定します。 デフォルトはコンポーネントの属性名です。 あるいは、 #{...} で括られた JSF EL 式を指定します。

  • create — コンテキスト変数がすべてのコンテキストで定義されていない (null) 場合に Seam がコンテキスト変数と同じ名前でコンポーネントをインスタンス化することを指定します。 デフォルトは false です。

  • required — コンテキスト変数がすべてのコンテキストで定義されていない場合に Seam が例外を発生させることを指定します。

@Out
@Out

Seam コンポーネントであるコンポーネントの属性が呼び出しの終わりでそのコンテキスト変数にアウトジェクトされることを指定します。 属性が null の場合、 例外が発生します。

@Out(required=false)

Seam コンポーネントであるコンポーネントの属性が呼び出しの終わりでそのコンテキスト変数にアウトジェクトされることを指定します。 属性は null でも構いません。

@Out(scope=ScopeType.SESSION)

Seam コンポーネントタイプではないコンポーネントの属性が呼び出しの終わりで特定スコープにアウトジェクトされることを指定します。

明示的にスコープが指定されていない場合、 代わりに @Out 属性を持つコンポーネント自体のスコープが使用されます (またはコンポーネントがステートレスであれば EVENT) 。

@Out(value="contextVariableName")

アノテーションを付けられたインスタンス変数名を使用せず、 コンテキスト変数名を明示的に指定します。

  • value — コンテキスト変数名を指定します。 デフォルトはコンポーネントの属性名です。

  • required — アウトジェクションを行ったときにコンポーネント属性が null だった場合、 Seam が例外を発生させることを指定します。

これらのアノテーションは同時に利用されます。 例:

@In(create=true) @Out private User currentUser;

次のアノテーションは マネージャコンポーネント パターンをサポートします。 このアノテーションは、インジェクトされる他のいくつかのクラスのインスタンスのライフサイクルを管理する Seam コンポーネントで利用され、コンポーネントの getter メソッドに付与されます。

@Unwrap
@Unwrap

このアノテーションが付いている getter メソッドにより返されるオブジェクトが、コンポーネントインスタンス自体の代わりにインジェクトされます。

次のアノテーションは ファクトリコンポーネント パターンをサポートします。 Seam コンポーネントはコンテキスト変数の値の初期化を行います。 特に非 Faces リクエストに対するレスポンスのレンダリングに必要なあらゆる状態の初期化に便利です。 コンポーネントメソッドで指定されます。

@Factory
@Factory("processInstance")

コンテキスト変数に値がない場合に、 このコンポーネントのメソッドが指定コンテキスト変数の値の初期化に使用されることを指定します。 このスタイルは void を返すメソッドと併用します。

@Factory("processInstance", scope=CONVERSATION)

コンテキスト変数に値がない場合、 Seam が指定コンテキスト変数の値の初期化に使用する値をこのメソッドが返すことを指定します。 このスタイルは値を返すメソッドと併用します。 明示的にスコープが指定されていない場合、 @Factory メソッドを持つコンポーネント自体のスコープが使用されます (そのコンポーネントがステートレスではない場合。 コンポーネントがステートレスである場合はEVENT コンテキストが使用される) 。

  • value — コンテキスト変数名を指定します。 メソッドが getter メソッドなら、 デフォルトは JavaBeans プロパティ名となります。

  • scope — Seam が返された値をバインドする先のスコープを指定します。 値を返すファクトリメソッドに対してしか意味がありません。

Log をインジェクトするアノテーション:

@Logger
@Logger("categoryName")

コンポーネントフィールドに org.jboss.seam.log.Log のインスタンスをインジェクトしますします。 エンティティ Bean の場合、 このフィールドは static として宣言されなければなりません。

  • value — ログカテゴリ名を指定します。 デフォルトはコンポーネントクラス名です。

最後のアノテーション、リクエストパラメータ値のインジェクト:

@RequestParameter
@RequestParameter("parameterName")

コンポーネントの属性にリクエストパラメータ値をインジェクトします。 基本的な対話は自動的に開始されます。

  • value — リクエストパラメータ名を指定します。 デフォルトはコンポーネント属性名です。

22.3. コンポーネントのライフサイクルメソッド用アノテーション

これらのアノテーションにより、 コンポーネントがそのコンポーネント自体のライフサイクルイベントに対して反応することができるようになります。 各コンポーネントクラスごとにそれぞれ 1 つのアノテーションのみ定義できます。

@Create
@Create

コンポーネントのインスタンスが Seam によってインスタンス化されたときに呼び出されるメソッドを指定します。 create メソッドは JavaBeans およびステートフルセッション Bean に対してしかサポートされないので注意してください。

@Destroy
@Destroy

コンテキストが終了し、そのコンテキスト変数が破棄されるときの呼び出されるメソッドを指定します。 destroy メソッドは JavaBeans およびステートフルセッション Bean に対してしかサポートされないので注意してください。

コンテキストが終了したときにステートフル Bean を必ず破棄するようにするため、 すべてのステートフルセッション Bean コンポーネントには @Destroy @Remove アノテーションを付けたメソッドを定義するようにしてください。

Destroy メソッドはクリーンアップにのみ使用するようにしてください。 Seam は destroy メソッドから伝播する例外はすべてキャッチしてログを出力し、捨ててしまいます。

@Observer
@Observer("somethingChanged")

指定されたタイプのコンポーネント駆動イベントが発生すると、このメソッドが呼び出されます。

@Observer(value="somethingChanged",create=false)

指定されたタイプのイベントが発生するとこのメソッドが呼び出されますが、 インスタンスが存在しない場合はインスタンスを作成しないことを指定します。 インスタンスが存在せず create が false の場合は、 そのイベントは捕捉されません。 create のデフォルト値は true です。

22.4. コンテキスト境界用アノテーション

これらのアノテーションは宣言的対話の境界を設定します。 これらは Seam コンポーネントのメソッド上、通常はアクションリスナメソッドに付与されます。

すべての WEB リクエストはそれに関連する対話的コンテキストを持っています。 ほとんどの対話はリクエストの終了と同時に終了します。 複数のリクエストにわたる対話が必要であれば、@Begin を付けたメソッドを呼び出すことで、 長期対話に昇格させなければなりません。

@Begin
@Begin

このメソッドが例外および null 以外の outcome を返したら長期対話が開始することを指定します。

@Begin(ifOutcome={"success", "continue"})

このアクションリスナメソッドが指定された outcome を返したら長期対話が開始することを指定します。

@Begin(join=true)

長期対話がすでに開始されている場合、 対話コンテキストが単に伝播されることを指定します。

@Begin(nested=true)

長期対話がすでに開始されている場合、 新たにネストされた対話コンテキストが開始することを指定します。 次の @End が出現したときにネストされた対話が終了し、 外側の対話が再開します。 同じ外側の対話において、 複数のネストされた対話が同時に存在することは全く問題ありません。

@Begin(pageflow="process definition name")

この対話のためのページフローを定義する jBPM プロセス定義の名前を定義します。

@Begin(flushMode=FlushModeType.MANUAL)

Seam 管理の永続コンテキストのフラッシュモードを指定します。 flushMode=FlushModeType.MANUALatomic conversations をサポートします。 この場合、 flush () (通常、 対話終了時に呼び出される) の明示的なコールが起きるまで、 すべての書き込み動作は対話コンテキスト内にキューイングされます。

  • ifOutcome — 新しい長期対話コンテキストとなるJSF outcome (s) を指定します。

  • join — 長期対話がすでに開始されている場合の動作を指定します。 true であれば、 コンテキストは伝播されます。 false であれば、 例外が発生します。 デフォルトは false です。 nested=true が指定される場合は、 この設定は無視されます。

  • nested — 長期対話がすでに開始されている場合、 ネストされた対話が開始されることを指定します。

  • flushMode — この対話で作成される Seam 管理の Hibernate セッション、または JPA 永続コンテキストのフラッシュモードをセットします。

  • pagefloworg.jboss.seam.core.jbpm.pageflowDefinitions. によってデプロイされた jBPM プロセス定義のプロセス定義名です。

@End
@End

このメソッドが例外および null 以外の outcome を返す場合、 長期対話が終了することを指定します。

@End(ifOutcome={"success", "error"}, evenIfException={SomeException.class, OtherException.class})

このアクションリスナメソッドが指定された outcome の 1 つを返すか、あるいは指定された例外の 1 つをスローする場合に、 長期対話が終了することを指定します。

  • ifOutcome — JSF 結果あるいは現在の長期対話の終了となるような結果を指定します。

  • beforeRedirect — デフォルトでは、 なんらかのリダイレクトが発生するまで対話は実際には破棄されません。 beforeRedirect=true と設定すると、 現在のリクエストの終了で対話が破棄され、 リダイレクトが新しいテンポラリ対話コンテキストで処理されるよう指定することになります。

@StartTask
@StartTask

jBPM タスクを「開始」します。 このメソッドが例外および null 以外の outcome を返すとき、 長期対話を開始することを指定します。 この対話は指定のリクエストパラメータ中で指定される jBPM タスクと関連しています。 この対話のコンテキスト内で、 タスクインスタンスのビジネスプロセスインスタンスに対して、 ビジネスプロセスコンテキストも定義されます。

jBPM TaskInstance は、 taskInstance という名前のリクエストコンテキスト変数として利用可能となります。 jBPM ProcessInstance は、 processInstance という名前のリクエストコンテキスト変数として利用可能です。 (もちろん、これらのオブジェクトは @In でインジェクションが可能です。)

  • taskIdParameter — タスクの ID を持つリクエストパラメータの名前です。 デフォルトは literal>"taskId"

  • flushMode — この対話で作成される Seam 管理の Hibernate セッション、または JPA 永続コンテキストのフラッシュモードをセットします。

@BeginTask
@BeginTask

完了していない jBPM タスクの処理を再開します。 このメソッドが例外および null 以外の outcome を返すとき、 長期対話が開始することを指定します。 この対話は指定のリクエストパラメータ中で指定される jBPM タスクと関連しています。 この対話のコンテキスト内で、 タスクインスタンスのビジネスプロセスインスタンスに対して、 ビジネスプロセスコンテキストも定義されます。

jBPM TaskInstance は 、 taskInstance という名前のリクエストコンテキスト変数として利用可能になります。 jBPM ProcessInstance は、 processInstance という名前のリクエストコンテキスト変数として利用できます。

  • taskIdParameter — タスクの ID を持つリクエストパラメータの名前です。 デフォルトは literal>"taskId"

  • flushMode — この対話で作成される Seam 管理の Hibernate セッション、または JPA 永続コンテキストのフラッシュモードをセットします。

@EndTask
@EndTask

jBPM タスクを「終了」します。 このメソッドが null 以外の結果を返すとき、 長期対話は終了し、 現在のタスクが完了することを指定します。 jBPM トランジションを引き起こします。 アプリケーションが transition と呼ばれる組込みコンポーネントの Transition.setName () を呼んでいない限り、 引き起こされる実際のトランジッションはデフォルトのトランザクションになります。

@EndTask(transition="transitionName")

指定された jBPM トランジションを引き起こします。

@EndTask(ifOutcome={"success", "continue"})

このメソッドが指定された outcome のいずれかを返すとき、タスクが終了することを指定します。

  • transition — タスクが終了するときに引き起こされる jBPM トランジションの名前です。 省略された場合はデフォルトトランジションとなります。

  • ifOutcome — タスクを終了させる JSF outcome を指定します。

  • beforeRedirect — デフォルトでは、 なんらかのリダイレクトが発生するまで対話は実際には破棄されません。 beforeRedirect=true と設定すると、 現在のリクエストの終了で対話が破棄され、 リダイレクトが新しいテンポラリ対話コンテキストで処理されるよう指定することになります。

@CreateProcess
@CreateProcess(definition="process definition name")

メソッドが例外および null 以外の outcome を返すとき、 新しい jBPM プロセスインスタンスを作成します。 ProcessInstance オブジェクトは processInstance というコンテキスト変数として使用できます。

  • definitionorg.jboss.seam.core.jbpm.processDefinitions によってデプロイされる jBPM プロセス定義の名前です。

@ResumeProcess
@ResumeProcess(processIdParameter="processId")

メソッドが例外または null 以外の outcome を返すとき、 既存の jBPM プロセスインスタンスのスコープに再度入ります。 ProcessInstance オブジェクトは processInstance というコンテキスト変数で使用できます。

  • processIdParameter — プロセス ID を持つリクエストパラメータ名です。 デフォルトは "processId" です。

22.5. トランザクション境界用のアノテーション

Seam は特定のアクションリスナの outcome に対すて JTA トランザクションのロールバックを強制するアノテーションを提供します。

@Rollback
@Rollback(ifOutcome={"failure", "not-found"})

メソッドの結果が指定された outcome のいずれかと一致する場合、 あるいは outcome を指定していない場合、 メソッドが完了したときにトランザクションがロールバックされるよう設定されます。

  • ifOutcome — トランザクションロールバックを引き起こす JSF の outcome です。 (outcome を指定しない場合、全ての outcome が指定されたものと解釈されます。)

@Transactional
@Transactional

JavaBean コンポーネントにセッション Bean コンポーネントのデフォルト動作と同じようなトランザクション動作を持たせることを指定します。 例えば、 メソッド呼び出しはトランザクション内で起こるべきであり、 メソッドが呼び出されたときにトランザクションが存在しない場合は、 トランザクションがそのメソッドのためだけに開始されます。 このアノテーションはクラスレベルでもメソッドレベルでも適用可能です。

Seam アプリケーションは通常、 トランザクション境界については標準の EJB3 のアノテーションを使用します。

22.6. 例外用のアノテーション

これらのアノテーションにより Seam コンポーネントから伝播する例外を処理する方法を指定することができます。

@Redirect
@Redirect(viewId="error.jsp")

このアノテーション付いている例外は、指定されたビュー ID にブラウザをリダイレクトします。

  • viewId — リダイレクトする JSF ビュー ID です。

  • message — 表示するメッセージです。 デフォルトはその例外のメッセージです。

  • end — 長期対話が終了するよう指定します。 デフォルトは false です。

@HttpError
@HttpError(errorCode=404)

このアノテーションが付いている例外は、 HTTP エラーが送信されます。

  • errorCode — HTTP エラーコードです。 デフォルトは 500 です。

  • message — HTTP エラーで送信されるメッセージです。 デフォルトはその例外のメッセージです。

  • end — 長期対話が終了するよう指定します。 デフォルトは false です。

22.7. バリデーション用のアノテーション

このアノテーションは Hibernate Validator を起動します。 Seam コンポーネントのメソッド、主にアクションリスナメソッドに付与されます。

Hibernate Validator フレームワークにより定義されたアノテーションに関する詳細は、 Hibernate Annotations パッケージのドキュメントを参照してください。

@IfInvalid の使用はほぼ廃止予定になっており、 現在は <s:validateAll> の使用を推奨しているので注意してください。

@IfInvalid
@IfInvalid(outcome="invalid", refreshEntities=true)

メソッドが呼び出される前に Hibernate Validator がコンポーネントを検証するよう指定します。 呼び出しが失敗した場合、 指定された outcocme が返され、 Hibernate Validator から返される検証失敗のメッセージが FacesContext に追加されます。 これ以外は、 呼び出しは続行されます。

  • outcome — 検証が失敗したときの JSF の outcome です。

  • refreshEntities — 検証が失敗したときに、 状態管理されている無効なエンティティは、 データベースの値でリフレッシュされます。 デフォルトは false です。 (拡張永続コンテキストで有用です。)

22.8. Seam Remoting用のアノテーション

Seam Remotingは、 以下のアノテーションを付けた セッション Bean のローカルインタフェースが必要です。

@WebRemote
@WebRemote(exclude="path.to.exclude")

このアノテーション付きのメソッドはクライアントサイドの JavaScript から呼ばれる可能性があることを示しています。 exclude プロパティはオプションで、 オブジェクトがその結果のオブジェクトグラフから除外されるようにすることができます (詳細は Remoting の章を参照) 。

22.9. Seam インターセプタ用のアノテーション

以下のアノテーションは、Seam インターセプタクラスで使われます。

EJB インターセプタ定義に必要なアノテーションに関する詳細は EJB 3.0 仕様のドキュメントを参照してください。

@Interceptor
@Interceptor(stateless=true)

このインターセプタはステートレスであることを指定するので、 Seam は複製処理を最適化することができます。

@Interceptor(type=CLIENT)

このインターセプタは EJB コンテナより前に呼ばれる「クライアントサイド」インターセプタであることを指定します。

@Interceptor(around={SomeInterceptor.class, OtherInterceptor.class})

このインターセプタは特定のインターセプタよりスタック内でより高い位置に配置されることを指定します。

@Interceptor(within={SomeInterceptor.class, OtherInterceptor.class})

このインターセプタは特定のインターセプタよりスタック内でより深い位置に配置されることを指定します。

22.10. 非同期用のアノテーション

次のアノテーションは非同期メソッドの宣言に使用されます。 例:

@Asynchronous public void scheduleAlert(Alert alert, @Expiration Date date) { ... }
@Asynchronous public Timer scheduleAlerts(Alert alert, @Expiration Date date, @IntervalDuration long interval) { ... }
@Asynchronous
@Asynchronous

メソッドコールは非同期で処理されることを指定します。

@Duration
@Duration

非同期コールのパラメータが、 そのコールが処理されるまでの期間であることを指定します (または反復コールの場合は初めての処理が行われるまで) 。

@Expiration
@Expiration

非同期コールのパラメータが、 そのコールが処理される (または反復コールの場合は初めての処理が行われる) 日付と時刻であることを指定します。

@IntervalDuration
@IntervalDuration

このアノテーションが付いている再帰呼び出しを行う非同期メソッドコールのパラメータが、 再帰呼び出し間の期間であることを指定します。

22.11. JSF dataTable と併用するアノテーション

以下のアノテーションはステートフルセッション Bean を使ったクリッカブルリストの実装を容易にします。 これらのアノテーションは属性に付与されます。

@DataModel
@DataModel("variableName")

ListMapSetObject[] のタイプの属性を JSF の DataModel として所有するコンポーネントのスコープで公開します。 (あるいは、所有するコンポーネントが STATELESS の場合は EVENT スコープとなります) 。 Map の場合、 DataModel の各行は、 Map.Entry になります。

  • value — 対話コンテキスト変数の名前です。 デフォルトは属性名です。

  • scopescope=ScopeType.PAGE が明示的に指定される場合、 DataModelPAGE コンテキストに保持されます。

@DataModelSelection
@DataModelSelection

JSF DataModel から選択された値をインジェクトします。 ( これは、内在する Collection の要素、または Map の値です。)

  • value — 対話コンテキスト変数の名前です。 コンポーネントの中に @DataModel が一つだけの場合は不要です。

@DataModelSelectionIndex
@DataModelSelectionIndex

JSF の DataModel の選択されたインデックスをコンポーネントの属性として公開します。 (これは、 Collection の行番号、または Map のキーとなります。)

  • value — 対話コンテキスト変数の名前です。 コンポーネントの中に @DataModel が一つだけの場合は不要です。

22.12. データバインディング用のメタアノテーション

これらのメタアノテーションは、一覧とは別に他のデータベース構造に対して @DataModel@DataModelSelection と同様の機能の実装を可能にします。

@DataBinderClass
@DataBinderClass(DataModelBinder.class)

アノテーションがデータバインディングのアノテーションであることを指定します。

@DataSelectorClass
@DataSelectorClass(DataModelSelector.class)

アノテーションがデータ選択のアノテーションであることを指定します。

22.13. パッケージング用のアノテーション

このアノテーションは、 一緒にパッケージングするコンポーネントセットに関する情報を宣言するメカニズムを提供します。 どの Java パッケージに対しても適用できます。

@Namespace
@Namespace(value="http://jboss.com/products/seam/example/seampay")

現在のパッケージにあるコンポーネントを特定のネームスペースに関連付けられることを指定します。 宣言されたネームスペースは components.xml ファイル内で XML ネームスペースとして使用することでアプリケーションの設定を単純化することができます。

@Namespace(value="http://jboss.com/products/seam/core", prefix="org.jboss.seam.core")

ネームスペースを特定のパッケージに関連付けるよう指定します。 また、 コンポーネント名のプレフィックスが XML ファイル内で指定されるコンポーネント名に適用されるよう指定します。 たとえば、 このネームスペースに関連付けられる microcontainer という XML エレメントは実際には org.jboss.seam.core.microcontainer というコンポーネントを参照するように解釈されます。

第23章 組み込み Seam コンポーネント

本章では Seam の組み込みコンポーネント、 その設定プロパティについて説明していきます。

@Name を使って独自のクラスで組み込みコンポーネントの名前を指定すると、 組み込みコンポーネントを独自の実装に簡単に置き換えることができます。

また、 組み込みコンポーネントは修飾名を使用しますが、 そのほとんどはデフォルトで非修飾名にエイリアスされているので注意してください。 こうしたエイリアスは auto-create="true" を指定するため、 組み込みコンポーネントをその非修飾名でインジェクトする場合に create=true を使う必要はありません。

23.1. コンテキストインジェクションのコンポーネント

最初の組み込みコンポーネントセットは、 単純にさまざまな文脈上のオブジェクトのインジェクトをサポートするために存在しています。 たとえば、 次のコンポーネントインスタンスの変数はインジェクトされた Seam セッションのコンテキストオブジェクトを持つことになります。

@In private Context sessionContext;
org.jboss.seam.core.eventContext

イベントコンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.pageContext

ページコンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.conversationContext

対話コンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.sessionContext

セッションコンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.applicationContext

アプリケーションコンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.businessProcessContext

ビジネスプロセスコンテキストオブジェクトの管理コンポーネント

org.jboss.seam.core.facesContext

FacesContext コンテキストオブジェクト (正確には Seam コンテキストではない) の管理コンポーネント

これらコンポーネントはすべて常にインストールされます。

23.2. ユーティリティコンポーネント

これらのコンポーネントが役に立つ機会はあまりありません。

org.jboss.seam.core.facesMessages

ブラウザリダイレクト全体に伝播するよう faces が正しくメッセージングできるようにします。

  • add(FacesMessage facesMessage) — faces メッセージを追加します。 このメッセージは現在の変換で発生する次のレンダリング応答フェーズ中に表示されます。

  • add(String messageTemplate) — EL 表現を含んでいる可能性がある特定のメッセージテンプレートからレンダリングされる faces メッセージを追加します。

  • add(Severity severity, String messageTemplate) — EL 表現を含んでいる可能性がある特定のメッセージテンプレートからレンダリングされる faces メッセージを追加します。

  • addFromResourceBundle(String key) — Seam リソースバンドル内で定義されるメッセージテンプレートからレンダリングされる faces メッセージを追加します。 EL 表現を含んでいる可能性があります。

  • addFromResourceBundle(Severity severity, String key) — Seam リソースバンドル内で定義されるメッセージテンプレートからレンダリングされる faces メッセージを追加します。 EL 表現を含んでいる可能性があります。

  • clear() — 全てのメッセージを消去します。

org.jboss.seam.core.redirect

パラメータつきでリダイレクトを行う場合に便利な API です (特にブックマーク可能な検索結果画面などに役立ちます)。

  • redirect.viewId — リダイレクト先の JSF ビュー ID です。

  • redirect.conversationPropagationEnabled — リダイレクト全体に変換が伝播するかどうかを確定します。

  • redirect.parameters — 値に対するリクエストパラメータ名のマップです。 リダイレクトリクエスト内で渡されます。

  • execute() — 直ちにリダイレクトを行います。

  • captureCurrentRequest() — 現在の GET リクエスト (対話コンテキスト内) のリクエストパラメータとビュー ID を格納します。 後で execute() をコールして使用します。

org.jboss.seam.core.httpError

HTTP エラーを送信する場合に便利な API です。

org.jboss.seam.core.events

@Observer のメソッドまたは components.xml 内のメソッドバインディング経由で監視できるイベントを引き起こす API です。

  • raiseEvent(String type) — 特定タイプのイベントを引き起こし、 監視している人全員に配信します。

  • raiseAsynchronousEvent(String type) — EJB3 タイマーサービスにより非同期的に処理されるイベントを引き起こします。

  • raiseTimedEvent(String type, ....) — EJB3 タイマーサービスにより非同期的に処理されるイベントをスケジュールします。

  • addListener(String type, String methodBinding) — 特定イベントタイプの監視者を追加します。

org.jboss.seam.core.interpolator

Strings に JFS EL 表現の値を補完するための API です。

  • interpolate(String template)#{...} 形式の JSF EL 表現用のテンプレートをスキャンし、 それを評価された値と置き換えます。

org.jboss.seam.core.expressions

値とメソッドバインティングを作成するための API です。

  • createValueBinding(String expression) — 値バインディングオブジェクトを作成します。

  • createMethodBinding(String expression) — メソッドバインディングオブジェクトを作成します。

org.jboss.seam.core.pojoCache

JBoss Cache PojoCache インスタンスの管理コンポーネントです。

  • pojoCache.cfgResourceName — 設定ファイルの名前です。 treecache.xml にデフォルトで設定します。

org.jboss.seam.core.uiComponent

EL からの ID で JSF UIComponent へのアクセスを許可します。 たとえば、 @In("#{uiComponent['myForm:address'].value}") と記述することができます。

これらコンポーネントはすべて常にインストールされます。

23.3. 国際化とテーマのコンポーネント

次のコンポーネントグループは Seam を使用した国際化ユーザーインターフェースのビルドを容易にします。

org.jboss.seam.core.locale

Seam のロケールです。 このロケールはセッションスコープです。

org.jboss.seam.core.timezone

Seam のタイムゾーンです。 タイムゾーンはセッションスコープです。

org.jboss.seam.core.resourceBundle

Seam リソースバンドルです。 リソースバンドルはセッションスコープです。 Seam リソースバンドルは Java リソースバンドルの一覧でキーの縦型検索を行います。

  • resourceBundle.bundleNames — 検索する Java リソースバンドルの名前です。 デフォルトで messages に設定されます。

org.jboss.seam.core.localeSelector

設定時間またはランタイム時のユーザーのいずれかでロケール選択をサポートします。

  • select() — 指定されたロケールを選択します。

  • localeSelector.locale — 実際の java.util.Locale です。

  • localeSelector.localeString — ロケールの文字列表現です。

  • localeSelector.language — 指定ロケールの言語です。

  • localeSelector.country — 指定ロケールの国名です。

  • localeSelector.variant — 指定ロケールのバリアントです。

  • localeSelector.supportedLocalesjsf-config.xml 内に一覧表示されるサポートロケールをを表している SelectItem の一覧です。

  • localeSelector.cookieEnabled — クッキーで存続されるべきロケール選択を指定します。

org.jboss.seam.core.timezoneSelector

設定時間またはランタイム時のユーザーのいずれかでタイムゾーン選択をサポートします。

  • select() — 指定されたロケールを選択します。

  • timezoneSelector.timezone — 実際の java.util.TimeZone です。

  • timezoneSelector.timeZoneId — タイムゾーンの文字列表現です。

  • timezoneSelector.cookieEnabled — クッキーで存続されるべきタイムゾーン選択を指定します。

org.jboss.seam.core.messages

Seam リソースバンドル内で定義されるメッセージテンプレートからレンダリングされる国際化メッセージを含んでいるマップです。

org.jboss.seam.theme.themeSelector

設定時間またはランタイム時のユーザーのいずれかでテーマ選択をサポートします。

  • select() — 指定されたテーマを選択します。

  • theme.availableThemes — 定義されたテーマの一覧です。

  • themeSelector.theme — 選択されたテーマです。

  • themeSelector.themes — 定義されたテーマを示している SelectItem の一覧です。

  • themeSelector.cookieEnabled — クッキーで存続されるべきテーマ選択を指定します。

org.jboss.seam.theme.theme

テーマエントリを含んでいるマップです。

これらコンポーネントはすべて常にインストールされます。

23.4. 対話を制御するためのコンポーネント

次のコンポーネントグループを使うとアプリケーションまたはユーザーインターフェースにより対話の制御を行うことができるようになります。

org.jboss.seam.core.conversation

現在の Seam 対話の属性をアプリケーション制御するための API です。

  • getId() — 現在の対話 ID を返します。

  • isNested() — 現在の対話がネストされる場合 true を返します。

  • isLongRunning() — 現在の対話が長期の対話の場合 true を返します。

  • getId() — 現在の対話 ID を返します。

  • getParentId() — 親対話の対話 ID を返します。

  • getRootId() — root 対話の対話 ID を返します。

  • setTimeout(int timeout) — 現在の対話のタイムアウトを設定します。

  • setViewId(String outcome) — conversation switcher、 conversation list、 breadcrumbs のいずれかから現在の対話に切り替える場合に使用されるビュー ID を設定します。

  • setDescription(String description) — conversation switcher、 conversation list、 または breadcrumbs で表示される現在の対話の詳細を設定します。

  • redirect() — この対話に対して詳細に定義された最後のビューにリダイレクトします (ログイン試行後に便利)。

  • leave() — 実際に対話を終了することなく、 この対話のスコープを終了します。

  • begin() — 長期の対話を開始します (@Begin と同等)。

  • beginPageflow(String pageflowName) — ページフロー付きの長期の対話を開始します (@Begin(pageflow="...") と同等)。

  • end() — 長期の対話を終了します (@End と同等)。

  • pop() — 親対話に戻り対話スタックをポップします。

  • root() — 対話スタックの root 対話に戻ります。

  • changeFlushMode(FlushModeType flushMode) — 対話のフラッシュモードを変更します。

org.jboss.seam.core.conversationList

対話一覧の管理コンポーネントです。

org.jboss.seam.core.conversationStack

対話スタックの管理コンポーネントです (breadcrumbs)。

org.jboss.seam.core.switcher

conversation switcher です。

これらコンポーネントはすべて常にインストールされます。

23.5. jBPM 関連のコンポーネント

jBPM と併用するコンポーネントになります。

org.jboss.seam.core.pageflow

Seam ページフローの API 制御です。

  • isInProcess() — 現在プロセスにページフローがある場合に true を返します。

  • getProcessInstance() — 現在のページフローに対して jBPM ProcessInstance を返します。

  • begin(String pageflowName) — 現在の対話のコンテキストでページフローを開始します。

  • reposition(String nodeName) — 現在のページフローを特定のノードに再配置します。

org.jboss.seam.core.actor

現在のセッションに関する jBPM actor の属性をアプリケーション制御するための API です。

  • setId(String actorId) — 現在のユーザーの jBPM アクター ID をセットします。

  • getGroupActorIds() — 追加される可能性のある現在のユーザーのグループに対する jBPM actor ids に Set を返します。

org.jboss.seam.core.transition

現在のタスクに対する jBPM 移行のアプリケーション制御を目的とする API です。

  • setName(String transitionName) — 現在のタスクが @EndTask 経由で終了される場合に使用される jBPM 移行名をセットします。

org.jboss.seam.core.businessProcess

対話とビジネスプロセス間の関連性をプログラム制御するための API です。

  • businessProcess.taskId — 現在の対話と関連付けられているタスクの ID です。

  • businessProcess.processId — 現在の対話と関連付けられているプロセスの ID です。

  • businessProcess.hasCurrentTask() — 現在の対話と関連付けられているタスクインスタンスです。

  • businessProcess.hasCurrentProcess() — 現在の対話と関連付けられているプロセスインスタンスです。

  • createProcess(String name) — 名前付けされたプロセス定義のインスタンスを作成して現在の対話と関連付けます。

  • startTask() — 現在の対話と関連付けされているタスクを開始します。

  • endTask(String transitionName) — 現在の対話と関連付けられているタスクを終了します。

  • resumeTask(Long id) — 特定の ID を持つタスクを現在の対話と関連付けます。

  • resumeProcess(Long id) — 特定の ID を持つプロセスを現在の対話と関連付けます。

  • transition(String transitionName) — 移行を誘発します。

org.jboss.seam.core.taskInstance

jBPM TaskInstance の管理コンポーネントです。

org.jboss.seam.core.processInstance

jBPM ProcessInstance の管理コンポーネントです。

org.jboss.seam.core.jbpmContext

イベントスコープ JbpmContext の管理コンポーネントです。

org.jboss.seam.core.taskInstanceList

jBPM task list の管理コンポーネントです。

org.jboss.seam.core.pooledTaskInstanceList

jBPM pooled task list の管理コンポーネントです。

org.jboss.seam.core.taskInstanceListForType

jBPM タスクリスト集の管理コンポーネントです。

org.jboss.seam.core.pooledTask

pooled task 割り当てのアクションハンドラです。

org.jboss.seam.core.jbpm がインストールされると、 これらの全コンポーネントが必ずインストールされます。

23.6. セキュリティ関連のコンポーネント

これらのコンポーネントはウェブ層のセキュリティに関連しています。

org.jboss.seam.core.userPrincipal

現在のユーザー Principal の管理コンポーネントです。

org.jboss.seam.core.isUserInRole

現在の principal が使用できるロールに応じて、 JSF ページがコントロールのレンダリングを選択できるようにします。 <h:commandButton value="edit" rendered="#{isUserInRole['admin']}"/>

23.7. JMS 関連のコンポーネント

これらのコンポーネントは管理対象の TopicPublisher および QueueSender との併用を目的としています (下記参照)。

org.jboss.seam.jms.queueSession

JMS QueueSession の管理コンポーネントです。

org.jboss.seam.jms.topicSession

JMS TopicSession の管理コンポーネントです。

23.8. メール関連のコンポーネント

Seam の Email サポートと併用して使用されるコンポーネントです。

org.jboss.seam.mail.mailSession

JavaMail Session の管理コンポーネントです。

  • org.jboss.seam.mail.mailSession.host — 使用する SMTP サーバーのホスト名です。

  • org.jboss.seam.mail.mailSession.port — 使用する SMTP サーバーのポートです。

  • org.jboss.seam.mail.mailSession.username — SMTP サーバーへの接続に使用するユーザー名です。

  • org.jboss.seam.mail.mailSession.password — SMTP サーバーへの接続に使用するパスワードです。

  • org.jboss.seam.mail.mailSession.debug — JavaMail のデバッグを有効にします (非常に冗長)。

  • org.jboss.seam.mail.mailSession.sessionJndiName — JNDI に対してバウンドされる javax.mail.Session 配下の名前です。

23.9. 基盤となるコンポーネント

非常に重要となるプラットフォームの基盤を提供するコンポーネントです。 org.jboss.seam.core.init.componentClasses 設定プロパティでコンポーネントのクラス名を含ませるとインストールすることができます。

org.jboss.seam.core.init

Seam の初期化設定です。 常にインストールされます。

  • org.jboss.seam.core.init.jndiPattern — セッションビーンのルックアップに使用される JNDI パターンです。

  • org.jboss.seam.core.init.debug — Seam デバッグモードを有効にします。

  • org.jboss.seam.core.init.clientSideConversationstrue にセットすると、 Seam は対話コンテキストの変数を HttpSession 内に保存せずクライアント内に保存するようになります。

  • org.jboss.seam.core.init.userTransactionName — JTA UserTransaction オブジェクトをルックアップする場合に使用する JNDI 名です。

org.jboss.seam.core.manager

Seam ページおよび対話コンテキスト管理用の内部コンポーネントです。 常にインストールされます。

  • org.jboss.seam.core.manager.conversationTimeout — 対話コンテキストのミリ秒単位のタイムアウトです。

  • org.jboss.seam.core.manager.concurrentRequestTimeout — 長期の対話コンテキストでロックの取得試行するスレッドに対する最大待機時間です。

  • org.jboss.seam.core.manager.conversationIdParameter — 対話 ID の伝播に使用されるリクエストパラメータで、 デフォルトでは conversationId に設定されます。

  • org.jboss.seam.core.manager.conversationIsLongRunningParameter — 対話が長期であるかどうかに関する情報を伝播するために使用されるリクエストパラメータで、 デフォルトでは conversationIsLongRunning に設定されます。

org.jboss.seam.core.pages

Seam ワークスペースの管理用の内部コンポーネントです。 常にインストールされます。

  • org.jboss.seam.core.pages.noConversationViewId — サーバー側に対話エントリが見つからない場合にリダイレクトするリダイレクト先のビュー ID のグローバル設定です。

org.jboss.seam.core.ejb

JBoss Embeddable EJB3 コンテナをブートストラップします。 クラス org.jboss.seam.core.Ejb としてインストールします。 Java EE 5 アプリケーションサーバーのコンテキストの外にある EJB コンポーネントと Seam を併用する場合に便利です。

基本的な Embedded EJB 設定は jboss-embedded-beans.xml で定義されます。 追加のマイクロコンテナ設定 (例、 追加データソース) は jboss-beans.xml またはクラスパス内の META-INF/jboss-beans.xml で指定することができます。

org.jboss.seam.core.microcontainer

JBoss マイクロコンテナをブートストラップします。 クラス org.jboss.seam.core.Microcontainer としてインストールします。 Seam を Hibernate と併用し Java EE アプリケーションサーバーのコンテキストの外には EJB コンポーネントがない場合に便利です。 マイクロコンテナは JNDI、 JTA、 JCA データソース及び Hibernate で部分的な EE 環境を実現します。

マイクロコンテナの設定は jboss-beans.xml またはクラスパス内の META-INF/jboss-beans.xml で指定することができます。

org.jboss.seam.core.jbpm

JbpmConfiguration をブートストラップします。クラス org.jboss.seam.core.Jbpm としてインストールします。

  • org.jboss.seam.core.jbpm.processDefinitions — ビジネスプロセス群の編成に使用される jPDL ファイル群のリソース名の一覧です。

  • org.jboss.seam.core.jbpm.pageflowDefinitions — 対話ページフローの編成に使用される jPDL ファイル群のリソース名の一覧です。

org.jboss.seam.core.conversationEntries

リクエスト間のアクティブな長期の対話を記録するセションスコープの内部コンポーネントです。

org.jboss.seam.core.facesPage

ページに関連付けられる対話コンテキストを記録するページスコープの内部コンポーネントです。

org.jboss.seam.core.persistenceContexts

現在の対話に使用された永続コンテキストを記録する内部コンポーネントです。

org.jboss.seam.jms.queueConnection

JMS QueueConnection を管理します。 管理 QueueSender がインストールされると必ずインストールされます。

  • org.jboss.seam.jms.queueConnection.queueConnectionFactoryJndiName — JMS QueueConnectionFactory の JNDI 名です。 デフォルトでは UIL2ConnectionFactory に設定されます。

org.jboss.seam.jms.topicConnection

JMS TopicConnection を管理します。 管理 TopicPublisher がインストールされると必ずインストールされます。

  • org.jboss.seam.jms.topicConnection.topicConnectionFactoryJndiName — JMS TopicConnectionFactory の JNDI 名です。 デフォルトでは UIL2ConnectionFactory に設定されます。

org.jboss.seam.persistence.persistenceProvider

JPA プロバイダの標準化されていない機能に対する抽出層です。

org.jboss.seam.core.validation

Hibernate Validator サポート用の内部コンポーネントです。

org.jboss.seam.debug.introspector

Seam Debug Page のサポートです。

org.jboss.seam.debug.contexts

Seam Debug Page のサポートです。

23.10. 特殊なコンポーネント

特定の特殊な Seam コンポーネントクラスは Seam 設定内で指定される name の配下で複数回のインストールが可能です。 例えば、 components.xml 内の次の行は Seam コンポーネントを 2 つインストールして設定します。

<component name="bookingDatabase"
          class="org.jboss.seam.core.ManagedPersistenceContext">
    <property name="persistenceUnitJndiName">java:/comp/emf/bookingPersistence</property>
</component>

<component name="userDatabase"
          class="org.jboss.seam.core.ManagedPersistenceContext">
    <property name="persistenceUnitJndiName">java:/comp/emf/userPersistence</property>
</component>

Seam コンポーネント名は bookingDatabaseuserDatabase です。

<entityManager>, org.jboss.seam.core.ManagedPersistenceContext

拡張永続コンテキストを持つ対話スコープで管理対象の EntityManager の管理コンポーネントです。

  • <entityManager>.entityManagerFactoryEntityManagerFactory のインスタンスに対して評価を行う値バインディングです。

    <entityManager>.persistenceUnitJndiName — entity manager factory の JNDI 名です。 デフォルトでは java:/<managedPersistenceContext> に設定します。

<entityManagerFactory>, org.jboss.seam.core.EntityManagerFactory

JPA EntityManagerFactory を管理します。 EJB 3.0 サポート環境以外で JPA を使用する場合に最適となります。

  • entityManagerFactory.persistenceUnitName — 永続ユニット名です。

設定プロパティの詳細については API JavaDoc をご覧ください。

<session>, org.jboss.seam.core.ManagedSession

対話スコープで管理対象の Hibernate Session の管理コンポーネントです。

  • <session>.sessionFactorySessionFactory のインスタンスに対して評価を行う値バインディング表現です。

    <session>.sessionFactoryJndiName — session factory の JNDI 名です。 デフォルトでは java:/<managedSession> に設定します。

<sessionFactory>, org.jboss.seam.core.HibernateSessionFactory

Hibernate SessionFactory を管理します。

  • <sessionFactory>.cfgResourceName — 設定ファイルへのパスです。 デフォルトでは hibernate.cfg.xml に設定されます。

設定プロパティの詳細については API JavaDoc をご覧ください。

<managedQueueSender>, org.jboss.seam.jms.ManagedQueueSender

イベントスコープで管理対象の JMS QueueSender の管理コンポーネントです。

  • <managedQueueSender>.queueJndiName — JMS キューの JNDI 名です。

<managedTopicPublisher>, org.jboss.seam.jms.ManagedTopicPublisher

イベントスコープで管理対象の JMS TopicPublisher の管理コンポーネントです。

  • <managedTopicPublisher>.topicJndiName — JMS トピックの JNDI 名です。

<managedWorkingMemory>, org.jboss.seam.drools.ManagedWorkingMemory

対話スコープで管理対象の Drools WorkingMemory の管理コンポーネントです。

  • <managedWorkingMemory>.ruleBaseRuleBase のインスタンスに対して評価を行う値表現です。

<ruleBase>, org.jboss.seam.drools.RuleBase

アプリケーションスコープの Drools RuleBase の管理コンポーネントです。 新しいルールの動的インストールをサポートしていないため、 実稼働での使用はまったく対象とされていないことに注意してください

  • <ruleBase>.ruleFiles — Drools のルールを含んでいるファイルの一覧です。

    <ruleBase>.dslFile — Drools DSL 定義です。

<entityHome>, org.jboss.seam.framework.EntityHome
<hibernateEntityHome>, org.jboss.seam.framework.HibernateEntityHome
<entityQuery>, org.jboss.seam.framework.EntityQuery
<hibernateEntityQuery>, org.jboss.seam.framework.HibernateEntityQuery

第24章 Seam JSF コントロール

Seam には Seam での作業に便利な JSF コントロールがいくつか含まれています。 JSF コントロールにおけるビルドの補完が目的で、 他のサードパーティのライブラリからの管理を行います。 Seam と併用する際は、 Ajax4JSF 及び ADF (現在は Trinidad) タグライブラリの使用を推奨します。 Tomahawk タグライブラリの使用はお薦めできません。

これらのコントロールを使用するには、 以下のように使用するページで "s" ネームスペースを定義します (facelets 固有)。

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:s="http://jboss.com/products/seam/taglib">
  

この例ではタグをいくつか使用している例を示しています。

表 24.1. Seam JSF コントロールの参照例

<s:validate>

詳細

非視覚的なコントロールです。 Hibernate Validator を使用してバウンドプロパティに対して JSF 入力フィールドを確認します。

属性

なし

使い方

<h:inputText id="userName" required="true" value="#{customer.userName}">
  <s:validate />
</h:inputText>
<h:message for="userName" styleClass="error" />

<s:validateAll>

詳細

非視覚的なコントロールです。 Hibernate Validator を使ってそのバウンドプロパティに対しすべての子 JSF 入力フィールドを確認します。

属性

なし

使い方

<s:validateAll>
  <div class="entry">
    <h:outputLabel for="username">Username:</h:outputLabel>
    <h:inputText id="username" value="#{user.username}" required="true"/>
    <h:message for="username" styleClass="error" />
  </div>
  <div class="entry">
    <h:outputLabel for="password">Password:</h:outputLabel>
    <h:inputSecret id="password" value="#{user.password}" required="true"/>
    <h:message for="password" styleClass="error" />
  </div>
  <div class="entry">
    <h:outputLabel for="verify">Verify Password:</h:outputLabel>
    <h:inputSecret id="verify" value="#{register.verify}" required="true"/>
    <h:message for="verify" styleClass="error" />
  </div>
</s:validateAll>

<s:formattedText>

詳細

Seam Text、 ブログに便利なリッチテキストマークアップ、 リッチテキストを使う可能性がある wiki やその他のアプリケーションを出力します。 使い方の全詳細については Seam Text の章を参照してください。

属性

  • value — 表示するリッチテキストマークアップを指定する EL 表現です。

使い方

<s:formattedText value="#{blog.text}"/>

<s:convertDateTime>

詳細

Seam タイムゾーン内でデータ変換または時間変換を行います。

属性

なし

使い方

<s:convertEnum>

詳細

enum コンバータを現在のコンポーネントに割り当てます。 おもにラジオボタンコントロール及びドロップダウンコントロールに役立ちます。

属性

なし

使い方

<s:convertEntity>

詳細

エンティティコンバータを現在のコンポーネントに割り当てます。 おもにラジオボタンコントロール及びドロップダウンコントロールに役立ちます。

コンバータは 単複いずれかの @Id アノテーションを持つエンティティならいずれでも使用できます。 管理永続コンテキストentityManager と呼ばれない場合、 components.xml で設定する必要があります。

属性

なし

設定

  <component name="org.jboss.seam.ui.entityConverter">
      <property name="entityManager">#{em}</property>
  </component>

使い方

<h:selectOneMenu value="#{person.continent}" required="true">
    <s:selectItems value="#{continents.resultList}" var="continent" label="#{continent.name}" noSelectionLabel="Please Select..."/>
    <s:convertEntity />
</h:selectOneMenu>

<s:enumItem>

詳細

enum の値から SelectItem を作成します。

属性

  • enumValue — enum の値の文字列表現です。

  • labelSelectItem を表示する場合に使用されるラベルです。

使い方


<s:selectItems>

詳細

List、 Set、 DataModel または Array から List<SelectItem> を作成します。

属性

  • valueList<SelectItem> を支持するデータを指定する EL 表現です。

  • var — 対話している間、 現在のオブジェクトを保持するローカルの変数名を定義します。

  • labelSelectItem を表示する場合に使用されるラベルです。 var 変数を参照することができます。

  • disabled — true の場合、 SelectItem は disabled と表示されます。 var 変数を参照できます。

  • noSelectionLabel — (オプションの) ラベルを指定して一覧の文頭に配置します (required="true" も指定されるとこの値の選択は検証エラーの要因となる)。

  • hideNoSelectionLabel — ture の場合、 値が選択されると noSelectionLabel は表示されなくなります。

使い方


<h:selectOneMenu value="#{person.age}"  converter="#{converters.ageConverter}">
    <s:selectItems value="#{ages}" var="age" label="#{age}" />
</h:selectOneMenu>

<s:graphicImage>

詳細

拡張された <h:graphicImage> では Seam Component 内にイメージを作成できるようになり、 そのイメージに対してさらに変換を適用することができます。 Facelets 固有になります。

<h:graphicImage> のすべての属性がサポートされている他、 以下もサポートされています。

属性

  • value — 表示させるイメージです。 パスの String (クラスパスからロードされる)、 byte[]java.io.Filejava.io.InputStream または java.net.URL が使用できます。 現在サポートされているイメージ形式は image/pngimage/jpegimage/gif になります。

  • fileName — 指定されていない場合、 提供されるイメージには生成されたファイル名が付きます。 ファイルに名前を付けたい場合には、 ここで指定します。 この名前は固有でなければなりません。

変換

イメージに変換を適用するには、 適用する変換を指定するタグをネストさせます。 Seam は現在、 次のような変換をサポートしています。

<s:transformImageSize>
  • width — イメージに新たに設定する幅です

  • height — イメージに新たに設定する高さです

  • maintainRatiotrue であり width/height のひとつが指定されている場合、 そのイメージは指定されたものではなく縦横の比率を維持するよう計算された寸法でリサイズされます。

  • factor — 特定の要素でイメージを縮小/拡大します。

<s:transformImageBlur>
  • radius — 特定の半径でコンボリューションブラーを行います。

<s:transformImageType>
  • contentType — イメージタイプを image/jpeg または image/png に変更します。

独自の変換を作成するのも簡単です。 implementsorg.jboss.seam.ui.graphicImage.ImageTransform を行う UIComponent を作成します。 applyTransform() メソッド内で、 オリジナルのイメージを取得するためにimage.getBufferedImage() 、 変換されたイメージをセットするために image.setBufferedImage() をそれぞれ使用します。 このビュー内で指定されている順序に従って変換は適用されます。

使い方


<s:decorate>

詳細

検証に失敗した場合または required="true" が設定されている場合、 JSF 入力フィールドを "Decorate" します。

属性

なし

使い方


<s:layoutForm>

詳細

標準のフォームレイアウトを生成するためのレイアウトコンポーネントです。 各子コンポーネントは 1 列で処理され、 子コンポーネントが <s:decorate> の場合は追加のフォーマット化が適用されます。

  • Label — label ファセットが <s:decorate> にある場合、 そのコンテンツがこのフィールドのラベルとして使用されます。 ラベルはコラム内に右詰めで表示されます。

    他にもサポートされているファセットがいくつかあります。 beforeLabelafterLabelaroundLabelbeforeInvalidLabelafterInvalidLabelaroundInvalidLabel などです。

  • Other text — belowLabel ファセットまたは belowField ファセットのいずれかあるいは両方が <s:decorate> にある場合、 そのコンテンツがラベルまたはフィールドの下に配置されます。

  • Required — フィールドに required="true" がセットされている場合、 aroundRequiredFieldbeforeRequiredFieldafterRequiredFieldaroundRequiredLabelbeforeRequiredLabel 及び afterRequiredLabel が適用されます。

属性

なし

使い方

<s:layoutForm>
   <f:facet name="aroundInvalidField">
       <s:span styleClass="error"/>
   </f:facet>
   <f:facet name="afterInvalidField">
       <s:message />
   </f:facet>
   <f:facet name="beforeRequiredLabel">
   	<s:span>&lowast;</s:span>
   </f:facet>
   <f:facet name="aroundLabel">
   	<s:span style="text-align:right;" />
   </f:facet>
   <f:facet name="aroundInvalidLabel">
   	<s:span style="text-align:right;" styleClass="error" />
   </f:facet>
   <s:decorate>
        <f:facet name="label">
		    <h:outputText value="Name" />
        </f:facet>
        <h:inputText value="#{person.name}" required="true"/>
        <f:facet name="belowField">
            <h:outputText styleClass="help" 
               value="Enter your name as it appears 
                  on your passport" />
        </f:facet>
    </s:decorate>
</s:layoutForm>

<s:message>

詳細

検証エラーのメッセージで JSF 入力フィールドを "Decorate" します。

属性

なし

使い方


<s:span>

詳細

HTML <span> をレンダリングします。

属性

なし

使い方


<s:div>

詳細

HTML <div> をレンダリングします。

属性

なし

使い方


<s:fragment>

詳細

レンダリングされないコンポーネントです。 その子コンポーネントのレンダリングを有効/無効にする場合に便利です。

属性

なし

使い方


<s:cache>

詳細

JBoss Cache を使用してレンダリングされるページフラグメントの Cache です。 <s:cache> は実際にはビルトインの pojoCache コンポーネントで管理される JBoss Cache のインスタンスを使用するので注意してください。

属性

  • key — レンダリングされたコンテンツをキャッシュするキーです。 値式 である場合がほとんです。 たとえば、 あるドキュメントを表示しているページフラグメントをキャッシュする場合は、 key="Document-#{document.id}" を使うことができます。

  • enabled — キャッシュが使用される必要があるかどうかを定義する値式です。

  • region — 使用する JBoss Cache ノードです (ノード別に異なる有効期限ポリシーを持たせることができる)。

使い方


<s:link>

詳細

対話伝搬の制御付きアクションの起動をサポートするリンクです。 フォームはサブミットしません。

属性

  • value — ラベルです。

  • action — アクションリスナーを指定するメソッドバインディングです。

  • view — リンク先の JSF view id です。

  • fragment — リンク先のフラグメント識別子です。

  • disabled — 無効にしたリンクです。

  • propagation — 対話伝搬のスタイルを定義します、 beginjoinnestnone あるいは end などです。

  • pageflow — 開始するページフロー定義です。 (propagation="begin" または propagation="join" の場合にのみ役立ちます。)

使い方


<s:button>

詳細

対話伝搬の制御付きアクションの起動をサポートするボタンです。 フォームはサブミットしません。

属性

  • value — ラベルです。

  • action — アクションリスナーを指定するメソッドバインディングです。

  • view — リンク先の JSF view id です。

  • fragment — リンク先のフラグメント識別子です。

  • disabled — 無効にしたリンクです。

  • propagation — 対話伝搬のスタイルを定義します、 beginjoinnestnone あるいは end などです。

  • pageflow — 開始するページフロー定義です。 (propagation="begin" または propagation="join" の場合にのみ役立ちます。)

使い方


<s:selectDate>

詳細

指定された入力フィールドの対して日付を選択する動的なデートピッカーコンポーネントを表示します。 selectDate エレメントのボディにはテキストやイメージなどの HTML エレメントが含まれるはずで、 ユーザーがデートピッカーを表示するためにクリックするよう指示します。 デートピッカーは CSS を使って表さなければなりません。 サンプルの CSS ファイルは date.css で Seam ブッキングデモ内にあります。 または、 seam-gen を使って生成することもできます。 デートピッカーの外観の制御に使用される CSS スタイルも以下に示します。

属性

  • for — 選択された日付がデートピッカーにより挿入される挿入先入力フィールドの ID です。

  • dateFormat — 日付形式の文字列です。 入力フィールドの日付形式と一致しなければなりません。

  • startYear — ポップアップの年度選択範囲はこの年から開始します。

  • endYear — ポップアップの年度選択範囲はこの年で終わります。

使い方

  <div class="row">
    <h:outputLabel for="dob">Date of birth<em>*</em></h:outputLabel>
    <h:inputText id="dob" value="#{user.dob}" required="true">
      <s:convertDateTime pattern="MM/dd/yyyy"/>
    </h:inputText>
    <s:selectDate for="dob" startYear="1910" endYear="2007"><img src="img/datepicker.png"/></s:selectDate>
    <div class="validationError"><h:message for="dob"/></div>
  </div>           

CSS スタイリング

次の一覧では日付選択コントロールのスタイルを制御する目的で使用される CSS クラス名を示します。

  • seam-date — ポップアップカレンダーを含んでいるアウター div に適用されます。 (1) カレンダーのインナーレイアウトを制御する table に対しても適用されます。 (2)

  • seam-date-header — このクラスはカレンダーのヘッダテーブル列 (tr) とヘッダテーブルのセル (td) に適用されます。 (3)

  • seam-date-header-prevMonth — このクラスは「先月」のテーブルセル (td) に適用され、 クリックするとカレンダーは現在表示されている月の 1 ヶ月前の月を表示します。 (4)

  • seam-date-header-nextMonth — このクラスは「来月」のテーブルセル (td) に適用され、 クリックするとカレンダーは現在表示されている月の 1 ヶ月先の月を表示します。 (5)

  • seam-date-headerDays — このクラスはカレンダーの曜日ヘッダ列 (tr) に適用され、 1 週間の曜日が含まれます。 (6)

  • seam-date-footer — このクラスはカレンダーのフッタ列 (tr) に適用され、 現在の日付を表示します。 (7)

  • seam-date-inMonth — このクラスは現在表示されている月の日付を含むテーブルセル (td) のエレメントに適用されます。 (8)

  • seam-date-outMonth — このクラスは現在表示されている月に含まれない日付を含むテーブルセル (td) のエレメントに適用されます。 (9)

  • seam-date-selected — このクラスは現在選択されている日付を含むテーブルセル (td) のエレメントに適用されます。 (10)

  • seam-date-dayOff-inMonth — このクラスは現在選択されている月の「休日」となる日付 (週末、土曜日および日曜日) を含むテーブルセル (td) のエレメントに適用されます。 (11)

  • seam-date-dayOff-outMonth — このクラスは現在選択されている月に含まれない「休日」 (週末、土曜日および日曜日) を含むテーブルセル (td) のエレメントに適用されます。 (12)

  • seam-date-hover — このクラスはカーソルが指しているテーブルセル (td) のエレメントに適用されます。 (13)

  • seam-date-monthNames — このクラスはポップアップの月選択を含む div コントロールに適用されます。 (14)

  • seam-date-monthNameLink — このクラスはポップアップの月の名前を含むアンカー (a) コントロールに適用されます。 (15)

  • seam-date-years — このクラスはポップアップの年選択を含む div コントロールに適用されます。 (16)

  • seam-date-yearLink — このクラスはポップアップの年を含むアンカー (a) コントロールに適用されます。 (15)

<s:conversationPropagation>

詳細

コマンドリンクやボタン (または JSF コントロールのようなもの) に対する対話伝搬をカスタマイズします。 Facelets 固有です。

属性

  • propagation — 対話伝搬のスタイルを定義します、 beginjoinnestnone あるいは end などです。

  • pageflow — 開始するページフロー定義です。 (propagation="begin" または propagation="join" の場合にのみ役立ちます。)

使い方


<s:conversationId>

詳細

対話 ID を出力リンク (または JSF コントロールのようなもの) に追加します。 Facelets 固有です。

属性

なし

使い方


<s:taskId>

詳細

タスクが #{task} で使用できる場合、 タスク ID を出力リンク (または JSF のようなもの) に追加します。 Facelets 固有です。

属性

なし

使い方


<s:fileUpload>

詳細

ファイルアップロードコントロールをレンダリングします。 このコントロールはエンコーディングタイプ multipart/form-data を使用してフォーム内で使用する必要があります。

                       
        <h:form enctype="multipart/form-data">
                       
                     

マルチパートのリクエストの場合、 Seam Multipart サーブレットフィルタも web.xml 内で設定しなければなりません。

                       
  <filter>
    <filter-name>Seam Filter</filter-name>
    <filter-class>org.jboss.seam.web.SeamFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>Seam Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
                       
                     

設定

components.xml では、 次のようなマルチパートリクエスト用の設定オプションが設定できます。

  • createTempFiles — このオプションが true に設定されると、 アップロードされたファイルはメモリ内ではなくテンポラリファイルにストリームされます。

  • maxRequestSize — ファイルアップロードリクエストの最大サイズをバイト単位で設定できます。

例を示します。

                       
  <component class="org.jboss.seam.web.MultipartFilter">
      <property name="createTempFiles">true</property>
      <property name="maxRequestSize">1000000</property>
  </component>
                       
                     

属性

  • data — この値バインディングはバイナリファイルデータを受け取ります。 受信フィールドは byte[] または InputStream で宣言されなければなりません (必須)。

  • contentType — この値バインディングはファイルの昆展とタイプを受け取ります (オプション)。

  • fileName — この値バインディングはファイル名を受け取ります (オプション)。

  • accept — 承認するコンテントタイプをコンマで区切った一覧です。 "images/png,images/jpg""images/*" などのようなブラウザではサポートされていない場合があります。

  • style — コントロールのスタイルです。

  • styleClass — コントロールのスタイルクラスです。

使い方


第25章 ELの拡張

標準のELは、メソッドへのパラメータはJavaコードにより渡される事を前提としています。 したがって、JSFのメソッドバインディングでは、 パラメータを渡すことは出来ません。 SeamではELを拡張して、パラメータを渡すことが出来るようにしています。この拡張は、下の例のように、 JSFのメソッドバインディングを含むすべての Seam のメソッド式に適用されます。

<s:commandButton action="#{hotelBooking.bookHotel(hotel)}" value="Book Hotel"/>

25.1. 設定

この拡張機能をfaceletで使うためには、faces-config.xmlに特別なview handler、 SeamFaceletViewHandlerを宣言する必要があります。

<faces-config>
    <application>
        <view-handler>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</view-handler>
    </application>
</faces-config>

25.2. 使用方法

パラメータは括弧で囲み、それぞれはコンマで区切ります。

<h:commandButton action="#{hotelBooking.bookHotel(hotel, user)}" value="Book Hotel"/>

上記の例ではパラメータ、hoteluser が値式として評価され、 コンポーネントのbookHotel() に渡されます。 この方法で、@Inの代用をする事が出来ます。

パラメータには、下のように、どのような値式も使う事が出来ます。

<h:commandButton action="#{hotelBooking.bookHotel(hotel.id, user.username)}" value="Book Hotel"/>

文字列も、下のように、シングルクオート、あるいはダブルクオートで括って渡す事が出来ます。

<h:commandLink action=”#{printer.println( ‘Hello world!’ )}” value=”Hello”/>
<h:commandLink action=’#{printer.println( “Hello again” )}’ value=’Hello’/>

パラメータを渡す必要が有る無しに関わらず、アクションメソッドの全てに、この様な表記をすることができ、これにより、 式が値式ではなく、メソッド式であることを明確に示し、可読性を向上させる事が出来ます。

<s:link value="Cancel" action="#{hotelBooking.cancel()}"/>

25.3. 制約

以下の制約について留意してください。

25.3.1. JSP 2.1との非互換性

現時点でこの拡張は JSP 2.1とは非互換です。 したがって、この拡張をJSF 1.2で使う場合にはfaceletを使う必要があります。 JSP 2.0.ではこの拡張は問題なく動作します。

25.3.2. JavaコードからのMethodExpressionの呼び出し

通常、MethodExpressionMethodBinding が生成された時に パラメータの型はJSFにより伝えられますが、メソッドバインディングの場合、JSFは渡されるパラメータが存在しないことを前提としています。 この拡張によりパラメータの型は式が評価されるまで知ることは出来ませんので、2つのマイナーな問題を生じます。

  • Javaコード中でMethodExpressionを呼び出したとき、渡されたパラメータが無視されます。また、 式中で定義されたパラメータが優先されます。

  • 通常、methodExpression.getMethodInfo().getParamTypes() はいつでも安全に呼び出す事が 出来ます。 パラメータを伴う式の場合、 まづ MethodExpression を呼び出してから、 getParamTypes() を呼び出すようにしてください。

上の2つのようなケースは非常に稀で、Javaコード中でMethodExpressionを呼び出す必要が有るときにのみ、 適用されます。

第26章 Seamアプリケーションのテスト

Seamアプリケーションのほとんどは、少なくとも2種類の自動テストが必要です。 個々のSeamコンポーネントをテストするユニットテストと、 アプリケーションのすべてのJava層 (ビューページ以外の全て) をスクリプトでテストする統合テストです。

どちらのテストもとても簡単に作成できます。

26.1. Seamコンポーネントのユニットテスト

すべてのSeamコンポーネントはPOJOです。簡単にユニットテストを始めるには、とても良い環境です。 さらにSeamは、コンポーネント間でのやり取りやコンテキスト依存オブジェクトのアクセスに バイジェクションを多用しているので、通常のランタイム環境でなくても とても簡単にSeamコンポーネントをテストすることができます。

次のようなSeamコンポーネントを考えてみましょう。

@Stateless
@Scope(EVENT)
@Name("register")
public class RegisterAction implements Register
{
   private User user;
   private EntityManager em;

   @In
   public void setUser(User user) {
       this.user = user;
   }
   
   @PersistenceContext
   public void setBookingDatabase(EntityManager em) {
       this.em = em;
   }
   
   public String register()
   {
      List existing = em.createQuery("select username from User where username=:username")
         .setParameter("username", user.getUsername())
         .getResultList();
      if (existing.size()==0)
      {
         em.persist(user);
         return "success";
      }
      else
      {
         return null;
      }
   }

}

このコンポーネントのTestNGテストは、以下のように書くことができます。

public class RegisterActionTest
{

    @Test
    public testRegisterAction()
    {
        EntityManager em = getEntityManagerFactory().createEntityManager();
        em.getTransaction().begin();
        
        User gavin = new User();
        gavin.setName("Gavin King");
        gavin.setUserName("1ovthafew");
        gavin.setPassword("secret");
        
        RegisterAction action = new RegisterAction();
        action.setUser(gavin);
        action.setBookingDatabase(em);
        
        assert "success".equals( action.register() );
        
        em.getTransaction().commit();
        em.close();
    }
    
    
    private EntityManagerFactory emf;
    
    public EntityManagerFactory getEntityManagerFactory()
    {
        return emf;
    }
    
    @Configuration(beforeTestClass=true)
    public void init() 
    {
        emf = Persistence.createEntityManagerFactory("myResourceLocalEntityManager");
    }
    
    @Configuration(afterTestClass=true)
    public void destroy()
    {
        emf.close();
    }
    
}

Seamコンポーネントは通常、コンテナのインフラストラクチャに直接依存していないため、 ほとんどのユニットテストはこのように簡単に書くことができるのです!

26.2. Seamアプリケーションの統合テスト

統合テストはもう少しだけ複雑になります。 コンテナのインフラストラクチャはテスト対象の一部であるため、 無視することができないのです! とは言え、自動テストを実行するために、わざわざアプリケーションサーバへアプリケーションをデプロイしたくはありません。 そこで、最低限必要なコンテナのインフラストラクチャをテスト環境に再現し、性能を大きく損なうことなく全てのアプリケーションを実行可能にする必要があります。

2番目の問題はユーザのインタラクションをどのようにエミュレートするかです。 3番目の問題はどこにアサーションを置くかです。あるテストフレームワークでは、 すべてのアプリケーションをテストするのに、Webブラウザでユーザのインタラクションを再生する必要があります。 このようなフレームワークは存在意義はありますが、開発段階で使用するには適切でありません。

Seam が採用するアプローチは、コンポーネントのテスト・スクリプトを作成し、 独立したコンテナ環境 (Seam と JBoss 内蔵の EJB コンテナ) で実行すると言うものです。 テスト・スクリプトの基本的な役割は、 ビューと Seam コンポーネントの間のインタラクションを再現することです。 つまり、 JSF 実装のふりをするということです。

このアプローチではビューを除くすべてをテストすることができます。

さきほどユニットテストしたコンポーネントの、JSPビューを考えてみましょう。

<html>
 <head>
  <title>Register New User</title>
 </head>
 <body>
  <f:view>
   <h:form>
     <table border="0">
       <tr>
         <td>Username</td>
         <td><h:inputText value="#{user.username}"/></td>
       </tr>
       <tr>
         <td>Real Name</td>
         <td><h:inputText value="#{user.name}"/></td>
       </tr>
       <tr>
         <td>Password</td>
         <td><h:inputSecret value="#{user.password}"/></td>
       </tr>
     </table>
     <h:messages/>
     <h:commandButton type="submit" value="Register" action="#{register.register}"/>
   </h:form>
  </f:view>
 </body>
</html>

このアプリケーションのユーザ登録機能 (Register ボタンをクリックしたときの動作) をテストします。 TestNG 自動テストで、 JSF リクエストのライフサイクルを再現してみましょう。

public class RegisterTest extends SeamTest
{
   
   @Test
   public void testRegister() throws Exception
   {
            
      new FacesRequest() {

         @Override
         protected void processValidations() throws Exception
         {
            validateValue("#{user.username}", "1ovthafew");
            validateValue("#{user.name}", "Gavin King");
            validateValue("#{user.password}", "secret");
            assert !isValidationFailure();
         }
         
         @Override
         protected void updateModelValues() throws Exception
         {
            setValue("#{user.username}", "1ovthafew");
            setValue("#{user.name}", "Gavin King");
            setValue("#{user.password}", "secret");
         }

         @Override
         protected void invokeApplication()
         {
            assert invokeMethod("#{register.register}").equals("success");
         }

         @Override
         protected void renderResponse()
         {
            assert getValue("#{user.username}").equals("1ovthafew");
            assert getValue("#{user.name}").equals("Gavin King");
            assert getValue("#{user.password}").equals("secret");
         }
         
      }.run();
      
   }

   ...
   
}

コンポーネントにSeam環境を提供するSeamTestを継承し、 JSFリクエストのライフサイクルをエミュレートするSeamTest.FacesRequest を継承した無名クラスにテストスクリプトを書いていることに注目してください。 (GETリクエストをテストするSeamTest.NonFacesRequest も用意されています。) さまざまなJSFフェーズを表す名前のメソッドに、JSFのコンポーネント呼び出しをエミュレートするコードを記述しています。 さらに、さまざまなアサーションをスローしています。

Seamのサンプルアプリケーションには、もっと複雑なケースの統合テストが用意されています。 Antを使用してテストを実行する方法と、EclipseのTestNGプラグインを使用する方法があります。

26.2.1. モックを使用した統合テスト

統合テスト環境では準備できないようなリソースをSeamコンポーネントが使用している場合、 コンポーネントの実装を置き換えることが必要な場合もあります。 たとえば支払処理システムのファサードを実装するSeamコンポーネントです。

@Name("paymentProcessor")
public class PaymentProcessor {
    public boolean processPayment(Payment payment) { .... }
}

統合テストをするには、次のようなコンポーネントのモック実装を作成します。

@Name("paymentProcessor")
@Install(precedence=MOCK)
public class MockPaymentProcessor extends PaymentProcessor {
    public void processPayment(Payment payment) {
        return true;
    }
}

優先度 (precedence) のMOCKは、アプリケーション・コンポーネントの デフォルト優先度より先なので、モック実装がクラスパスにあればSeamは モック実装を優先します。 本番環境ではモック実装は存在しないので、実際のコンポーネントが実行されます。

第27章 Seam ツール

27.1. jBPM デザイナ と ビュアー

jBPM デザイナとビュアーは、 ビジネスプロセスとページフローというすてきな方法で、 設計と照会を可能にします。 この便利なツールはJBoss Eclipse IDEの一部で、詳細は jBPM ドキュメントで参照できます。 (http://docs.jboss.com/jbpm/v3/gpd/)

27.1.1. ビジネスプロセスデザイナ

このツールでグラフィカルな方法でビジネスプロセスを設計することが可能です。

27.1.2. ページフロービュアー

このツールでページフローを拡張する部分を設計することや、 それらのグラフィカルなビューを作成することができます。 その結果、どのように設計されたかを共有したり比較したりが可能です。

27.2. CRUD アプリケーションジェネレータ

この章では、Hibernate ツールで利用可能な Seam のサポートの簡単な概要を説明します。 Hibernate ツールは、Hibernate と連携するためのツールと JBoss Seam や EJB3 などの関連技術のツール一式です。 このツールは Eclipse プラグイン や Ant タスクと組み合わせて使用可能です。 JBoss Eclipse IDE あるいは、Hibernate ツール のWEBサイトからダウンロードすることが可能です。

現在使用可能な Seam 特有のサポートとは、完全に機能するSeam ベースのCRUDアプリケーションを生成することです。 CRUD アプリケーションは、 既存の Hibernate マッピングファイルに基づいて生成されるか、 EJB3 をアノテーション付き POJO のマッピングに基づいて生成されるか、 あるいは既存のデータベーススキーマからリバースエンジニアリングすることで生成が可能です。

以下のセクションは、 Seam の使用を理解するために必要な特徴にフォーカスしています。 内容は Hibernate ツールのリファレンスドキュメントに依拠しています。 従って、より詳細な情報が必要であれば、Hibernateツールドキュメンテーションを参照してください。

27.2.1. Hibernate 設定ファイルの作成

リバースエンジニアリングやコード生成のためには、 hibernate.properties あるいは、hibernate.cfg.xml が必要です。 すでにそのようなファイルがないときのために、 Hibernate ツールは hibernate.cfg.xml を生成するためにウィザードを提供します。

"New Wizard" (Ctrl+N) をクリックしてウィザードを開始し、 Hibernate/Hibernate Configuration file (cfg.xml) ウィザードを選択し、 "Next" を押します。 hibernate.cfg.xml ファイルの指定するロケーションを選択した後に、 次のページが見えるでしょう。

Tip: ダイアレクト (Dialect) や選択した実際のドライバによって、 JDBC ドライバクラスと JDBC URL のためのコンボボックスの中の内容は自動的に変わります。

このダイアログに構成情報を入力します。 設定オプションについての詳細は、 Hibernate リファレンスドキュメンテーションで参照できます。

設定ファイルを作成するために "Finish" を押してください。 オプションでコンソール設定を作成した後に、 hibernate.cfg.xmlはエディタで自動的に開かれます。 最後のオプション"Create Console Configuration" は、デフォルトで有効になっています。 有効の場合、"Console Configuration (コンソール設定) " の基礎情報のための hibernate.cfg.xml を 自動的に使用します。

27.2.2. Hibernate コンソール設定の作成

コンソール設定は、Hibernate プラグインに、 POJO や JDBC ドライバをロードするためにどのクラスパスが必要かなどを含む、 どの設定ファイルが Hibernate を設定するために使用されるべきかを記述します。 それは、 クエリープロトタイピング、リバースエンジニアリング、 そしてコードジェネレーションを使用するために必要です。 複数の名前付きコンソール設定を持つこともできます。 通常、プロジェクトに 1 つだけ必要ですが、それ以上 (あるいは以下) も可能です。

以下のスクリーンショットが示すように、 コンソール設定ウィザードを実行してコンソール設定を作成します。 もし、hibernate.cfg.xmlウィザードから起動し、 そして、"Create Console Configuration"を有効にしていれば、 同じウィザードが使用されます。

以下のテーブルは関連する設定を記述しています。 もし、 選択されたプロジェクトの関連する Java でにウィザードを開始すれば、 これら大部分においてウィザードは自動的にデフォルト値を検出します。

表 27.1. Hibernate コンソール設定パラメータ

パラメータ

説明

自動検出される値

Name

設定におけるユニークな名前

選択されたプロジェクトの名前

Property file

hibernate.properties ファイルのパス

選択されたプロジェクトで最初に見つけた hibernate.properties ファイル

Configuration file

hibernate.cfg.xml ファイルのパス

選択されたプロジェクトで最初に見つけた hibernate.cfg.xml ファイル

Enable Hibernate ejb3/annotations

このオプションを選択するとアノテーション付きクラスが有効になります。 hbm.xml ファイルももちろん使用可能です。 この機能はEclipse IDE が JDK 5 ランタイムで実行される必要があります。 そうでなければ、classloading または version errors が発生します。

無効

Mapping files

ロードされなければならない追加マッピングファイルのリスト 注記: hibernate.cfg.xml もマッピングを含むことができます。 従って、これらが重複すれば、コンソール設定を使うときに、 "Duplicate mapping" errors が発生します。

hibernate.cfg.xml ファイルが見つからなければ、 選択されたプロジェクトにあるすべての hbm.xml ファイル

Classpath

POJO や JDBC ドライバをロードするためのクラスパス。 Hibernate core libraries あるいは、dependencies を追加しないでください。 それらは既に含まれています。 もし、ClassNotFound errors が発生したら、不足あるいは重複の可能性のある ディレクトリ / Jar をこのリストでチェックしてください。

デフォルトのビルド出力ディレクトリ、 そして選択されたプロジェクトで java.sql.Driver を実装したクラスを持つJARs

"Finish"をクリックして設定を作成してください、"Hibernate Configurations" ビューでそれを見ることができます。

27.2.3. リバースエンジニアリング と コードジェネレーション

とても簡単な "click-and-generate" リバースエンジニアリングと、 コードジェネレーションの機能が利用できます。 この機能で Seam CRUD アプリケーション全体のスケルトンの生成が可能です。

このプロセス作業を開始するために、"Hibernate Code Generation"を開始しましょう。 これは、Hibernateアイコンのツールバーからでも、"Run/Hibernate Code Generation"のメニュからも利用できます。

27.2.3.1. コードジェネレーションのランチャー

"Hibernate Code Generation" をクリックしたとき、 標準の Eclipse ランチャーダイアログが現れます。 このダイアログで、指定された Hibernate code generation "launchers" を 作成、編集、削除することが可能です。

このダイアログには、標準タブの "Refresh" と "Common" があります。 これらは、たとえば、チーム内のランチャーを共有するためにプロジェクトで保存される設定のような、 どのディレクトリを自動的に更新すべきかの設定、 そして、一般的なランチャーの設定に使用することが可能です。

初めて、コードジェネレーションランチャーを作成するとき、 意味のある名前をつけなければなりません。そうでなければデフォルトの接頭語として "New_Generation" が使用されます。

注記: "At least one exporter option must be selected" は、 単に、作業するこのスタートのために、 エキスポートタブの上のエキスポートするものを選ぶ必要があることを述べている警告です。 エキスポートするものが選択されれば、 警告は消えます。

"Main" タブで設定する項目:

表 27.2. Code generation "Main" タブのフィールド

フィールド

説明

Console Configuration

コード生成されるときに、使用されるコンソール設定の名前

Output directory

デフォルトで書き込まれるすべての出力のディレクトリーパスの場所。 既存のファイルが上書きされることに注意してください、 正しくディレクトリが指定される必要があります。

Reverse engineer from JDBC Connection

有効化されていれば、 ツールは、 選択された Hibernate コンソール設定中の コネクション情報を経由して、 データベースのリバースエンジニアリングを可能にし、 そして、 データベーススキーマをベースとして コードを生成します。 もし、有効化されていなければ、 コードジェネレーションは単に Hibernate コンソール設定で指定する マッピングに基づくでしょう。

Package

ここでのパッケージ名は、 リバースエンジニアリングする時に見つけられたエンティティに対して使用される、 デフォルトのパッケージ名です。

reveng.xml

reveng.xml ファイルへのパス。 reveng.xml ファイルは、 特定のリバースエンジニアリングの、 アスペクトの制御を可能にします。 例えば、どのように JDBC タイプが Hibernate タイプにマッピングされるかや、 特に重要なものとして、 どのテーブルがこのプロセスに包含するか / 排除されるかです。 "setup" のクリックは既存の reveng.xml の選択や、 新しい reveng.xml の作成を可能にます。

reveng. strategy

もし、reveng.xml でのカスタマイズが不十分であれば、 ReverseEngineeringStrategy を実装した独自のものを設定することも可能です。 このクラスはコンソール設定のクラスパスにある必要があります、 そうでない場合、class notfound exceptions が発生します。

Generate basic typed composite ids

Sesam CRUD アプリケーションを生成する場合には、 このフィールドはいつも有効にしておく必要があります。 複数カラムの主キーを持つテーブル <composite-id> マッピングはいつも生成されます。 このオプションが有効で、かつマッチする外部キーがあるならば、 それぞれのキーカラムはエンティティへの参照の代わりに、 基本的なスカラー (string、 long、 など) と考えられます。 もしこのオプション <key-many-to-one> を反対に無効とした場合、 <many-to-one> プロパティはやはり作成されますが、 単に non-updatable や non-insertable とマークされますますのでご注意ください。

Use custom templates

有効であれば、velocity テンプレートを探すときに、 テンプレートディレクトリーが最初に検索され、 どのように個別のテンプレートが Hibernate マッピングモデルで処理されるかを 再定義することを可能にします。

Template directory

カスタム Velocity テンプレートのディレクトリーパス

27.2.3.2. エクスポート

エクスポートタブは生成するコードのタイプをを指定するために使用されます。 それぞれの選択はコード (つまり、名前) の生成を担当する "Exporter" を表します。

以下のテーブルは単にさまざまな exporters を説明しています。 Seam について最も関連あることはもちろん "JBoss Seam Skeleton app" です。

表 27.3. Code generation "Exporter" タブフィールド

フィールド

説明

Generate domain code

所与の Hibernate 設定にあるコンポーネントと 永続クラスのための POJO を生成します。

JDK 1.5 constructs

有効であれば、POJO's は JDK 1.5 constructs を使用します。

EJB3/JSR-220 annotations

有効であれば、POJO's は EJB3/JSR-220 永続性仕様書にしたがってアノテーションが付けられます。

Generate DAO code

見つけたそれぞれのエンティティの DAO 一式を生成します。

Generate Mappings

それぞれのエンティティのためにマッピングファイル (hbm.xml) を生成します。

Generate hibernate configuration file

hibernate.cfg.xml ファイルを生成します。 新しく見つかったマッピングファイルを hibernate.cfg.xml と最新状態に保つために使用されます。

Generate schema html-documentation

データベーススキーマといくつかのマッピングを記述した HTML ページ一式を生成します。

Generate JBoss Seam skeleton app (beta)

JBoss Seam スケルトンアプリケーション一式を生成します。 ジェネレーションはアノテーション付き POJO、 Seam コントローラ Beans、 そして、プレゼンテーション層用のJSPを含みます。 使用方法は生成された readme.txt を参照してください。

注記: このエキスポータは、 build.xmlを含む アプリケーション一式を生成します。 従って、 プロジェクトのルートに出力ディレクトリを使用すると、 最もよい結果が得られるでしょう。

27.2.3.3. コードの生成と利用

設定を記入し終わったら、コード生成を起動するために単に "Run" を押してください。 データベースからリバースエンジニアリングするならば、少し時間がかかるかもしれません。

生成が終わったら、出力ディレクトリの中にスケルトンの Seam application 一式があります。 出力ディレクトリ中に、 サンプルをデプロイ、実行に必要なステップを記述した readme.txt ファイルがあります。

もし、スケルトンコードの再生成/更新が必要であれば、 ツールバーの "Hibernate Code Generation" あるいはメニューから"Run"を選択して、 単にコードジェネレーションを実行してください。 楽しんでくださいね。