SeamFramework.orgCommunity Documentation

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

エンタープライズ Java 向けフレームワーク

2.1.0.GA


JBoss Seam の紹介
1. Seam に貢献するには
1. Seam チュートリアル
1.1. Seam サンプルを使用する
1.1.1. JBoss AS 上でのサンプルの実行
1.1.2. Tomcat 上でのサンプル実行
1.1.3. サンプルのテスト起動
1.2. 初めての Seam アプリケーション: ユーザー登録サンプル
1.2.1. コードの理解
1.2.2. 動作内容
1.3. Seam でクリックが可能な一覧: 掲示板サンプル
1.3.1. コードの理解
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. Seam デバッグページ
1.7. ネストされた対話 : ホテル予約サンプルの拡張
1.7.1. はじめに
1.7.2. ネストされた対話の理解
1.8. Seam と jBPM を使った本格的アプリケーション: DVD ストアサンプル
1.9. Blog サンプルのブックマーク可能 URL
1.9.1. "PULL" 型 MVC の使用
1.9.2. ブックマーク可能検索結果ページ
1.9.3. RESTful アプリケーションの "PUSH" 型 MVC の使用
2. seam-gen を使って Seam を始めよう
2.1. 始める前に
2.2. 新しい Eclipse プロジェクトのセットアップ
2.3. 新規のアクションを生成する
2.4. アクションのあるフォームを生成する
2.5. 既存のデータベースからアプリケーションを自動生成する
2.6. 既存の JPA/EJB3 エンティティからアプリケーションを自動生成する
2.7. EAR 形式でアプリケーションをデプロイする
2.8. Seam と増分ホットデプロイメント
2.9. JBoss 4.0 で Seam を使用する
2.9.1. JBoss 4.0 のインストール
2.9.2. JSF 1.2 RI のインストール
3. JBoss Tools を使って Seam を始めよう
3.1. 始める前に
3.2. 新しい Eclipse プロジェクトのセットアップ
3.3. 新規のアクションを生成する
3.4. アクションのあるフォームを生成する
3.5. 既存のデータベースからアプリケーションを自動生成する
3.6. Seam と JBoss Tools を使用した増分ホットデプロイメント
4. コンテキスト依存コンポーネントモデル
4.1. Seam コンテキスト
4.1.1. ステートレスコンテキスト
4.1.2. イベントコンテキスト
4.1.3. ページコンテキスト
4.1.4. 対話コンテキスト
4.1.5. セッションコンテキスト
4.1.6. ビジネスプロセスコンテキスト
4.1.7. アプリケーションコンテキスト
4.1.8. コンテキスト変数
4.1.9. コンテキスト検索優先順位
4.1.10. 同時並行処理モデル
4.2. Seam コンポーネント
4.2.1. ステートレスセッション Bean
4.2.2. ステートフルセッション Bean
4.2.3. エンティティ Bean
4.2.4. JavaBeans
4.2.5. メッセージ駆動型 Bean
4.2.6. インタセプション
4.2.7. コンポーネント名
4.2.8. コンポーネントスコープの定義
4.2.9. 複数ロールを持つコンポーネント
4.2.10. 組み込みコンポーネント
4.3. バイジェクション
4.4. ライフサイクルメソッド
4.5. 条件付きインストール
4.6. ロギング
4.7. Mutable インタフェースと @ReadOnly
4.8. ファクトリと管理コンポーネント
5. Seamコンポーネントの構成
5.1. プロパティ設定によるコンポーネントの構成
5.2. components.xmlによるコンポーネントの構成
5.3. 細分化した構成ファイル
5.4. 構成可能なプロパティの型
5.5. XML名前空間の使用
6. イベント、インタセプタ、例外処理
6.1. Seamイベント
6.2. ページアクション
6.3. ページパラメータ
6.3.1. 要求パラメータからモデルへのマッピング
6.4. 要求パラメータの伝播
6.5. ページパラメータを伴うURL書き換え
6.6. 変換と妥当性検証
6.7. ナビゲーション
6.8. ナビゲーション、ページアクション、パラメータの定義用に細分化したファイル
6.9. コンポーネント駆動イベント
6.10. コンテキスト依存イベント
6.11. Seamインタセプタ
6.12. 例外を管理する
6.12.1. 例外およびトランザクション
6.12.2. Seam 例外処理を有効にする
6.12.3. 例外処理に対してアノテーションを使用する
6.12.4. 例外処理に XML を使用する
6.12.5. 共通の例外
7. 対話とワークスペースの管理
7.1. Seam の対話モデル
7.2. ネストされた対話
7.3. GET 要求を使って対話を開始する
7.4. <s:link><s:button> の使いかた
7.5. 成功のメッセージ
7.6. ナチュラル対話の ID
7.7. ナチュラル対話を作成する
7.8. ナチュラル対話にリダイレクトする
7.9. ワークスペースの管理
7.9.1. ワークスペース管理と JSF ナビゲーション
7.9.2. ワークスペース管理と jPDL ページフロー
7.9.3. 対話切り替え
7.9.4. 対話一覧
7.9.5. ブレッドクラム (Breadcrumbs)
7.10. 対話型コンポーネントと JSF コンポーネントのバインディング
7.11. 対話的コンポーネントへの並列コール
7.11.1. 対話的 AJAX アプリケーションを設計する
7.11.2. エラーを処理する
7.11.3. RichFaces Ajax
8. ページフローとビジネスプロセス
8.1. Seamのページフロー
8.1.1. 二つのナビゲーションモデル
8.1.2. Seam と 戻るボタン
8.2. jPDL ページフローの使用
8.2.1. ページフローの設定
8.2.2. ページフローの開始
8.2.3. ページノードと遷移
8.2.4. フローの制御
8.2.5. フローの終了
8.2.6. ページフローコンポジション
8.3. Seam のビジネスプロセス管理
8.4. jPDL ビジネスプロセス定義の使用
8.4.1. プロセス定義の設定
8.4.2. アクターIDの初期化
8.4.3. ビジネスプロセスの初期化
8.4.4. タスク割り当て
8.4.5. タスクリスト
8.4.6. タスクの実行
9. Seam とオブジェクト/リレーショナルマッピング
9.1. はじめに
9.2. Seam 管理トランザクション
9.2.1. Seam 管理トランザクションを無効にする
9.2.2. Seamトランザクションマネージャを設定する
9.2.3. トランザクションの同期化
9.3. Seam 管理の永続コンテキスト
9.3.1. JPA で Seam 管理の永続コンテキストを使用する
9.3.2. Seam 管理の Hibernate セッションを使用する
9.3.3. Seam 管理の永続コンテキストとアトミックな対話
9.4. JPA 「デリゲート」を使用する
9.5. EJB-QL/HQL で EL を使用する方法
9.6. Hibernate フィルタを使用する
10. Seam での JSF フォーム検証
11. Groovy インテグレーション
11.1. はじめに
11.2. Groovy による Seam アプリケーションの記述
11.2.1. Groovy コンポーネントの記述
11.2.2. seam-gen
11.3. デプロイ
11.3.1. Groovy コードのデプロイ
11.3.2. 開発時のネイティブ .groovy ファイルのデプロイ
11.3.3. seam-gen
12. アプリケーションのプレゼンテーション層にApache Wicketを使用する
12.1. Seam上でWicketアプリケーションを動作させる
12.1.1. バイジェクション
12.1.2. オーケストレーション
12.2. プロジェクトのセットアップ
12.2.1. アプリケーションの定義
13. Seamアプリケーションフレームワーク
13.1. はじめに
13.2. Homeオブジェクト
13.3. Queryオブジェクト
13.4. Controllerオブジェクト
14. Seam と JBoss Rules
14.1. ルールをインストールする
14.2. Seam コンポーネントからのルールを使用する
14.3. jBPM プロセス定義からのルールを使用する
15. セキュリティ
15.1. 概要
15.2. セキュリティの無効化
15.3. 認証
15.3.1. 認証コンポーネントの設定
15.3.2. 認証メソッドの記述
15.3.3. ログインフォームの記述
15.3.4. 設定のまとめ
15.3.5. Remember Me (覚えておいて!)
15.3.6. セキュリティ例外の処理
15.3.7. ログインリダイレクト
15.3.8. HTTP認証
15.3.9. 高度な認証機能
15.4. IDの管理
15.4.1. IDマネージャの設定
15.4.2. JpaIdentityStore
15.4.3. LdapIdentityStore
15.4.4. 自分のIDストアを記述する
15.4.5. ID管理による認証
15.4.6. IDマネージャの使用
15.5. エラーメッセージ
15.6. 認可
15.6.1. 核となる概念
15.6.2. コンポーネントの安全性を確保する
15.6.3. ユーザーインタフェースのセキュリティ
15.6.4. ページ単位のセキュリティ
15.6.5. エンティティのセキュリティ
15.6.6. タイプセーフなアクセス権のアノテーション
15.6.7. タイプセーフなロールのアノテーション
15.6.8. パーミッション許可モデル
15.6.9. ルールベースのパーミッションリゾルバー
15.6.10. 永続的パーミッションリゾルバー
15.7. パーミッションの管理
15.7.1. パーミッションマネージャ
15.7.2. パーミッションマネージャ操作のためのパーミッションチェック
15.8. SSLによるセキュリティ
15.8.1. デフォルトのポートの上書き
15.9. キャプチャ
15.9.1. キャプチャ サーブレットの設定
15.9.2. キャプチャのフォームへの追加
15.9.3. キャプチャアルゴリズムのカスタム化
15.10. セキュリティ イベント
15.11. 自分とは別の権限での実行
15.12. IDコンポーネント(Identity component)の拡張
16. 国際化と地域化およびテーマ
16.1. アプリケーションを国際化する
16.1.1. アプリケーションサーバーの設定
16.1.2. 翻訳されたアプリケーション文字列
16.1.3. その他のエンコーディング設定
16.2. ロケール
16.3. ラベル
16.3.1. ラベルを定義する
16.3.2. ラベルを表示する
16.3.3. Faces メッセージ
16.4. タイムゾーン
16.5. テーマ
16.6. ロケールとテーマ設定のクッキーによる永続化
17. Seam Text
17.1. フォーマットの基本
17.2. プログラムコードや特殊文字を含むテキストの記述
17.3. リンク
17.4. HTMLの記述
18. iText PDF 生成
18.1. PDF サポートを使う
18.1.1. ドキュメントを作成する
18.1.2. 基本的なテキストのエレメント
18.1.3. ヘッダーとフッター
18.1.4. 章とセクション
18.1.5. リスト
18.1.6. 表
18.1.7. ドキュメントの定数
18.2. グラフ
18.3. バーコード
18.4. Swing/AWT コンポーネントをレンダリングする
18.5. iText を設定する
18.6. その他のドキュメント
19. Microsoft® Excel® スプレッドアプリケーション
19.1. Microsoft® Excel® スプレッドアプリケーションを利用する
19.2. 簡単なブックを作成する
19.3. workbook要素
19.4. worksheet要素
19.5. column要素
19.6. cell要素
19.6.1. validation要素
19.6.2. 書式マスク
19.7. formula要素
19.8. image要素
19.9. hyperlink要素
19.10. header要素とfooter要素
19.11. printArea要素とprintTitle要素
19.12. ワークシートコマンド要素
19.12.1. グルーピング
19.12.2. 改ページ要素
19.12.3. 結合要素
19.13. データテーブルへの出力
19.14. フォントとレイアウト
19.14.1. スタイルシートへのリンク
19.14.2. フォント
19.14.3. ボーダー
19.14.4. 背景
19.14.5. 列の設定
19.14.6. セルの設定
19.14.7. データテーブル出力
19.14.8. レイアウト例
19.14.9. 制限
19.15. 関連リンクと詳細なドキュメント
20. RSSサポート
20.1. インストール
20.2. フィードを生成する
20.3. フィード
20.4. エントリ
20.5. リンクと、より詳細な文書について
21. 電子メール
21.1. メッセージを作成する
21.1.1. 添付ファイル
21.1.2. HTML/Text 代替部分
21.1.3. 複数の受信者
21.1.4. 複数のメッセージ
21.1.5. テンプレートの作成
21.1.6. 国際化
21.1.7. その他のヘッダー
21.2. 電子メールを受信する
21.3. 設定
21.3.1. mailSession
21.4. Meldware
21.5. タグ
22. 非同期性とメッセージング
22.1. 非同期性
22.1.1. 非同期メソッド
22.1.2. Quartz ディスパッチャを使った非同期メソッド
22.1.3. 非同期イベント
22.1.4. 非同期の呼び出しによる例外を処理する
22.2. Seam でのメッセージング
22.2.1. 設定
22.2.2. メッセージ送信
22.2.3. メッセージ駆動型 Bean を使用したメッセージの受信
22.2.4. クライアントでのメッセージの受信
23. キャッシュ
23.1. Seamでキャッシュを利用する
23.2. ページ断片のキャッシュ
24. Webサービス
24.1. 設定とパッケージング
24.2. 対話型Webサービス
24.2.1. 推奨される方法
24.3. Webサービスの例
24.4. RESTEasy によるRESTful HTTP Webサービス
24.4.1. RESTEasy の設定と要求
24.4.2. Seam コンポーネントとしてのリソースとプロバイダ
25. リモーティング
25.1. 設定
25.2. "Seam"オブジェクト
25.2.1. Hello World サンプル
25.2.2. Seam.Component
25.2.3. Seam.Remoting
25.3. EL 式を評価する
25.4. クライアントのインタフェース
25.5. コンテキスト
25.5.1. 対話 ID の設定と読み込み
25.5.2. 現在の対話スコープ内のリモート呼び出し
25.6. バッチ要求
25.7. データタイプの取り扱い
25.7.1. プリミティブ型 / 基本タイプ
25.7.2. JavaBeans
25.7.3. 日付と時刻
25.7.4. Enum
25.7.5. 集合
25.8. デバッグ機能
25.9. メッセージをロードする
25.9.1. メッセージを変更する
25.9.2. ローディングメッセージを隠す
25.9.3. カスタムのローディングインジケータ
25.10. 戻り値の制御
25.10.1. フィールドの制約
25.10.2. Map とコレクションの制約
25.10.3. 特定タイプのオブジェクトを制約する
25.10.4. 制約同士を組み合わせる
25.11. JMS メッセージング
25.11.1. 設定
25.11.2. JMS Topic のサブスクライブ
25.11.3. トピックのサブスクライブを中止する
25.11.4. ポーリングのプロセスの調整
26. SeamとGoogle Web Toolkit
26.1. 設定
26.2. コンポーネントの用意する
26.3. GWTウィジェットを Seam コンポーネントに関連づける
26.4. GWTとAntターゲット
27. Spring Framework 統合
27.1. Seam コンポーネントを Spring Bean にインジェクトする
27.2. Spring Bean を Seam コンポーネントにインジェクトする
27.3. Spring Bean を Seam コンポーネントにする
27.4. Seam スコープの Spring Bean
27.5. Spring の PlatformTransactionManagement を使用する
27.6. Spring で Seam 管理の永続コンテキストを使用する
27.7. Spring で Seam 管理の Hibernate セッションを使用する
27.8. Seam コンポーネントとしての Spring Application Context
27.9. @Asynchronous に Spring の TaskExecutor を使用する
28. Hibernate Search
28.1. はじめに
28.2. 設定
28.3. 使い方
29. Seam の設定と Seam アプリケーションのパッケージング
29.1. Seam の基本設定
29.1.1. Seam と JSF、 サーブレットコンテナとの統合
29.1.2. Using facelets
29.1.3. Seam リソースサーブレット
29.1.4. Seamサーブレットフィルタ
29.1.5. EJB コンテナと Seam の統合
29.1.6. 忘れないようにしてください。
29.2. 代替の JPA プロバイダを使用する
29.3. Java EE 5 で Seam を設定
29.3.1. パッケージング
29.4. J2EEでの Seam の設定
29.4.1. Seam での Hibernateのブートストラップ
29.4.2. Seam での JPAのブートストラップ
29.4.3. パッケージング
29.5. JBoss Embedded なしの Java SE で Seam を設定する
29.6. JBoss Embedded を使用し Java SE で Seam を設定する
29.6.1. Embedded JBoss をインストールする
29.6.2. パッケージング
29.7. SeamでのjBPM設定
29.7.1. パッケージング
29.8. JBoss ASでの SFSBとセッションタイムアウトの設定
29.9. Portlet で Seam を実行する
29.10. カスタムのリソースをデプロイする
30. Seam アノテーション
30.1. コンポーネント定義のためのアノテーション
30.2. バイジェクション用アノテーション
30.3. コンポーネントのライフサイクルメソッド用アノテーション
30.4. コンテキスト境界用アノテーション
30.5. J2EE 環境で Seam JavaBean コンポーネントを使用するためのアノテーション
30.6. 例外用のアノテーション
30.7. Seam Remoting用のアノテーション
30.8. Seam インタセプタ用のアノテーション
30.9. 非同期用のアノテーション
30.10. JSF と使用するアノテーション
30.10.1. dataTable と使用するアノテーション
30.11. データバインディング用のメタアノテーション
30.12. パッケージング用のアノテーション
30.13. サーブレットコンテナと統合するためのアノテーション
31. 組み込み Seam コンポーネント
31.1. コンテキストインジェクションのコンポーネント
31.2. ユーティリティコンポーネント
31.3. 国際化とテーマのコンポーネント
31.4. 対話を制御するためのコンポーネント
31.5. jBPM 関連のコンポーネント
31.6. セキュリティ関連のコンポーネント
31.7. JMS 関連のコンポーネント
31.8. メール関連のコンポーネント
31.9. 基盤となるコンポーネント
31.10. その他のコンポーネント
31.11. 特殊なコンポーネント
32. Seam JSF コントロール
32.1. タグ
32.1.1. ナビゲーション コントロール
32.1.2. コンバータとバリデータ
32.1.3. フォーマット
32.1.4. Seam Text
32.1.5. ドロップダウン
32.1.6. その他
32.2. アノテーション
33. JBoss EL
33.1. パラメータ化された式
33.1.1. 使い方
33.1.2. 制約とヒント
33.2. プロジェクション
34. Seamアプリケーションのテスト
34.1. Seamコンポーネントのユニットテスト
34.2. Seamコンポーネントの統合テスト
34.2.1. モックを使用した統合テスト
34.3. ユーザーインタラクションの統合テスト
34.3.1. 設定
34.3.2. 別フレームワークでのSeamTestの利用
34.3.3. モックデータを利用した統合テスト
34.3.4. Seamメールの統合テスト
35. Seam ツール
35.1. jBPM デザイナとビュアー
35.1.1. ビジネスプロセスデザイナ
35.1.2. ページフロービュアー
36. OC4J 上の Seam
36.1. OC4J のインストールと操作
36.2. jee5/booking サンプル
36.2.1. ホテル予約サンプルの依存関係
36.2.2. 構成ファイルの変更点
36.2.3. jee5/booking サンプルのビルド
36.3. OC4J への Seam アプリケーションのデプロイ
36.4. seam-gen により生成されたアプリケーションの OC4J へのデプロイ
36.4.1. seam-gen アプリケーションの雛形の生成
36.4.2. OC4J へのデプロイに必要な変更点
36.4.3. seam-gen 生成アプリケーションのビルドと OC4J へのデプロイ
36.4.4. リバースエンジンニアリング生成した CRUD と Drools を使用したサンプルの拡張
36.5. 完成
37. BEA Weblogic における Seam
37.1. WebLogicのインストールと操作
37.1.1. 10.3のインストール
37.1.2. Weblogicのドメインを作成する。
37.1.3. ドメインの 起動/停止/アクセス 方法
37.1.4. WeblogicのJSFサポートの設定
37.2. jee5/bookingサンプル
37.2.1. Weblogic上のEJB3の問題
37.2.2. jee5/booking の動作
37.3. jpa 予約サンプル
37.3.1. jpa 予約サンプルのビルドとデプロイ
37.3.2. Weblogic 10.xでの違い
37.4. Weblogic 10.x にseam-genを使用して作成したアプリケーションをデプロイ
37.4.1. seam-genをp起動するための設定
37.4.2. Weblogic 10.Xについての変更
37.4.3. アプリケーションのビルドとデプロイ
38. Seam on IBM's Websphere
38.1. Websphere environment and deployment information
38.1.1. Installation versions and tips
38.1.2. Required custom properties
38.2. jee5/booking サンプル
38.2.1. 構成ファイルの変更
38.2.2. jee5/booking サンプルのビルド
38.2.3. Websphere へのアプリケーションのデプロイ
38.3. jpa booking サンプル
38.3.1. jpa サンプルのビルド
38.3.2. jpa サンプルのデプロイ
38.3.3. Whats different for Websphere 6.1
38.4. Deploying an application created using seam-gen on Websphere 6.1.0.13
38.4.1. seam-gen セットアップの実行
38.4.2. Websphere へのデプロイに必要な変更点
39. GlassFish アプリケーションサーバー上の Seam
39.1. GlassFish 環境とデプロイメント情報
39.1.1. インストール
39.2. jee5/booking サンプル
39.2.1. GlassFish へのアプリケーションのデプロイ
39.3. jpa booking サンプル
39.3.1. jpa サンプルのビルド
39.3.2. jpa サンプルのデプロイ
39.3.3. GlassFish v2 UR2 での変更点
39.4. seam-gen により生成されたアプリケーションの GlassFish v2 UR2 へのデプロイ
39.4.1. seam-gen セットアップの実行
39.4.2. GlassFish へのデプロイに必要な変更点
40. 依存性
40.1. JDK の依存性
40.1.1. Sun の JDK 6 に関する注意点
40.2. プロジェクトの依存性
40.2.1. Core
40.2.2. RichFaces
40.2.3. Seam Mail
40.2.4. Seam PDF
40.2.5. Seam Microsoft® Excel®
40.2.6. Seam RSS サポート
40.2.7. JBoss Rules
40.2.8. JBPM
40.2.9. GWT
40.2.10. Spring
40.2.11. Groovy
40.3. Maven を使用した依存性管理

Seam はエンタープライズ Java 向けのアプリケーションフレームワークです。以下のような理念に基づいています。

一種類の「材料」 (一貫したコンポーネントモデル)

Seam はアプリケーションのすべてのビジネスロジックのために一貫したコンポーネントモデルを定義します。Seam コンポーネントは、明確に定義された数種類のコンテキストの一つに関係付けられた状態を持つステートフルなものであるかもしれません。これらのコンテキストには、長時間に渡って実行され永続化される ビジネスプロセスコンテキスト や、複数の Web 要求にまたがる一連のユーザーインタラクション間で保持される 対話コンテキスト (conversation context) が含まれます。

Seam ではプレゼンテーション層コンポーネントとビジネスロジック層コンポーネントとに区別はありません。アプリケーションを自分で工夫したどんなアーキテクチャにも階層化することができます。今日使用している煙突型(スタック型)フレームワークのいかなる組み合わせによっても要求される不自然な階層化構造に、アプリケーションロジックを詰め込むようなことは強制されません。

Unlike plain Java EE or J2EE components, Seam components may simultaneously access state associated with the web request and state held in transactional resources (without the need to propagate web request state manually via method parameters). You might object that the application layering imposed upon you by the old J2EE platform was a Good Thing. Well, nothing stops you creating an equivalent layered architecture using Seam—the difference is that you get to architect your own application and decide what the layers are and how they work together.

JSF と EJB 3.0 の統合

JSF と EJB 3.0 は、Java EE 5 の最も素晴らしい二つの新機能です。EJB3 は、サーバサイドのビジネスロジックと永続化ロジックのための全く新しいコンポーネントモデルです。一方、JSF はプレゼンテーション層のための優れたコンポーネントモデルです。残念なことに、どちらのコンポーネントモデルも片方だけではすべてのコンピューティング問題を解決することはできません。その代わりに、JSF と EJB3 を一緒に使用すれば最も良く働かせることができます。しかし、Java EE 5 仕様では、二つのコンポーネントモデルを統合するための標準的な方法を提供していません。幸いにも、両方のモデルの策定者はこの状況を予見していて、モデルを拡張したり他のフレームワークと統合したりを可能にするための標準拡張ポイントを提供しています。

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

It is possible to write Seam applications where "everything" is an EJB. This may come as a surprise if you're used to thinking of EJBs as coarse-grained, so-called "heavyweight" objects. However, version 3.0 has completely changed the nature of EJB from the point of view of the developer. An EJB is a fine-grained object—nothing more complex than an annotated JavaBean. Seam even encourages you to use session beans as JSF action listeners!

一方で、もし現時点では EJB 3.0 を採用しない方を好めば、EJB 3.0 を使用する必要はありません。事実上はどんな Java クラスでも、Seam コンポーネントになることができます。さらに Seam は、EJB であろうとなかろうといかなるコンポーネントに対しても、「軽量 (lightweight)」コンテナに期待されるすべての機能を提供します。

統合 AJAX

Seamは、最も素晴らしいオープンソースの JSF ベース AJAX ソリューションであるJBoss RichFaces と ICEfaces をサポートします。これらのソリューションは、一切 JavaScript コードを記述する必要なしに、ユーザーインタフェースに AJAX 機能を追加させることができます。

もう一つの方法として、Seam は組み込みの JavaScript リモーティング層を提供していて、中間のアクションレイヤを必要とせずに、クライアント側の JavaScript から非同期にコンポーネントと呼び出すことができます。サーバ側の JMS トピックをサブスクライブして、AJAX プッシュによってメッセージを受信することもできます。

これらのアプローチのどちらも、Seam の組み込みの並行処理制御や状態管理に対してのものではないので、それらの機能に対してはうまく動作しません。しかし、並列に実行される多くの細粒度の非同期の AJAX 要求がサーバサイドで安全にそして効率的に処理されるということを保証します。

ファーストクラス構成要素としてのビジネスプロセス

オプションとして、Seam は jBPM による透過的なビジネスプロセス管理を提供します。jBPM と Seam を利用した複雑なワークフロー、コラボレーション、タスク管理の実装がいかに簡単であるか信じられないことでしょう。

Seam は、jBPM がビジネスプロセス定義に使用するのと同じ言語 (jPDL) をプレゼンテーション層のページフローの定義にも利用することを可能にします。

JSFは、プレゼンテーション層のために信じられないほど豊富なイベントモデルを提供します。Seam の一貫したコンポーネントモデルに対して一貫したイベントモデルを提供することにより、Seam は全く同様のイベント処理メカニズムを jBPM のビジネスプロセス関連イベントにも適用するによってこのモデルを改良します。

宣言的な状態管理

EJB は初期の頃から、宣言的なトランザクション管理と宣言的なセキュリティのコンセプトを採用しています。EJB 3.0 では、宣言的な永続コンテキスト管理さえも導入します。これら三つは、特定のコンテキストに関連付けられたより広範囲での状態管理の問題の例で、コンテキストが終わるときには、それらはすべての確実に破棄することが必要となります。Seamは、宣言的な状態管理のコンセプトをはるかに広くとらえて、アプリケーション状態にもそれを適用します。伝統的にJ2EE アプリケーションでは、サーブレットセッションと要求属性を 保存 (set) そして取得 (get) することによって、状態管理を手動で実装します。状態管理に対するこの手法は、アプリケーションがセッション属性をきれいにし損ねたり、あるいは異なるワークフローに関連したセッションデータがマルチウィンドウのアプリケーションで衝突したりしたときに、多くのバグとメモリリークの発生源となります。Seam には、この種類のバグをほとんど完全に削除できる潜在能力があります。

Declarative application state management is made possible by the richness of the context model defined by Seam. Seam extends the context model defined by the servlet spec—request, session, application—with two new contexts—conversation and business process—that are more meaningful from the point of view of the business logic.

対話コンテキストを利用し始めると、いかに多くのことがより簡単になるか驚かされるでしょう。Hibernate あるいは JPA のような ORM ソリューションで遅延関連フェッチを利用して、障害を経験したことがありませんか。 Seam の対話スコープ永続コンテキストを使用すると、めったに LazyInitializationException を見ることがなくなるということになります。リフレッシュボタンで問題が発生したことがありませんか。 戻るボタンで問題が発生したことがありませんか。送信フォームの二重送信で問題が発生したことがありませんか。post-then-redirect をまたがったメッセージ引継ぎで問題が発生したことがありませんか。Seam の対話管理は、これらの問題を個別に考える必要なしに解決します。これらはすべて、Web の最も初期の頃以来蔓延している中途半端な状態管理アーキテクチャが原因の症状なのです。

バイジェクション

制御の反転 (IoC: Inversion of Control) あるいは 依存性注入 (DI: Dependency Injection) の概念は、多くのいわゆる「軽量コンテナ」と同様に、JSF と EJB3 の両方に存在します。これらのコンテナのほとんどは、ステートレスなサービス を実装するコンポーネントのインジェクションに力点が置かれています。たとえステートフルなコンポーネントのインジェクションがサポートされたとしても (例えば JSF において)、アプリケーション状態を扱う場合においては事実上役に立ちません。なぜならば、ステートフルなコンポーネントのスコープが十分な柔軟性を持って定義されていないので、より広いスコープに属しているコンポーネントをより狭いスコープに属するコンポーネントへインジェクションすることができないからです。

バイジェクション (bijection) は、それが動的 (dynamic) であり、コンテキスト依存 (contextual) であり、そして双方向的 (bidirectional) であるという点で IoC とは異なります。コンテキスト上の変数(現在のスレッドにバインドされたさまざまなコンテキストでの名前)をコンポーネントの属性に別名でアクセスするためのメカニズムだと考えることができます。バイジェクションは、コンテナによるステートフルなコンポーネントを自動的に組み立てることを可能にします。それはコンポーネントの属性に値を代入するだけで、コンポーネントが安全にそして簡単にコンテキスト変数の値を操作することを可能にします。

ワークスペース管理とマルチウィンドウアクセス

Seam アプリケーションは、それぞれが別々の安全に分離された対話に関連付けられた複数のブラウザタブ間を、ユーザーが自由にスイッチすることを可能にします。アプリケーションは、さらにワークスペース管理を利用して、一つのブラウザタブ内で対話 (ワークスペース) の間をユーザーが切り替えることも可能にします。Seam は、正しいマルチウィンドウの操作のみを提供するのではなく、一つのウィンドウ内でのマルチウィンドウ風の操作も提供するのです

XMLよりもアノテーション

伝統的にJavaコミュニティは、どのような種類のメタ情報が構成として重要かについて深い混乱の状態にいました。J2EE と人気がある 「軽量」のコンテナは、XML ベースのデプロイメント記述子を提供し、異なるシステム間でのデプロイの構成を共通化するとともに、Java では簡単に表現できないその他の宣言を可能にしました。Java 5 のアノテーションがこのすべてを変えました。

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

結合テストを簡単に

Seam コンポーネントは、単純な Java クラスであって、本来ユニットテストで十分テストできるものです。しかし複雑なアプリケーションでは、ユニットテストだけは不十分です。伝統的にJava の Web アプリケーションにおいては、結合テストは繁雑で困難な作業でした。それゆえに、Seam はフレームワークのコア機能として、Seam アプリケーションのテスト機能を提供します。システムのすべてのコンポーネントをビュー (JSP ページまたは Facelets ページ)から切り離して動作させることにより、ユーザーとのすべての相互作用を再現する JUnit あるいは TestNG のテストを簡単に記述することができます。これらのテストを直接 IDE の内部で実行することができます。そこでは、Seam が 組み込み型 JBoss を利用して EJB コンポーネントを自動的にデプロイします。

仕様は完璧ではない

Java EEの最新の実装は素晴らしいと思います。しかし、それが決して完全ではないということも知っています。どの仕様にも欠点はあるので(例えば、GET 要求における JSF ライフサイクルの制限)、Seam はそれを修正します。Seam の作者らは、JCP エキスパートグループと一緒に活動していて、それらの修正が標準仕様の次の改訂版に確実に反映されるようにしています。

WebアプリケーションにはHTMLページを供給すること以上の役割がある

今日の Web フレームワークは、あまりにも小さく考え過ぎます。Web フレームワークは、フォームからユーザー入力を取り出し、Java のオブジェクトへ代入します。そしてそのままにしておきます。本当に完全な Web アプリケーションフレームワークは、永続化、並行処理、非同期処理、状態管理、セキュリティ、Eメール、メッセージング、PDFとチャートの生成、ワークフロー、してwiki テキスト、Web サービス、キャッシングその他多数の問題を処理すべきです。一旦 Seam を使用してみれば、いかに多くの問題がより簡単になることに驚くでしょう...

Seamは、永続化のために JPA や Hibernate3 を統合します。軽量な非同期処理のためには EJB タイマサービスや Quartz、ワークフローのために jBPM、ビジネスルールのために JBoss Rules、Eメールのために Meldware Mail、 フルテキスト検索のために Hibernate Search や Lucene、メッセージングのために JMS、ページフラグメントキャッシュのために JBoss Cache を統合します。Seam は、JAAS とJBoss Rules を連携した革新的なルールベースのセキュリティフレームワーク層を提供します。さらに、PDF レンダリングやメール送信、チャート、wiki テキスト のための JSF タグライブラリもあります。Seam コンポーネントは、Web サービスとして同期的に呼び出すことができます。クライアント側の JavaScript あるいは Google Web Toolkit 、またもちろん直接 JSF から非同期的に呼び出すことができます。

さあ、はじめましょう !

Seam は、どの Java EE アプリケーションサーバーでも動作します。さらに、Tomcat でさえも動作します。もしあなたの環境が EJB 3.0 をサポートしているのであれば、すばらしい完璧です!もしサポートしていなくても、問題ありません。永続化のための JPA あるいは Hibernate3 と Seam の組み込みトランザクション管理を使用することができます。あるいは、Tomcat に組み込み型 JBoss をデプロイして、EJB 3.0 に対するフルサポートを受けることもできます。

Seam と JSF と EJB3 の組み合わせが Java で複雑な Web アプリケーションを記述する最もシンプルな方法であることが明らかになります。必要となるコードが信じられないほど少なくなるのです。

Seam は Seam のさまざまな機能の利用方法を実演する多くのサンプルアプリケーションを提供しています。 このチュートリアルはこれらのサンプルを通してあなたが Seam を学び始めるための案内をします。Seam サンプルは Seam ディストリビューションの examples サブディレクトリに置かれています。 初めて見るユーザー登録サンプルは、examples/registration ディレクトリにあります。

各サンプルは同じディレクトリの構造をしています。

サンプルアプリケーションは追加設定することなく JBoss AS と Tomcat で動作します。 これからの章では両方のケースの手順を説明します。 Ant build.xml によりビルドと起動を行いますから、始める前に最新の Ant をインストールする必要があることに留意してください。

サンプルは Tomcat 6.0 用にも構成されています。Tomcat 6.0 組み込み JBossへのインストールは 項29.6.1. 「Embedded JBoss をインストールする」 のインストラクションに従う必要があります。 組み込み JBoss は Tomcat 上で EJB3コンポーネントを利用する Seam デモを動作させるためだけに必要です。 組み込み JBoss を利用しない Tomcat 上で動作可能な non-EJB3 サンプルもあります。

Seam のインストレーションのルートフォルダ内 build.properties ファイル中の tomcat.home に Tomcat のインストレーションの場所を設定する必要があります。 Tomcat の場所を設定したことを確かめてください。

Tomcat を利用する場合には異なった Ant ターゲットを使用する必要があります。 Tomcat 用のサンプルのビルドとデプロイで example サブディレクトリ内の ant tomcat.deploy を使用してください。

On Tomcat, the examples deploy to URLs like /jboss-seam-example, so for the registration example the URL would be http://localhost:8080/jboss-seam-registration/.

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

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

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

このサンプルは、二つの Facelets テンプレート、一つのエンティティ Bean と、一つのステートレスセッション Bean で実装されています。 "bottom" からコードを見てみましょう。

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

例 1.1. User.java

(1)@Entity

(2)@Name("user")
(3)@Scope(SESSION)
(4)@Table(name="users")
public class User implements Serializable
{
   private static final long serialVersionUID = 1881413500711441951L;
   
(5)   private String username;
   private String password;
   private String name;
   
   public User(String name, String password, String username)
   {
      this.name = name;
      this.password = password;
      this.username = username;
   }
   
(6)   public User() {}
   
(7)   @NotNull @Length(min=5, max=15)
   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;
   }
   
(8)   @Id @NotNull @Length(min=5, max=15)
   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 はセッションスコープのコンポーネントです。

4

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

5

namepasswordusername は、 エンティティ Bean の永続属性です。 すべての永続属性はアクセスメソッドを定義しています。 応答のレンダリングフェーズおよびモデル値の更新フェーズで 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 が必要となります。

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

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

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

例 1.2. RegisterAction.java

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

   @In        (2)
   private User user;
   
   @Persistenc(3)eContext
   private EntityManager em;
   
   @Logger    (4)
   private Log log;
   
   public Stri(5)ng register()
   {
      List existing = em.createQuery(
         "sele(6)ct username from User where username=#{user.username}")
         .getResultList();
         
      if (existing.size()==0)
      {
         em.persist(user);
         log.i(7)nfo("Registered new user #{user.username}");
         retur(8)n "/registered.xhtml";
      }
      else
      {
         Faces(9)Messages.instance().add("User #{user.username} already exists");
         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

The Log API lets us easily display templated log messages.

8

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

9

Seam provides a number of built-in components to help solve common problems. The FacesMessages component makes it easy to display templated error or success messages. Built-in Seam components may be obtained by injection, or by calling an instance() method.


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

このセッション Bean のアクションリスナーは、この小さなアプリケーションのビジネスロジックと永続ロジックを提供しています。 さらに複雑なアプリケーションでは、個別のサービスレイヤが必要かもしれません。 Seamで、これをするのは簡単ですが、 ほとんどの Web アプリケーションでは過剰です。 Seam は、アプリケーションのレイヤ化のために特殊な方法を強要しているのではなく、アプリケーションをより簡単に、また望むならばより複雑にすることを可能にしています。

このアプリケーションについて、私たちはこれまで実際に必要とされるよりはるかに複雑にしてきました。 Seam アプリケーションコントローラを利用していたならば、アプリケーションコードのほとんどを排除できたかもしれない。しかしながら、 当時、説明する多くのアプリケーションがありませんでした。

Seam アプリケーションのビューページは、 JSF をサポートする多くの技術を使用して実装されています。 このサンプルでは、JSP より優れていると考えている Facelets を使用しています。


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


This is a simple Facelets page using some embedded EL. There is nothing specific to Seam here.

はじめて Seam アプリを見るので、デプロイメント記述子も見てみます。 その話の前に、Seam が最小限の設定であることは注目に値します。 これらの設定ファイルは、Seamアプリケーションが作成されるときに生成されるものです。 あなたはほとんどこれらのファイルに触れる必要はないでしょう。 サンプルにおいてすべての要素が何をしているかを理解すること助けるためだけにこれらを提示しています。

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

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


This code configures a property named jndiPattern of a built-in Seam component named org.jboss.seam.core.init. The funny @ symbols are there because our Ant build script puts the correct JNDI pattern in when we deploy the application.

ミニアプリケーションのプレゼンテーションレイヤは WAR にデプロイされます。 したがって、Web デプロイメント記述子が必要です。


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

ほとんどの Seam アプリケーションはプレゼンテーション層として JSF ビューを使用します。 従って通常 faces-config.xml が必要です。 この場合ビュー定義に Facelets を使用しますので、JSF にテンプレートエンジンとして Faceles を使用することを指定する必要があります。


Note that we don't need any JSF managed bean declarations! Our managed beans are annotated Seam components. In Seam applications, the faces-config.xml is used much less often than in plain JSF.

In fact, once you have all the basic descriptors set up, the only XML you need to write as you add new functionality to a Seam application is orchestration: navigation rules or jBPM process definitions. Seam takes the view that process flow and configuration data are the only things that truly belong in XML.

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

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


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

これで私たちはアプリケーションにあるすべてのファイルを見ました!

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

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

Next, JSF asks Seam to resolve the variable named register. Seam finds the RegisterAction stateless session bean in the stateless context and returns it. JSF invokes the register() action listener method.

Seam intercepts the method call and injects the User entity from the Seam session context, before continuing the invocation.

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

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

データベースの検索結果をクリック可能一覧とすることは、 多くのオンラインアプリケーションにおいてたいへん重要な部分です。Seam は、EJB-QL またはHQL を使用してデータの問合せを行うことと、 その結果をJSF <h:dataTable> を使用してクリック可能な一覧として表示することを容易にするために、 JSF 上に特別な機能を提供します。 この掲示板サンプルは、この機能を実演しています。

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

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

But MessageManagerBean is also responsible for fetching the list of messages the first time we navigate to the message list page. There are various ways the user could navigate to the page, and not all of them are preceded by a JSF action—the user might have bookmarked the page, for example. So the job of fetching the message list takes place in a Seam factory method, instead of in an action listener method.

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

例 1.11. MessageManagerBean.java

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

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

}
1

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

2

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

3

The @Out annotation then exposes the selected value directly to the page. So ever time a row of the clickable list is selected, the Message is injected to the attribute of the stateful bean, and the subsequently outjected to the event context variable named 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 とマークされたパラメータを持たないメソッドを持つことが必須です。 これによりSeam コンテキストが終わり、サーバサイドの状態をクリーンアップするときに、 Seam はステートフル Bean の削除を行います。


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

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

例 1.13. messages.jsp


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

最初に messages.jsp ページに画面遷移させるとき、ページは 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 を削除します。 最後に、 このページが再度レンダリングされ、 メッセージ一覧を再表示します。

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

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

例 1.14. todo.jpdl.xml

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

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

2

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

3

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

4

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

5

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


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

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

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


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

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


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


より現実的なサンプルでは、 @StartTask@EndTask は同じメソッドの上には登場しません。 なぜなら、通常、タスクを完了するために、アプリケーションを使用して行われる仕事があるからです。

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

例 1.18. todo.jsp


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

一つづつ見ていきましょう。

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


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


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

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


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

ログイン後、todo.jsp は現在のユーザーのための未解決の To-Do 項目を表示するために taskInstanceList を使用します。最初は何もありません。新しいエントリを登録するフォームが表示されます。ユーザーが todo 項目をタイプし、"Create New Item" ボタンを押下するとき、#{todoList.createTodo} は呼ばれます。これは todo.jpdl.xml で定義したプロセスを開始します。

プロセスインスタンスが生成されると、start 状態が開始されすぐに todo 状態に遷移します。そこで新しいタスクが生成されます。 タスク記述はユーザーの入力にしたがって設定されます。それは #{todoList.description} に保存されます。 そして、タスクは現在のユーザーに割り当てられます。それは Seam の actor コンポーネントに保管されます。 このサンプルにおいて、プロセスは追加のプロセス状態を持っていないことに留意してください。 このサンプルにおけるすべての状態はタスク定義に保管されています。 プロセスとタスク情報は要求の最後でデータベースに保管されます。

todo.jsp が再表示されるとき、taskInstanceList はちょうど作成されたタスクを見つけます。 タスクは h:dataTable に表示されます。 タスクの内部状態は #{task.description}#{task.priority}#{task.dueDate} などのカラムに表示されます。 これらのフィールドはすべて編集やデータベースに保管可能です。

各To-Do項目は "Done" ボタンを持っていて、それは #{todoList.done} を呼び出します。 todoList コンポーネントは、各 s:button が taskInstance="#{task}" を指定しているために、どのタスクボタンがテーブルの特定行のためのタスクを参照するかを知っています。 @StartTast@EndTask アノテーションは タスクをアクティブにさせるものと、すぐにタスクを完了させるものです。 プロセス定義にしたがってオリジナルのプロセスが done 状態に遷移します。 そこでそれは終了します。 タスクとプロセスの状態はともにデータベースにアップデートされます。

todo.jsp が再表示されるとき、いま完了したタスクはもう taskInstanceList には表示されません。 なぜならコンポーネントはユーザーにとってアクティブなタスクだけを表示するからです。

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

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

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

例 1.20. pageflow.jpdl.xml

<pageflow-definition 
        xmlns="http://jboss.com/products/seam/pageflow"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://jboss.com/products/seam/pageflow 
                            http://jboss.com/products/seam/pageflow-2.1.xsd"
        name="numberGuess">
   
   <start-page(1) name="displayGuess" view-id="/numberGuess.jspx">
      <redirect/>
      <transit(2)ion name="guess" to="evaluateGuess">
         <acti(3)on expression="#{numberGuess.guess}"/>
      </transition>
      <transition name="giveup" to="giveup"/>
      <transition name="cheat" to="cheat"/>
   </start-page>
              (4)
   <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="giveup" view-id="/giveup.jspx">
      <redirect/>
      <transition name="yes" to="lose"/>
      <transition name="no" to="displayGuess"/>
   </page>
   
   <process-state name="cheat">
      <sub-process name="cheat"/>
      <transition to="displayGuess"/>
   </process-state>
   
   <page name="win" view-id="/win.jspx">
      <redirect/>
      <end-conversation/>
   </page>
   
   <page name="lose" view-id="/lose.jspx">
      <redirect/>
      <end-conversation/>
   </page>
   
</pageflow-definition
>
1

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

2

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

3

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

4

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


以下は JBoss Developer Studio ページフローエディタでどのように表示するかを示しています。

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

これがアプリケーションの中心のページ numberGuess.jspx です。

例 1.21. numberGuess.jspx


<<?xml version="1.0"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns="http://www.w3.org/1999/xhtml"
          version="2.0">
  <jsp:output doctype-root-element="html" 
              doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
              doctype-system="http://www.w3c.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
  <jsp:directive.page contentType="text/html"/>
  <html>
  <head>
    <title
>Guess a number...</title>
    <link href="niceforms.css" rel="stylesheet" type="text/css" />
    <script language="javascript" type="text/javascript" src="niceforms.js" />
  </head>
  <body>
    <h1
>Guess a number...</h1>
    <f:view>
          <h:form styleClass="niceform">
            
            <div>
            <h:messages globalOnly="true"/>
            <h:outputText value="Higher!" 
                   rendered="#{numberGuess.randomNumber gt numberGuess.currentGuess}"/>
            <h:outputText value="Lower!" 
                   rendered="#{numberGuess.randomNumber lt numberGuess.currentGuess}"/>
                </div>
                
                <div>
        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.
        </div>
        
        <div>
        Your guess: 
        <h:inputText value="#{numberGuess.currentGuess}" id="inputGuess" 
                     required="true" size="3" 
                     rendered="#{(numberGuess.biggest-numberGuess.smallest) gt 20}">
          <f:validateLongRange maximum="#{numberGuess.biggest}" 
                               minimum="#{numberGuess.smallest}"/>
        </h:inputText>
        <h:selectOneMenu value="#{numberGuess.currentGuess}" 
                         id="selectGuessMenu" required="true"
                         rendered="#{(numberGuess.biggest-numberGuess.smallest) le 20 and 
                                     (numberGuess.biggest-numberGuess.smallest) gt 4}">
          <s:selectItems value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
        </h:selectOneMenu>
        <h:selectOneRadio value="#{numberGuess.currentGuess}" id="selectGuessRadio" 
                          required="true"
                          rendered="#{(numberGuess.biggest-numberGuess.smallest) le 4}">
          <s:selectItems value="#{numberGuess.possibilities}" var="i" label="#{i}"/>
        </h:selectOneRadio>
                <h:commandButton value="Guess" action="guess"/>
        <s:button value="Cheat" view="/confirm.jspx"/>
        <s:button value="Give up" action="giveup"/>
                </div>
                
                <div>
        <h:message for="inputGuess" style="color: red"/>
        </div>
        
          </h:form>
    </f:view>
  </body>
  </html>
</jsp:root
>

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

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


lose.jspx はほぼ同じです。 説明は省略します。

最後に、実際のアプリケーションコードを見ましょう。

例 1.23. NumberGuess.java

@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess implements Serializable {
   
   private int randomNumber;
   private Integer currentGuess;
   private int biggest;
   private int smallest;
   private int guessCount;
   private int maxGuesses;
   private boolean cheated;
   
   @Create    (1)
   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;
   }

   public void cheated()
   {
      cheated = true;
   }
   
   public boolean isCheat() {
      return cheated;
   }
   
   public List<Integer
> getPossibilities()
   {
      List<Integer
> result = new ArrayList<Integer
>();
      for(int i=smallest; i<=biggest; i++) result.add(i);
      return result;
   }
   
}
1

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


pages.xml ファイルは Seam 対話 (conversation) を開始し ( 詳細は後述 )、対話のページフローを使用するためのページフロー定義を規定します。


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

アプリケーションの基本的なフローを見てみましょう。 ゲームは numberGuess.jspx から始まります。 始めてページが表示されたとき、pages.xml 設定は対話を開始させ numberGuess ページフローを対話と関連付けます。 ページフローは 待機状態であるstart-page から開始されるので numberGuess.xhtml が表示されます。

ビューは numberGuess コンポーネントを参照します。 その結果新しいインスタンスが生成され対話に保管されます。 @Create メソッドが呼ばれゲームの状態が初期化されます。 ビューはユーザーが #{numberGuess.currentGuess} を編集可能な h:form を表示します。

"Guess" ボタンは guess アクションを呼び起こします。 Seam はアクションを処理するためにページフローに従います。それはページフローが evaluateGuess 状態に遷移することを命じます。 最初の呼び出し #{numberGuess.guess} は guess count と numberGuess コンポーネント中の highest/lowest suggestions を更新します。

evaluateGuess 状態は #{numberGuess.correctGuess} の値をチェックし win または evaluatingRemainingGuesses 状態に遷移させます。 数字が間違っていたとすると、その場合ページフローは evaluatingRemainingGuesses に遷移します。 それは decision 状態であり、 ユーザーがまだ数字当てをするか否かを決定するために #{numberGuess.lastGuess} をテストします。 まだ数字あてをするならば ( lastGuessfalse )、最初の displayGuess 状態に遷移させます。 最後に、page 状態に達し、関連するページ /numberGuess.jspx が表示されます。ページは redirect 要素を持っているので、Seam はユーザーのブラウザにリダイレクトを送信しプロセスを再始動させます。

以降の要求により win または lose に遷移する状態以外これ以上状態については説明しません。つまりユーザーが /win.jspx または /lose.jspx を取得することについてです。 両方の状態は Seam が対話を終了し、ユーザーに最終ページをリダイレクトする前に、ゲームの状態やページフローの状態を破棄することを規定しています。

The numberguess example also contains Giveup and Cheat buttons. You should be able to trace the pageflow state for both actions relatively easily. Pay particular attention to the cheat transtition, which loads a sub-process to handle that flow. Although it's overkill for this application, it does demonstrate how complex pageflows can be broken down into smaller parts to make them easier to understand.

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

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

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

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

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

このアプリケーションは以下の機能を実装するビジネスロジックのために 6 つのセッション Bean を使用しています。

  • AuthenticatorAction はログイン認証ロジックを提供します。

  • BookingListAction は、その時のログインユーザーのために現状の予約を取得します。

  • ChangePasswordAction は、その時のログインユーザーのパスワードを変更します。

  • HotelBookingAction は、アプリケーションの中核的機能を実装します。 この機能は 対話 として実装されるため、 このアプリケーションの中でもっとも興味を引くクラスです。

  • HotelSearchingAction はホテル検索を実装しています。

  • RegisterAction は、新しいシステムユーザーを登録します。

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

  • Hotel はホテルを表現するエンティティ Bean です。

  • Booking は、現状の予約を表すエンティティ Bean です。

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

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

ほとんどの WEB アプリケーションのアーキテクチャは対話を扱うためのファーストクラスの構造を持っていません。 これは対話の状態を管理するために重大な問題となります。通常、Java WEB アプリケーションは二つの技術を組み合わせて使用します。 ある状態は URL に変換可能です。 不可能なものはすべての要求の後に HttpSession に投げられるかあるいはデータベースにフラッシュされます。そしてすべての新しい要求の最初にデータベースから再構築されます。

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

さて HttpSession に保管された状態を考察してみましょう。 HttpSession はセッションデータにとってとても便利な場所です。 つまりユーザーがアプリケーションとして持つすべての要求に共通なデータにとって。 しかし、一連の個別の要求に関するデータを保管する場所としては適しません。 戻るボタンや複数のウィンドウを操作するとき、対話的なセッションの使用はすぐに破たんしてしまいます。 それに加えて、慎重なプログラミングがなければ、HTTP セッションのデータはとても大きくなり、HTTP セッションをクラスタに対応させることが困難になる可能性があります。 異なる同時並行的な対話に関連するセッション状態を分離するメカニズムを開発することや、ブラウザウィンドウまたはタブを閉じることでユーザーが対話の一つを中断するときに対話状態が破棄されることを保証するフェイルセーフを組み込むことは簡単な仕事ではありません。 幸いにも Seam にはそんな心配は無用です。

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

このアプリケーションでは、ステートフルセッション Bean を保管するために対話コンテキストを使用します。 Java コミュニティには、ステートフルセッション Bean はスケーラビリティ殺しだというデマが古くからあります。 初期のエンタープライズ Java では真実であったかもしれませんが、今日ではもはや真実ではありません。 今日のアプリケーションサーバーはステートフルセッション Bean の状態を複製するために極めて洗練されたメカニズムを持っています。 例えば、JBoss AS はきめの細かい複製を行い、 実際に変化した bean 属性値のみの複製を行います。 ステートフル Bean が非効率的かという伝統的技術論はすべて HttpSession にも等しく当てはまります。 その結果パフォーマンスを改善するためにビジネス層のステートフルセッション Bean から Web セッションに移行する慣習は驚くほど誤解されていることに留意してください。間違ってステートフル Bean を使用することあるいは間違ったもののためにそれらを使うことによって、スケーラブルでないアプリケーションを書く可能性は確かにあります。 しかしそれは使うべきでないということにはなりません。もし納得できなければ、Seam ではセッション Bean の代わりに POJO を使用することも可能です。 Seam では、選択はあなた次第です。

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

手書きの JavaScript を使用することなくリッチクライアントの振る舞いを実装するためにホテル予約サンプルは RichFaces Ajax の使用を実演しています。

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

例 1.25. HotelSearchingAction.java

@Stateful     (1)
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@Restrict("#{i(2)dentity.loggedIn}")
public class HotelSearchingAction implements HotelSearching
{
   
   @PersistenceContext
   private EntityManager em;
   
   private String searchString;
   private int pageSize = 10;
   private int page;
   
   @DataModel (3)
   private List<Hotel
> hotels;
   
   public void find()
   {
      page = 0;
      queryHotels();
   }
   public void nextPage()
   {
      page++;
      queryHotels();
   }
      
   private void queryHotels()
   {
      hotels = 
          em.createQuery("select h from Hotel h where lower(h.name) like #{pattern} " + 
                         "or lower(h.city) like #{pattern} " + 
                         "or lower(h.zip) like #{pattern} " +
                         "or lower(h.address) like #{pattern}")
            .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;
   }
   
   @Factory(value="pattern", scope=ScopeType.EVENT)
   public String getSearchPattern()
   {
      return searchString==null ? 
            "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
   }
   
   public String getSearchString()
   {
      return searchString;
   }
   
   public void setSearchString(String searchString)
   {
      this.searchString = searchString;
   }          (4)
   
   @Remove
   public void destroy() {}
}
1

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

2

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

3

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

4

EJB 標準の @Remove アノテーションはアノテーションが付けられたメソッドが呼ばれた後ステートフルセッション Bean が取り除かれることを規定しています。 Seam では、すべてのセッション Bean はパラメータなしの @Remove メソッドを定義される必要があります。 Seam がセッションコンテキストを破棄するときメソッドは呼び出されます。


アプリケーションの中心となるページは Facelets ページです。 ホテルを検索に関連する部分を見てみましょう。

例 1.26. main.xhtml

<div class="section">
  
    <span class="errors">
       <h:messages globalOnly="true"/>
    </span>
    
    <h1
>Search Hotels</h1>

        <h:form id="searchCriteria">
        <fieldset
> 
           <h:inputText id="searchString" value="#{hotelSearch.searchString}" 
              (1)      style="width: 165px;">
         <a:support event="onkeyup" actionListener="#{hotelSearch.find}" 
                    reRender="searchResults" />
       </h:inputText>
       &#160;
           <a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}" 
              (2)          reRender="searchResults"/>
       &#160;
       <a:status>
          <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> (3)
    
</div>

<a:outputPanel id="searchResults">
  <div class="section">
    <h:outputText value="No Hotels Found"
                  rendered="#{hotels != null and hotels.rowCount==0}"/>
    <h:dataTable id="hotels" 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>
            #{(4)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 id="viewHotel" value="View Hotel" 
                    action="#{hotelBooking.selectHotel(hot)}"/>
        </h:column>
    </h:dataTable>
    <s:link value="More results" action="#{hotelSearch.nextPage}" 
            rendered="#{hotelSearch.nextPageAvailable}"/>
  </div>
</a:outputPanel
>    
1

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

2

RichFaces Ajax <a:status> タグは非同期の要求が返されるのを待つ間にアニメーションイメージを表示させます。

3

RichFaces Ajax <a:outputPanel> タグは非同期要求によって再レンダリング可能なページの領域を定義します。

4

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

どのようにナビゲーションが起こるかと思うならば、WEB-INF/pages.xml にすべてのルールが定義されていることを見つけるでしょう。 これについては 項6.7. 「ナビゲーション」 で議論します。


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

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

例 1.27. HotelBookingAction.java

@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
   
   @Persistenc(1)eContext(type=EXTENDED)
   private EntityManager em;
   
   @In 
   private User user;
   
   @In(required=false) @Out
   private Hotel hotel;
   
   @In(required=false) 
   @Out(requir(2)ed=false)
   private Booking booking;
     
   @In
   private FacesMessages facesMessages;
      
   @In
   private Events events;
   
   @Logger 
   private Log log;
   
   private boolean bookingValid;
   
   @Begin     (3)
   public void selectHotel(Hotel selectedHotel)
   {
      hotel = em.merge(selectedHotel);
   }
   
   public void 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() );
   }
   
   public void setBookingDetails()
   {
      Calendar calendar = Calendar.getInstance();
      calendar.add(Calendar.DAY_OF_MONTH, -1);
      if ( booking.getCheckinDate().before( calendar.getTime() ) )
      {
         facesMessages.addToControl("checkinDate", "Check in date must be a future date");
         bookingValid=false;
      }
      else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
      {
         facesMessages.addToControl("checkoutDate", 
                                    "Check out date must be later than check in date");
         bookingValid=false;
      }
      else
      {
         bookingValid=true;
      }
   }
   
   public boolean isBookingValid()
   {
      return bookingValid;
   }
   
   @End       (4)
   public void confirm()
   {
      em.persist(booking);
      facesMessages.add("Thank you, #{user.name}, your confimation number " + 
                        " for #{hotel.name} is #{booki g.id}");
      log.info("New booking: #{booking.id} for #{user.username}");
      events.raiseTransactionSuccessEvent("bookingConfirmed");
   }
   
   @End
   public void cancel() {}
   
   @Remove    (5)
   public void destroy() {}
1

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

2

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

3

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

4

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

5

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


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

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

長期対話はマルチウインドウ操作やも戻るボタンに直面してもアプリケーションの状態の一貫性を維持することを容易にします。 残念なことに、長期対話の開始や終了は通常十分ではありません。 アプリケーション要件に応じて、ユーザーの期待するもとのアプリケーションの状態の現実の間の矛盾は結果としてまだ生じます。

ネストされたホテル予約アプリケーションは部屋の選択を関連づけるためホテル予約アプリケーションの機能を拡張しています。 それぞれのホテルはユーザーが選択するために宿泊可能な部屋を説明付きで持っています。 これはホテルの予約の流れにおいて部屋選択ページの機能追加を必要とします。

ユーザーはその時予約に含まれるべき宿泊可能な部屋のオプションを持っています。 これまで見たホテルの予約アプリケーションと同様に、これは状態の一貫性の問題を引き起こす可能性があります。 HTTPSession に状態を保管するように、対話変数が変更すると同じ対話コンテキストの中で動作しているすべてのウィンドウに影響します。

これを実演するために、ユーザーが一つの新しいウィンドウでの中でまったく同じの選択画面を表示させたとします。 そしてユーザーは Wonderful Room を選択して確認画面に進みます。 上流の生活を過ごすのにどれだけかかるかを見るために、ユーザーはもともとの画面を戻して、予約のために Fantastic Suite を選択し、再び確認に進みます。 総費用を見直した後に、ユーザーは実用性を重視して確認のために Wonderful Room を表示するウィンドウに戻ることを決めました。

このシナリオでは、単純にすべての状態を対話に保管するならば、同じ対話なかにある複数ウィンドウの操作を保護できません。 ネストされた対話は同じ対話中でコンテキストが変更するときでさえユーザーの正しい振る舞いを達成可能にしています。

さあ、ネストされたホテル予約サンプルがネストされた対話を使用することでどのようにホテル予約アプリケーション機能拡張させているか見てみましょう。 繰り返しになりますが、物語のようにクラスを徹底的に読むことができます。

例 1.28. RoomPreferenceAction.java

@Stateful
@Name("roomPreference")
@Restrict("#{identity.loggedIn}")
public class RoomPreferenceAction implements RoomPreference 
{

   @Logger 
   private Log log;

   @In private Hotel hotel;
   
   @In private Booking booking;

   @DataModel(value="availableRooms")
   private List<Room
> availableRooms;

   @DataModelSelection(value="availableRooms")
   private Room roomSelection;
    
   @In(required=false, value="roomSelection")
   @Out(required=false, value="roomSelection")
   private Room room;

   @Factory("a(1)vailableRooms")
   public void loadAvailableRooms()
   {
      availableRooms = hotel.getAvailableRooms(booking.getCheckinDate(), booking.getCheckoutDate());
      log.info("Retrieved #0 available rooms", availableRooms.size());
   }

   public BigDecimal getExpectedPrice()
   {
      log.info("Retrieving price for room #0", roomSelection.getName());
      
      return booking.getTotal(roomSelection);
   }
              (2)
   @Begin(nested=true)
   public String selectPreference()
   {
      log.info("Room selected");
              (3)
      this.room = this.roomSelection;
      
      return "payment";
   }

   public String requestConfirmation()
   {
      // all validations are performed through the s:validateAll, so checks are already
      // performed
      log.info("Request confirmation from user");
      
      return "confirm";
   }

   @End(before(4)Redirect=true)
   public String cancel()
   {
      log.info("ending conversation");

      return "cancel";
   }

   @Destroy @Remove                                                                      
   public void destroy() {}        
}
1

The hotel instance is injected from the conversation context. The hotel is loaded through an extended persistence context so that the entity remains managed throughout the conversation. This allows us to lazily load the availableRooms through an @Factory method by simply walking the assocation.

2

@Begin(nested=true) に出会うとき、ネストされた対話は対話スタックにプッシュされます。 ネストされた対話中で実行するとき、コンポーネントはそれでも対話の外のすべての状態にアクセスできますが、ネストされた対話の状態コンテナに値を設定することは対話の外側には影響しません。 加えて、ネストされた対話は同じ外側の対話に対して同時並行的にスタックされて存在することが可能で、それぞれの状態は独立しています。

3

roomSelection@DataModelSelection に基づいた対話にアウトジェクトされます。 ネストされた対話は独立したコンテキストを持つので、roomSelection は新たなネストされた対話にのみ設定されることに留意してください。 ユーザーが別のウインドウまたはタブで違う好みを選ぶならば新たなネストされた対話が開始されます。

4

@End アノテーションは対話スタックからポップし外側の対話を再開します。 対話コンテキストとともに roomSelection は破棄されます。


ネストされた対話にあるとき対話スタックにプッシュされます。 nestedbooking サンプルでは、対話スタックは外側の長期対話 (booking) とそれぞれのネストされた対話 (room selections) から構成されます。

例 1.29. rooms.xhtml

<div class="section">
        <h1
>Room Preference</h1>
</div>

<div class="section">
        <h:form id="room_selections_form">
                <div class="section">
                        <h:outputText styleClass="output" 
                                value="No rooms available for the dates selected: " 
                                rendered="#{availableRooms != null and availableRooms.rowCount == 0}"/>
                        <h:outputText styleClass="output" 
                                value="Rooms available for the dates selected: " 
                                rendered="#{availableRooms != null and availableRooms.rowCount 
> 0}"/>
                                
                        <h:outputText styleClass="output" value="#{booking.checkinDate}"/> -
                        <h:outputText styleClass="output" value="#{booking.checkoutDate}"/>
              (1)          
                        <br/><br/>
                        
                        <h:dataTable value="#{availableRooms}" var="room" 
                                        rendered="#{availableRooms.rowCount 
> 0}">
                                <h:column>
                                        <f:facet name="header"
>Name</f:facet>
                                        #{room.name}
                                </h:column>
                                <h:column>
                                        <f:facet name="header"
>Description</f:facet>
                                        #{room.description}
                                </h:column>
                                <h:column>
              (2)                          <f:facet name="header"
>Per Night</f:facet>
                                        <h:outputText value="#{room.price}">
                                                <f:convertNumber type="currency" currencySymbol="$"/>
                                        </h:outputText>
                                </h:column>
                                <h:column>
                                        <f:facet name="header"
>Action</f:facet>
              (3)                          <h:commandLink id="selectRoomPreference" 
                                                action="#{roomPreference.selectPreference}"
>Select</h:commandLink>
                                </h:column>
                        </h:dataTable>
                </div>
                <div class="entry">
                        <div class="label"
>&#160;</div>
                        <div class="input">
                                <s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
                        </div>
                </div
>        
        </h:form>
</div>
1

EL から要求されるとき、RoomPreferenceAction に定義された @Factory メソッドにより #{availableRooms} がロードされます。 @Factory メソッドは @DataModel インスタンスのような現在のコンテキストに値をロードするときに 1 度だけ実行されます。

2

#{roomPreference.selectPreference} アクションを呼び出すことにより行が選択され @DataModelSelection に値が設定されます。 そして値はネストされた対話コンテキストにアウトジェクトされます。

3

日付の変更は単純に /book.xhtml に返されます。 まだネストされた対話ではないこと ( room preference は選ばれていない )、現在の対話は単純にレジューム可能であることに留意してください。 <s:button > コンポーネントは /book.xhtml ビューを表示するときに単純に現在の対話を伝播します。


今や対話のネスティングの方法がわかったので、部屋が選ばれたらどのように予約を確認することができるかを見てみましょう。 これは HotelBookingAction.の振る舞いを単に拡張することによって達成可能です。

例 1.30. HotelBookingAction.java

@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
   
   @PersistenceContext(type=EXTENDED)
   private EntityManager em;
   
   @In 
   private User user;
   
   @In(required=false) @Out
   private Hotel hotel;
   
   @In(required=false) 
   @Out(required=false)
   private Booking booking;
   
   @In(required=false)
   private Room roomSelection;
   
   @In
   private FacesMessages facesMessages;
      
   @In
   private Events events;
   
   @Logger 
   private Log log;
   
   @Begin
   public void selectHotel(Hotel selectedHotel)
   {
      log.info("Selected hotel #0", selectedHotel.getName());
      hotel = em.merge(selectedHotel);
   }
   
   public String setBookingDates()
   {
      // the result will indicate whether or not to begin the nested conversation
      // as well as the navigation.  if a null result is returned, the nested
      // conversation will not begin, and the user will be returned to the current
      // page to fix validation issues
      String result = null;

      Calendar calendar = Calendar.getInstance();
      calendar.add(Calendar.DAY_OF_MONTH, -1);

      // validate what we have received from the user so far
      if ( booking.getCheckinDate().before( calendar.getTime() ) )
      {
         facesMessages.addToControl("checkinDate", "Check in date must be a future date");
      }
      else if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
      {
         facesMessages.addToControl("checkoutDate", "Check out date must be later than check in date");
      }
      else
      {
         result = "rooms";
      }

      return result;
   }
   
   public void 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() );
   }
   
   @End(root=true)
   public void(1) confirm()
   {
      // on confirmation we set the room preference in the booking.  the room preference
      // will be injected based on the nested conversation we are in.
      booking.setRoomPreference(roomSelection);
              (2)
      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.raiseTransactionSuccessEvent("bookingConfirmed");
   }
   
   @End(root=t(3)rue, beforeRedirect=true)
   public void cancel() {}
   
   @Destroy @Remove
   public void destroy() {}
}
1

Annotating an action with @End(root=true) ends the root conversation which effectively destroys the entire conversation stack. When any conversation is ended, it's nested conversations are ended as well. As the root is the conversation that started it all, this is a simple way to destroy and release all state associated with a workspace once the booking is confirmed.

2

roomSelection はユーザー確認において booking とだけ関連します。 ネストされた対話に値をアウトジェクトする間にコンテキストは外側の対話に影響を与えません。外側の対話からインジェクトされるオブジェクトは参照によりインジェクトされます。 これはオブジェクトに対する変更は別ののネストされた対話だけでなく親の対話にも反映されることを意味しています。

3

@End(root=true, beforeRedirect=true) で簡単にアクションをキャンセルするアノテーションによりユーザーをホテル選択ビューにリダイレクトする前に容易にワークスペースに関連するすべての状態を破棄しリリースすることが可能です。


気軽にアプリケーションをデプロイし、たくさんのウィンドウやタブを開きさまざまな好みの部屋によるさまざまなホテルの組み合わせを試してみて下さい。 予約確認はネストされた対話モデルのおかげで正しいホテルと好みの部屋をもたらします。

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

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

index.xhtml facelets ページの一部は最新のブログエントリの一覧を表示しています。


If we navigate to this page from a bookmark, how does the #{blog.recentBlogEntries} data used by the <h:dataTable> actually get initialized? The Blog is retrieved lazily—"pulled"—when needed, by a Seam component named blog. This is the opposite flow of control to what is used in traditional action-based web frameworks like Struts.


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

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


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


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

But when we redirect, we need to include the values submitted with the form in the URL get a bookmarkable URL like http://localhost:8080/seam-blog/search/. JSF does not provide an easy way to do this, but Seam does. We use two Seam features to accomplish this: page parameters and URL rewriting. Both are defined in WEB-INF/pages.xml:


検索ページへの要求があるときや検索ページへのリンクが生成されるときはいつでも、ページパラメータは Seam に searchPattern という名前の要求パラメータを #{searchService.searchPattern} の値にリンクすることを指示します。 Seam は URL とアプリケーションの状態のリンクについて維持することに責任を持ちます。 私たちや開発者はそれを心配する必要はありません。

Without URL rewriting, the URL for a search on the term book would be http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book. This is nice, but Seam can make the URL even simpler using a rewrite rule. The first rewrite rule, for the pattern /search/{searchPattern}, says that any time we have have a URL for search.xhtml with a searchPattern request parameter, we can fold that URL into the simpler URL. So,the URL we saw earlier, http://localhost:8080/seam-blog/seam/search.xhtml?searchPattern=book can be written instead as http://localhost:8080/seam-blog/search/book.

ページパラメータと同様に、URL 書き換えは両方向です。 Seam はより簡単な URL の要求を適切なビューにフォーワードすること、そして簡単なビューを自動的に生成することも意味します。 唯一の要件は URL を書き換えするために、書き換えフィルタが components.xml において使用可能であることです。

<web:rewrite-filter view-mapping="/seam/*" />

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


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

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

@Name("searchService")

public class SearchService 
{
   
   @In
   private FullTextEntityManager entityManager;
   
   private String searchPattern;
   
   @Factory("searchResults")
   public List<BlogEntry
> getSearchResults()
   {
      if (searchPattern==null || "".equals(searchPattern) ) {
         searchPattern = null;
         return entityManager.createQuery("select be from BlogEntry be order by date desc").getResultList();
      }
      else
      {
         Map<String,Float
> boostPerField = new HashMap<String,Float
>();
         boostPerField.put( "title", 4f );
         boostPerField.put( "body", 1f );
         String[] productFields = {"title", "body"};
         QueryParser parser = new MultiFieldQueryParser(productFields, new StandardAnalyzer(), boostPerField);
         parser.setAllowLeadingWildcard(true);
         org.apache.lucene.search.Query luceneQuery;
         try
         {
            luceneQuery = parser.parse(searchPattern);
         }
         catch (ParseException e)
         {
            return null;
         }
         return entityManager.createFullTextQuery(luceneQuery, BlogEntry.class)
               .setMaxResults(100)
               .getResultList();
      }
   }
   public String getSearchPattern()
   {
      return searchPattern;
   }
   public void setSearchPattern(String searchPattern)
   {
      this.searchPattern = searchPattern;
   }
}

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

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

@Name("entryAction")

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

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


<pages>
   ...

    <page view-id="/entry.xhtml"
> 
        <rewrite pattern="/entry/{blogEntryId}" />
        <rewrite pattern="/entry" />
        
        <param name="blogEntryId" 
               value="#{blogEntry.id}"/>
        
        <action execute="#{entryAction.loadBlogEntry(blogEntry.id)}"/>
    </page>
    
    <page view-id="/post.xhtml" login-required="true">
        <rewrite pattern="/post" />
        
        <action execute="#{postAction.post}"
                if="#{validation.succeeded}"/>
        
        <action execute="#{postAction.invalid}"
                if="#{validation.failed}"/>
        
        <navigation from-action="#{postAction.post}">
            <redirect view-id="/index.xhtml"/>
        </navigation>
    </page>

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

</pages
>

このサンプルはポストバリデーションとページビューカウンタのためにページアクションを使用していことに留意してください。 同様にページアクションメソッドバインディングでのパラメータの使用にも留意してください。 これは標準 JSF EL の機能ではありませんが、Seam はページアクションだけでなく JSF メソッドバインディングでも使用を可能にしています。

When the entry.xhtml page is requested, Seam first binds the page parameter blogEntryId to the model. Keep in mind that because of the URL rewriting, the blogEntryId parameter name won't show up in the URL. Seam then runs the page action, which retrieves the needed data—the blogEntry—and places it in the Seam event context. Finally, the following is rendered:


<div class="blogEntry">
    <h3
>#{blogEntry.title}</h3>
    <div>
        <s:formattedText 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 であって欲しいので、 例外クラスのアノテーションを付けます。

@ApplicationException(rollback=true)

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

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

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

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

ブログデモはまたとても簡単なパスワード認証、ブログのポスト、ページの一部のキャッシュ、 atom フィードの生成も実演しています。

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

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

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

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

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

始める前に、JDK 5 または JDK 6 ( 詳細は 項40.1. 「JDK の依存性」 参照 ) と JBoss AS 4.2 と 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 が発生した時点で対処してください。

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

cd jboss-seam-2.1.x
seam setup

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

~/workspace/jboss-seam$ ./seam setup
Buildfile: build.xml

init:

setup:
     [echo] Welcome to seam-gen :-)
    [input] Enter your Java project workspace (the directory that contains your Seam projects) [C:/Projects] [C:/Projects]
/Users/pmuir/workspace
    [input] Enter your JBoss home directory [C:/Program Files/jboss-4.2.2.GA] [C:/Program Files/jboss-4.2.2.GA]
/Applications/jboss-4.2.2.GA
    [input] Enter the project name [myproject] [myproject]
helloworld
     [echo] Accepted project name as: helloworld
    [input] Select a RichFaces skin (not applicable if using ICEFaces) [blueSky] ([blueSky], classic, ruby, wine, deepMarine, emeraldTown, sakura, DEFAULT)

    [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] [com.mydomain.helloworld]
org.jboss.helloworld
    [input] Enter the Java package name for your entity beans [org.jboss.helloworld] [org.jboss.helloworld]

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

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

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

    [input] Enter the JDBC URL for your database [jdbc:mysql:///test] [jdbc:mysql:///test]
jdbc:mysql:///helloworld
    [input] Enter database username [sa] [sa]
pmuir
    [input] Enter database password [] []

    [input] skipping input as property hibernate.default_schema.new has already been set.
    [input] Enter the database catalog name (it is OK to leave this blank) [] []

    [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
    [input] Enter your ICEfaces home directory (leave blank to omit ICEfaces) [] []

[propertyfile] Creating new property file: /Users/pmuir/workspace/jboss-seam/seam-gen/build.properties
     [echo] Installing JDBC driver jar to JBoss server
     [echo] Type 'seam create-project' to create the new project

BUILD SUCCESSFUL
Total time: 1 minute 32 seconds
~/workspace/jboss-seam $ 

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

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

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

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

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

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

...

new-project:
     [echo] A new Seam project named 'helloworld' was created in the C:\Projects 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 が Java SE 5 あるいは Java SE 6 の JDK でなければ、 プロジェクト -> プロパティ -> Java コンパイラ の手順で、Java SE 5 互換の JDK を選ぶ必要があります。

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

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

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

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

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

seam new-action

Seam は情報のために質問をしてきます。そして、プロジェクトのための新しい facelets page や Seam コンポーネントを生成します。

C:\Projects\jboss-seam>seam new-action
Buildfile: 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\helloworld\src\hot\org\jboss\helloworld
     [copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld
     [copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
     [copy] Copying 1 file to C:\Projects\helloworld\src\hot\org\jboss\helloworld\test
     [copy] Copying 1 file to C:\Projects\helloworld\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 ターゲットを起動します。

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


<core:init debug="true"
>

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

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

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

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

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

Seam 2.0 は JavaServer Faces 1.2 にデプロイされます。 JBoss AS を使用するならば、JBoss 4.2 を使用することを推奨します。 これは JSF 1.2 リファレンス実装がバンドルされています。 でも JBoss 4.0 プラットフォームで Seam 2.0 を使用することも可能です。 これを行うには 2 つの基本的なステップが必要です。 EJB3 が有効なバージョンの JBoss 4.0 のインストール、 MyFaces を JSF 1.2 リファレンス実装に交換。 これらのステップを行えば、Seam 2.0 アプリケーションは JBoss 4.0 にデプロイ可能になります。

JBoss Tools は Eclipse プラグインを集めたものです。 JBoss Tools は Seam プロジェクト作成ウィザード、facelets と Java コードの Unified Expression Language (EL) のための入力補助、jPDLのためのグラフィックエディタ、Seam 設定ファイルのためのグラフィックエディタ、Eclipse から Seam 統合テストの実行サポートなどです。

端的にいえば、Eclipse ユーザーであれば、JBoss Tools を必要とするでしょう。

seam-gen 同様 JBoss Tools は JBoss AS と動作させるのが好ましいのですが、わずかに変更することで他のアプリケーションサーバーでも動作させることが可能です。 変更はこのリファレンスマニュアル中の seam-gen の記述と似ています。

Eclipse を起動して Seam パースペクティブを選択してください。

File -> New -> Seam Web Project とすすめます。

最初に、プロジェクト名を登録します。 このチュートリアルでは helloworld とします。

次に、JBoss Tools に JBoss AS について指定します。 これは 二段階のプロセスです。 最初にランタイムを定義します。 JBoss AS 4.2 を選択してください。

ランタイムの名前を登録し、ハードディスク上の位置を指定します。

次に、JBoss Tools がプロジェクトをデプロイ可能なサーバを定義する必要があります。 ここでも JBoss AS 4.2 と直前で定義したランタイムを選択してください。

次のサーバに名前をつける画面では、Finish を押してください。

いま作成と選択をしたランタイムとサーバを確認して、Dynamic Web Project with Seam 2.0 (technology preview) を選択して Next を押してください。

The next 3 screens allow you to further customize your new project, but for us the defaults are fine. So just hit <empahsis>Next</empahsis> until you reach the final screen.

最初のステップは JBoss Tools を使用する上で必要な Seam ランタイムを指定します。 新しい Seam Runtime追加します。 - 名前をつけて、バージョンとして 2.0 を選択してください。

決める必要がある重要な選択は、プロジェクトとして EAR 形式デプロイと WAR 形式デプロイのどちらにするかです。 EAR プロジェクトは EJB 3.0 に対応し Java EE 5 が必要です。 WAR プロジェクトは EJB 3.0 には対応しませんが、く J2EE 環境にデプロイ可能です。 WAR は EAR に比べシンプルで理解しやすいパッケージです。 JBoss のような EJB3 が実行可能なアプリケーションサーバーにインストールする場合は EAR を選択してください。 これ以外は WAR を選択してください。 以降、 このチュートリアルでは WAR デプロイメントが選択されたと仮定しますが、 EAR デプロイメントの場合もまったく同じステップで進むことができます。

次に、データベースのタイプを選択します。 ここでは既存のスキーマを持つ MySQL がインストールされていることを前提とします。 JBoss Tools にデータベースについて指定する必要があります、データベースとして MySQL を選択して、新たにコネクションプロファイルを作成してください。 Generic JDBC Connection を選択してください。

名前をつけてください。

JBoss Tools はデータベースドライバを持っていないので、JBoss Tools にどこに MySQL JDBC があるかを指定する必要があります。 クリックしてドライバに関して指定してください。

MySQL 5 の場所で、Add... を押してください。

MySQL JDBC Driver テンプレートを選択してください。

Edit Jar/Zip を選択することでコンピュータ上の jar の位置を指定してください。

接続のためのユーザー名とパスワードを確認して正しければ、Ok を押してください。

最後に新規に作成されたドライバを選択してください。

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

接続のためのユーザー名とパスワードを確認して、Test Connection ボタンを使用して接続をテストします。 動作したならば、Finish を押します。

最後に、生成された Bean のパッケージ名を確認して、問題なければ、Finish をクリックします。

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

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

十分なメモリがない場合には、以下が最小の推奨値です。

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

JBoss Server View にサーバを配置し、サーバの上で右クリックして Edit Launch Configuration を選択してください。

そして、VM 引数を変更してください。

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

JBoss の起動、プロジェクトのデプロイのためには、作成したサーバの上の右クリックして Start をクリックしてください。 また、デバッグモードで起動するには Debug をクリックしてください。

プロジェクトディレクトリに XML 設定ドキュメントが生成されますがびっくりしないでください。 これらのほとんどが標準 Java EE に関するもので、 一度生成したら 二度と見る必要のないものです。 すべての Seam プロジェクトで 90% は同じものです。

JBoss Tools は以下について増分ホットデプロイメントをサポートします。

out of the box.

ただし Java コードを変更したいのならば、Full Publish することでアプリケーションを再起動する必要があります。

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

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

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

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

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

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

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

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

順に、それぞれのコンテキストを見ていきましょう。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 は以下の コンポーネントタイプ をサポートします。

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

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

新しいインスタンスが各要求で使用されるのと同様にステートレスセッション Bean は同時並行的にアクセスされることが可能です。 インスタンスを要求に割り当てることは EJB3 コンテナの責務です。 ( 通常インスタンスは再利用可能なプールから割り当てられます、 つまり、Bean の使用済みのものからデータを含むインスタンス変数を見つけることができることを意味します。)

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

Seam ステートレスセッション Bean コンポーネントは Component.getInstance() または @In(create=true) を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new オペレータでインスタンス化されるべきではありません。

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

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

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

セッションスコープのステートレスセッション Bean への同時並行要求は、 そのBeanへのSeamインタセプタが無効にされていない限り、常に Seam によってシリアライズされます。

Seam ステートフルセッション Bean コンポーネントは Component.getInstance() または @In(create=true) を使用してインスタンス化可能です。これらは JNDI ルックアップや 直接 new オペレータでインスタンス化されるべきではありません。

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

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

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

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

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

Seam エンティティ Bean コンポーネントは Component.getInstance() または @In(create=true) を使用してインスタンス化可能です。あるいは、直接 new オペレータを使用することが可能です。

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

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    ... 
}

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

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

Just like in JSF, a seam component instance is usually bound to a context variable with the same name as the component name. So, for example, we would access the LoginAction using Contexts.getStatelessContext().get("loginAction"). In particular, whenever Seam itself instantiates a component, it binds the new instance to a variable with the component name. However, again like JSF, it is possible for the application to bind a component to some other context variable by programmatic API call. This is only useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable.

For very large applications, and for built-in seam components, qualified names are often used.

@Name("com.jboss.myapp.loginAction")

@Stateless
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の名前空間をインポートする機能によって非修飾名でもアクセスすることができます。Seam JARに含まれる components.xml ファイルは以下の名前空間を定義します。

<components xmlns="http://jboss.com/products/seam/components">
    
    <import>org.jboss.seam.core</import>
    <import>org.jboss.seam.cache</import>
    <import>org.jboss.seam.transaction</import>
    <import>org.jboss.seam.framework</import>
    <import>org.jboss.seam.web</import>
    <import>org.jboss.seam.faces</import>
    <import>org.jboss.seam.international</import>
    <import>org.jboss.seam.theme</import>
    <import>org.jboss.seam.pageflow</import>
    <import>org.jboss.seam.bpm</import>
    <import>org.jboss.seam.jms</import>
    <import>org.jboss.seam.mail</import>
    <import>org.jboss.seam.security</import>
    <import>org.jboss.seam.security.management</import>  
    <import>org.jboss.seam.security.permission</import>
    <import>org.jboss.seam.captcha</import>
    <import>org.jboss.seam.excel.exporter</import>
    <!-- ... --->
</components>

修飾された名前を解決するときは、Seamは順にそれぞれの名前空間を調べます。アプリケーション固有の名前空間のためにはアプリケーションのcomponents.xmlファイルに追加する名前空間を含めます。

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

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

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

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In User user;
    ... 
}

あるいは、setter メソッドにインジェクトされます。

@Name("loginAction")

@Stateless
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
public class LoginAction implements Login { 
    @In("#{user.username}") String username;
    ... 
}

Injected values are disinjected (i.e, set to null) immediately after method completion and outjection.

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

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

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @Out User user;
    ... 
}

あるいは getter メソッドからアウトジェクトされます。

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    User user;
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

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

@Name("loginAction")

@Stateless
public class LoginAction implements Login { 
    @In @Out User user;
    ... 
}

または、

@Name("loginAction")

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

セッション Bean とエンティティ Bean Seam コンポーネントは通常の EJB 3.0 のライフサイクルのコールバック (@PostConstruct@PreDestroy など) のすべてをサポートしています。 しかも、Seam は JavaBean コンポーネント でのこれらコールバックの使用もサポートしています。 しかし、これらのアノテーションは J2EE 環境では有効とならないため、Seam は @PostConstruct@PreDestroy と等価な 2 つの追加コンポーネントライフサイクルコールバックを定義しています。

@Create メソッドは Seam がコンポーネントをインスタンス化した後に呼ばれます。 コンポーネントは 1 つの @Createメソッドのみ定義可能です。

@Destroy メソッドは Seam コンポーネントがバインドするコンテキストが終了するときに呼ばれます。 コンポーネントは 1 つの @Destroy メソッドのみ定義可能です。

さらに、ステートフルセッション Bean コンポーネントはパラメータ無しの@Removeを付けることが必須 です。このメソッドはコンテキストが終了するときに Seam により呼ばれます。

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

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

@Install優先順位依存性 を指定することで動作します。

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

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

@Name("messageSender") 

public class MessageSender {
    public void sendMessage() {
        //do something with JMS
    }
}

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

@Name("messageSender") 

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

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

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

以下のようなうっとうしいコードを見るのに飽き飽きしていませんか。

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);
}

It doesn't matter if you declare the log variable static or not—it will work either way, except for entity bean components which require the log variable to be static.

ストリング連結は、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 を使用します。

アプリケーションが明示的に 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 Account getAccount()
    {
        return account;
    }
    
    ...
    
}

Seam アプリケーションフレームワークにおいて EntityHome クラスは Seam コンポーネントを使用することでエンティティ Bean インスタンスを管理する優れたサンプルを提供していることに留意してください。

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

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

@Factory(scope=CONVERSATION)

public List<Customer
> getCustomerList() { 
    return ... ;
} 

二番目のスタイルは、 値をコンテキスト変数そのものにバインドした 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 ... ;
    }
}

マネージャコンポーネントパターンはコンポーネントのライフサイクルでより制御を必要とする場面でオブジェクトがあるとき特に有用です。 例えば、コンテキスト終了時にクリーンアップを必要とする重量級のオブジェクトがあるとき、オブジェクトを @Unwrap し、マネージャコンポーネントの @Destroy メソッドでクリーンアップすることが可能です。

@Name("hens")

@Scope(APPLICATION) 
public class HenHouse {
    
    Set<Hen
> hens;
    
    @In(required=false) Hen hen;
    
    @Unwrap
    public List<Hen
> getHens() {
        if (hens == null) {
            // Setup our hens
        }
        return hens;
    }
    
    @Observer({"chickBorn", "chickenBoughtAtMarket"})
    public addHen() {
        hens.add(hen);
    }
    
    @Observer("chickenSoldAtMarket")
    public removeHen() {
        hens.remove(hen);
    }
    
    @Observer("foxGetsIn")
    public removeAllHens() {
        hens.clear();
    }
    ...
} 

ここでは管理コンポーネントが基礎をなすオブジェクトの多くのイベント監視をしています。 コンポーネントはこれらのアクションそのものを管理し、オブジェクトはアクセスごとにアンラップされるために一貫性のあるビューが提供されます。

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

components.xmlファイルはプロパティ設定に比べパワフルです。 次を行うことができます。

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

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

例えば、次の components.xml ファイルはjBPMをインストールします。


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:bpm="http://jboss.com/products/seam/bpm">
    <bpm:jbpm/>
</components
>

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


<components>
    <component class="org.jboss.seam.bpm.Jbpm"/>
</components
>

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


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

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

</components
>

これは以下と同じです。


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

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


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

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

</components
>

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

</components
>

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


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

  <persistence: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.persistence.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サンプルプログラムで見ることができます。

もしも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パッケージ内のすべてのクラスの構成をすることも可能です。

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

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.bpm.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml

<bpm:jbpm>
    <bpm:process-definitions>
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </bpm:process-definitions>
</bpm:jbpm
>

<component name="org.jboss.seam.bpm.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
>

When configuring multi-valued properties, by default, Seam will preserve the order in which you place the attributes in components.xml (unless you use a SortedSet/SortedMap then Seam will use TreeMap/TreeSet). If the property has a concrete type (for example LinkedList Seam will use that type.

次のように完全修飾名を指定することでその型を上書きすることも可能です。


<component name="issueEditor">
   <property name="issueStatusOptions" type="java.util.LinkedHashMap">
      <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
>

SeamはコンポーネントのBeanプロパティへ初期値を代入する前にEL式の文字列も解決します。そこでコンテキスト依存データをコンポーネントにインジェクトすることも可能になります。


<component name="greeter" class="com.example.action.Greeter">
    <property name="message"
>Nice to see you, #{identity.username}!</property>
</component
>

しかし、一つ重要な例外があります。もしも初期値が代入されようとするプロパティの型がSeamのValueExpression または MethodExpressionであるなら、そのEL式の評価は遅延されます。その代わり、適切な式のラッパーが生成されてそのプロパティに代入されます。SeamアプリケーションフレームワークでのHomeコンポーネントのメッセージテンプレートがその一例になります。


<framework:entity-home name="myEntityHome"
    class="com.example.action.MyEntityHome" entity-class="com.example.model.MyEntity"
    created-message="'#{myEntityHome.instance.name}' has been successfully added."/>

コンポーネントの内部では、 ValueExpression または MethodExpression 上で getExpressionString() を呼び出すことによって式の文字列にアクセス可能です。もしもそのプロパティが ValueExpressionであるなら、その値を getValue() によって解決可能ですし、もしもそのプロパティがMethodExpressionであるなら、 invoke(Object args...)を使ってそのメソッドを呼び出すことができます。MethodExpressionプロパティ へ値を代入するためには、その初期値全体は単一のEL式でなければなりません。

例に示す通り、コンポーネントを宣言するには、XML名前空間を使用する、使用しないという二つの相異なる方法があります。以下は名前空間を使用しない典型的なcomponents.xmlファイルを示します。


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

    <component class="org.jboss.seam.core.init">
        <property name="debug"
>true</property>
        <property name="jndiPattern"
>@jndiPattern@</property>
    </component>
    
</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-2.1.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

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

</components
>

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

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

これらのサンプルは名前空間付き要素の二つの利用モデルを説明します。 最初の宣言では<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によって使用済みの名前空間です。

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

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


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

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


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

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

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

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

TODO: translate struts action into page action

non-faces要求 (たとえば、 HTTP Get 要求) に対する応答で複雑な処理をしたい場合などに非常に便利です。

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


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

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

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

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

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


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

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

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

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


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

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

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

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



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

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

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



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

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

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



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

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

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

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


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

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


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

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


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

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


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

さらに良いことには、モデルベースのHiberanteバリデータアノテーションは自動的に認識されて妥当性の検証をします。

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

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

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

このJSFナビゲーション規則は、


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

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


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

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


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

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


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

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

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


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

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


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

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


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

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


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

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


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

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

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

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


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

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

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

@Name("helloWorld")

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

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

@Name("helloWorld")

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

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

@Name("helloListener")

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

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

@Name("helloListener")

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

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

@Name("helloWorld")

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

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

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

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

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

public class LoggedInInterceptor {


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

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

@Target(TYPE)

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

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

@Stateless

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

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

@Interceptor(around={BijectionInterceptor.class,

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

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

@Interceptor(type=CLIENT)

public class LoggedInInterceptor
{
    ...
}

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

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

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

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

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

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

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

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

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

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

@HttpError(errorCode=404)

public class ApplicationException extends Exception { ... }

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

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

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

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

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

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

public class SystemException extends RuntimeException { ... }

関心のあるすべての例外クラスへアノテーションを付加することは不可能なので、Seamはこの機能を pages.xml でも指定できるようにしています。


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

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

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

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


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

<pages>

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

</pages
>

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

もしJPAを使っている場合


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

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

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


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

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


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

そして、JSFの場合


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

ViewExpiredException はセッションの有効期限が切れたときにポストバックをページに送信しようとすると発生します。もし対話の中で操作中であればno-conversation-view-idconversation-required を使えば細かなセッションの有効期限の制御が可能になります。

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

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

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

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

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

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

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

Seam は透過的に対話コンテキスト (一時的な対話コンテキストを含む) を JSF ポストパックおよびリダイレクト全体に伝播します。 特に何もしなければ 非 faces 要求 (たとえば GET 要求など) は対話コンテキストを伝播せず新しい一時的な対話内で処理されます。 常にとは限りませんが、 これが通常求められる動作になります。

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


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

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


<h:outputLink value="main.jsf">
    <f:param name="#{manager.conversationIdParameter}" 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
>

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

ネストされた対話は既存の対話のスコープ内で @Begin(nested=true) とマークされたメソッドを呼び出すことによって作成されます。 ネストされた対話はそれ自体の対話コンテキストを持っていますが、 外側の対話のコンテキストから値を読み取ることができます。 オブジェクトは参照により取得されるため外側の対話のコンテキストはネストされた対話内では読み取り専用となりますが、 オブジェクト自体への変更はその外側のコンテキストに反映されます。

次に @End が出てくると、 対話スタックから「ポップ(Pop)」されることで、 その取り出された対話は破棄されます。 対話は任意の深さにネストすることができます。

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

対話スタックの最下位にある対話がルートの対話になります。 この対話を破棄すると常に派生した対話はすべて破棄されます。 @End(root=true) を指定すると宣言的にこれを行うことができます。

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

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

ページが 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 つのオプションから選択できます。

JSF コマンドリンクは常に JavaScript でフォームサブミットを行います。 これによりウェブブラウザの「新しいウィンドウで開く」または「新しいタブで開く」機能を動作させなくしてしまいます。 プレーンの JFS でこの機能が必要な場合は、 <h:outputLink> を使用する必要があります。 ただし、 <h:outputLink> には重要な制限が 2 つあります。

Seam provides the notion of a page action to help solve the first problem, but this does nothing to help us with the second problem. We could work around this by using the RESTful approach of passing a request parameter and requerying for the selected object on the server side. In some cases—such as the Seam blog example application—this is indeed the best approach. The RESTful style supports bookmarking, since it does not require server-side state. In other cases, where we don't care about bookmarks, the use of @DataModel and @DataModelSelection is just so convenient and transparent!

この機能を補ってさらに対話伝播の管理をより簡略化するために、 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"/>

動作に対して成功したか失敗したかをユーザーに示すメッセージを表示するのは非常に一般的です。 これには、 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"/>

永続オブジェクトを処理する対話を作業する場合は標準のサロゲート対話 ID ではなくそのオブジェクトのナチュラルキーを使用する方が望ましい場合があります。

既存の対話に容易にリダイレクト

ユーザーが同じ操作を 2 回要求する場合は既存の対話にリダイレクトすると便利な場合があります。 例をあげてみます。 「 「ebay で両親へのクリスマスプレゼントを獲得しそのアイテムの支払いプロセスが途中まで進んでいます。 ここでこのアイテムが直接両親に配達されるようにしようと思います。 支払い詳細は入力しましたが両親の明確な住所を思い出すことができません。 住所を検索しようとして誤って同じブラウザウィンドウを使ってしまいました。 もう一度さっきのアイテムの支払いに戻る必要があります。」

With a natural conversation its really easy to have the user rejoin the existing conversation, and pick up where they left off - just have them to rejoin the payForItem conversation with the itemId as the conversation id.

ユーザーフレンドリーな URL

個人的には操作可能な階層と (URL を編集しながら操作可能) 意味のある URL (この Wiki に見られるようにランダムな ID での識別を行わない) で構成されているように見えますが、 アプリケーションユーザーにとってはフレンドリーな URL はそれほど重要ではない場合もあるでしょう。

With a natural conversations, when you are building your hotel booking system (or, of course, whatever your app is) you can generate a URL like http://seam-hotels/book.seam?hotel=BestWesternAntwerpen (of course, whatever parameter hotel maps to on your domain model must be unique) and with URLRewrite easily transform this to http://seam-hotels/book/BestWesternAntwerpen.

かなり改善されたはずです。

ナチュラル対話は pages.xml で定義されます。


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

上記の定義でまず判るのは対話に名前があることです。 この場合 PlaceBid になります。 名前は特定の指定対話を固有に識別するため page 定義により参加する指定対話の識別に使用されます。

次の属性 parameter-name はデフォルトの対話 ID パラメータの代わりにナチュラル対話の ID を含む要求パラメータを定義します。 この例では parameter-nameauctionId になります。 つまり、 ページの URL 内に出現する cid=123 のような対話パラメータの代わりに auctionId=765432 を含むようになります。

上記の設定の最後の属性 parameter-value は対話 ID として使用するナチュラルビジネスキーの値の評価に使用される EL 式を定義します。 この例では対話 ID は現在範囲内の auction のプライマリーキー値になります。

次に指定対話に参加するページを定義します。 page 定義の conversation 属性を指定してこれを行います。


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

ナチュラル対話を開始またはリダイレクトする場合、 ナチュラル対話名を指定するいくつかのオプションがあります。 次のページ定義をまず見てみることにします。


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

auction ビューからアクション #{bidAction.placeBid} を呼び出していること (これらのサンプルはすべて Seam にある seamBay から取得している)、 前述のようにナチュラル対話 PlaceBid で設定される /bid.xhtml にリダイレクトしていることがわかります。 アクションメソッドの宣言は以下のようになります。

   @Begin(join = true)

   public void placeBid()

名前が付けられた対話が <page/> エレメントに指定される場合、 その名前の対話へのリダイレクトはアクションメソッドが完全に呼び出された後でナビゲーションルールの一部として発生します。 既存の対話にリダイレクトする場合はアクションメソッドが呼び出される前にリダイレクトが発生する必要があるため問題となってきます。 したがってアクションが呼び出される場合は対話名を指定する必要があります。 s:conversationName タグを使って行うのがその方法のひとつとなります。


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

もうひとつの方法は s:link または s:button のどちらかを使用するときに conversationName を指定する方法です。


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

ワークスペース管理は、1 つのウィンドウの中で対話を「切り換える」能力です。 Seam はワークスペース管理を Java コードのレベルで完全に透過的にします。 ワークスペース管理を可能にするために、必要なすべては以下の通りです。

対話一覧は対話切り替えに非常によく似ていますが、 表形式で表示される点が異なります。


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

恐らく、 多くの方が独自のアプリケーションに合うようカスタマイズを希望するだろうと思います。

詳細を持つ対話のみがこの一覧に含まれます。

対話一覧によりユーザーがワークスペースを破壊することができるので留意してください。

重要ではありませんが、 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;
    
    ...
}

また、 対話スコープのコンポーネントを JSF コントロールのバインド先となるイベントスコープのコンポーネントにはインジェクトできません。 これには facesMessages のようなコンポーネント内での Seam 構築が含まれます。

Alternatively, you can access the JSF component tree through the implicit uiComponent handle. The following example accesses getRowIndex()of the UIData component which backs the data table during iteration, it prints the current row number:



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

JSF UI のコンポーネントはこのマップ内のクライアント識別子で使用可能になります。

Seam コンポーネントへの並列コールに関する全般は 項4.1.10. 「同時並行処理モデル」 でご覧ください。 ここでは並列が発生する最も一般的な状況について説明しています (AJAX 要求から対話的コンポーネントにアクセスする)。 クライアントで発生したイベントの制御に Ajax クライアントライブラリが提供すべきオプションについて説明してから RichFaces で提供されるオプションについて見ていきます。

対話的コンポーネントでは実際の並列アクセスは許可されないので、 Seam は要求を連続的に処理するようそれぞれを待ち行列に入れます。 これにより各要求は決定論的に実行されるようになります。 ただし、 シンプルなキューはあまりよくありません。 まず、 メソッドがなんらかの理由で完了に長時間かかっている場合、 クライアントが要求を生成するたび繰り返してそれを実行するのはよくありません (サービス妨害攻撃の可能性)。 また、 ユーザーに対するステータスのクィックアップデートに AJAX が頻繁に使用されるためアクションを長時間実行し続けるのは実用的ではありません。

したがって、 長時間実行の対話の内側で作業する場合は Seam は一定期間アクションイベントを待ち行列に入れます (並列要求タイムアウト)。 時間通りにイベントを処理できないと一時的な対話を作成してユーザーに状況を知らせるメッセージを出力します。 このため、 AJAX イベントでサーバーを溢れさせないようにすることが非常に重要となります。

components.xml で並列要求のタイムアウトにミリ秒単位で適当なデフォルトを設定することができます。


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

また、 ページごとに並列要求のタイムアウトを微調整することもできます。


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

ここまではユーザーに対して連続的に出現する AJAX 要求について説明してきました。 クライアントはサーバーにイベントが発生したことを伝え、 その結果に応じてページの一部を表示します。 この方法は AJAX が軽量である場合には最適となります (1 コラム内の数字の合計を計算するなど呼び出されるメソッドがシンプルである場合)。 しかし、 計算に 1 分を要するような複雑な計算が必要な場合はどうでしょう。

重量のある計算にはポーリングベースの方法を使用してください。 クライアントがサーバーに AJAX 要求を送信し、 これによりサーバーで非同期にアクションが実行され (クライアントへの応答が即時となる)、 クライアントは次にサーバーに更新をポーリングします。 それぞれのアクションがすべて実行する (いずれもタイムアウトしない) ことが重要となるような長時間実行のアクションがある場合に適した方法となります。

まず、 簡単な「連続」要求を使用するのかどうか、 あるいはポーリングの方法を使用するのかどうかを決める必要があります。

「連続」要求を選択する場合は要求が完了する長さを推測する必要があります。 並列要求のタイムアウトに比べ十分に短い時間で完了するかどうか推測してみます。 不十分な場合はこのページの並列要求のタイムアウトを変更します (前述の通り)。 サーバーを要求で溢れさせないようにするためクライアント側で行列待ちをさせた方がよいでしょう。 イベントが頻繁に発生し (入力フィールドの keypress や onblur など) クライアントの即時更新が優先事項ではない場合はクライアント側で要求の遅延をセットしてください。 要求遅延の作業を行う場合、 イベントがサーバー側でも行列待ちできるようにすることを考慮に入れてください。

最後に、 クライアントライブラリは未完了の重複要求を最新のものを残してすべて停止するオプションを提供することができます。

ポールスタイルのデザインを使用する場合は細かな調整があまり必要ありません。 アクションメソッド @Asynchronous をマークしてポーリングの間隔を決定するだけです。

int total;


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

並列要求を対話的コンポーネントに対して行列待ちに入れるよう十分注意をしてアプリケーションを設定しても、 サーバー側がオーバーロードとなり要求処理にかかる時間が concurrent-request-timeout を越えるまでに全要求を処理できなくなる危険性があります。 こうした場合、 Seam は pages.xml で処理が可能な ConcurrentRequestTimeoutException を送出します。 HTTP 503 エラーの送信をお勧めします。


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

代わりにエラーページにリダイレクトすることができます。


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

ICEfaces, RichFaces Ajax and Seam Remoting can all handle HTTP error codes. Seam Remoting will pop up a dialog box showing the HTTP error and ICEfaces will indicate the error in it's connection status component. RichFaces Ajax provides the most complete support for handling HTTP errors by providing a user definable callback. For example, to show the error message to the user:

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

RichFaces Ajax は Seam で使用される最も一般的な AJAX ライブラリであり、 上述したような制御をすべて提供します。

JBoss jBPM はJava SE や EE 環境のためのビジネスプロセス管理エンジンです。 jBPM はビジネスプロセスやユーザーインタラクションを、 待ち状態、デシジョン、タスク、WEBページなどノードの図式として表現を可能にします。 図式は簡単でとても読みやすい jPDL と呼ばれる XML 表現を使用して定義されており、 Eclipse プラグインを利用して編集、グラフィックによる視覚化が可能です。 jPDL は拡張可能な言語でありWEB アプリケーションのページフローを定義することから、典型的なワークフローの管理、SOA 環境におけるサービスのオーケストレーションまで対応します。

Seam アプリケーションは jBPM を2 つの異なる問題に使用します。

  • 複雑なユーザーインタラクションを含むページフローを定義します。 jPDL プロセス定義は対話のためのページフローを定義します。 Seamの対話はシングルユーザーとの相対的に短期な対話のインタラクションであると考えられます。

  • ビジネスプロセスを包括的に定義します。 ビジネスプロセスは、複数ユーザーの複数の対話の範囲を含むかもしれません。 その状態は jBPM データベースの中で永続的なので長期的であると考えられます。 複数ユーザーのアクティビティの調整は、 シングルユーザーとのインタラクションについて動作を記述するよりずっと複雑な問題です。 そこで、jBPM は複数の並行な実行パスを扱うようなタスク管理のための洗練された機能を提供します。

この二つを混乱させないでください!まったく異なるレベルまたは粒度で動作します。Pageflow, conversationtask すべて、一人のユーザーの一つのインタラクションを指しています。ビジネスプロセスはたくさんのタスクに広がります。さらに二つのjBPMアプリケーションは完全に直行しています。それらを一緒にも利用できますし、個別にも利用できます。まったく使わないということもできます。

Seamを使うためにJPDLのことは知らなくていいです。もしページフローの定義やSeamナビゲーションルールの定義に何の問題もなく、アプリケーションがプロセスドリブンよりかはデータドリブンである場合には、おそらくjBPMは必要ないでしょう。しかし、私たちはユーザー間のインタラクションを、よくまとまったグラフィカルな図で考えることは、より堅牢なアプリケーションを構築しやすくしてくれます。

Seam にはページフローを定義する 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>

</page
>

ナビゲーション規則が冗長過ぎると考えるならば、 アクションリスナーメソッドから直接、ビュー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
>

ここですぐに気づく 二つのことがあります。

それに加えてステートフルモデルはもっと 制約的 です。 各論理的な状態 (ページフローの各ステップ) に対して他の状態に遷移可能な制約された組み合わせがあります。 ステートレスモデルはアドホックな モデルです。 それはアプリケーションではなく、 比較的制約のない、ユーザーが次に行きたいところを決めるフリーフォームナビゲーションに適しています。

ステートフル / ステートレスナビゲーションの違いは、 典型的なモーダル / モーダレスインタラクションの考え方ととてもよく似ています。 さて、アプリケーションをモーダルな振る舞いから回避することは、 対話を持つ 1 つの主な理由ですが、 Seam アプリケーションは、 通常、単純な意味でのモーダルではありません。 しかし、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 messageを表示します)。これを機能とみるかステートフルモデルの制約とみるかは視点によります:アプリケーション開発者の視点からは機能とみて、ユーザーからの視点としてはフラストレーションを引き起こすようなものかもしれません。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
>

実際には両ナビゲーションモデルとも使い道がありますが、どんなときにどちらのモデルの方が適切かを理解するために、これから簡単に学んでいきます。