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 は、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)」コンテナに期待されるすべての機能を提供します。
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 は、正しいマルチウィンドウの操作のみを提供するのではなく、一つのウィンドウ内でのマルチウィンドウ風の操作も提供するのです
伝統的に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 フレームワークは、あまりにも小さく考え過ぎます。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 に貢献して頂ける方は SeamFramework.org をご覧ください。
Seam は Seam のさまざまな機能の利用方法を実演する多くのサンプルアプリケーションを提供しています。 このチュートリアルはこれらのサンプルを通してあなたが Seam を学び始めるための案内をします。Seam サンプルは Seam ディストリビューションの examples サブディレクトリに置かれています。 初めて見るユーザー登録サンプルは、examples/registration ディレクトリにあります。
各サンプルは同じディレクトリの構造をしています。
The view ディレクトリにはWebページテンプレート、イメージ、スタイルシートなどビュー関連のファイルが入っています。
resources ディレクトリにはデプロイメント記述子やその他の構成ファイルが入っています。
src ディレクトリにはアプリケーションソースコードが入っています。
サンプルアプリケーションは追加設定することなく JBoss AS と Tomcat で動作します。 これからの章では両方のケースの手順を説明します。 Ant build.xml によりビルドと起動を行いますから、始める前に最新の Ant をインストールする必要があることに留意してください。
The examples are configured for use on JBoss 4.2. You'll need to set jboss.home, in the shared build.properties file in the root folder of your Seam installation, to the location of your JBoss AS installation .
Once you've done that and started the application server, you can build and deploy any example by typing ant deploy in the the directory for that example. The examples all deploy to a URL like /seam-. For the registration example, the URL would be examplehttp://localhost:8080/seam-registration/.
サンプルは 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-, so for the registration example the URL would be examplehttp://localhost:8080/jboss-seam-registration/.
ユーザー登録サンプルはデータベースに新規ユーザーのユーザー名、 実名、 パスワードをデータベースに保存できる簡単なアプリケーションです。 このサンプルでは Seam の高度な機能のすべてを見せることはできませんが、 JSF アクションリスナーとして EJB3 セッション Bean を使用する方法や、 基本的な Seam の設定方法を見せてくれます。
EJB 3.0 にまだ不慣れな方もいらっしゃるかもしれませんので、 ゆっくり進めていきます。
最初のページは三つの入力フィールドを持つ基本的なフォームを表示します。 試しに、項目を入力してフォームをサブミットしてください。 これでユーザーオブジェクトはデータベースに保存されます。

このサンプルは、二つの Facelets テンプレート、一つのエンティティ Bean と、一つのステートレスセッション Bean で実装されています。 "bottom" からコードを見てみましょう。
ユーザーデータのために、EJBエンティティ Beanが必要です。 このクラスでは、アノテーションによって 永続性 と データ妥当性検証 を宣言的に定義しています。 Seam コンポーネントとしてのクラスを定義するために、別にいくつかのアノテーションも必要です。
例 1.1. User.java
@Entity
@Name("user")
@Scope(SESSION)
@Table(name="users")
public class User implements Serializable
{
private static final long serialVersionUID = 1881413500711441951L;
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;
}
public User() {}
@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;
}
@Id @NotNull @Length(min=5, max=15)
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
![]() | EJB3 標準 |
![]() | Seam コンポーネントは |
![]() | Seam がコンポーネントをインスタンス化する時には、 必ずコンポーネントの デフォルトコンテキストにあるコンテキスト変数に新しいインスタンスをバインドします。 デフォルトコンテキストは |
![]() | EJB 標準 |
![]() |
|
![]() | 空コンストラクタは、EJB と Seam の両方の仕様から必要となります。 |
![]() |
|
![]() | EJB 標準 |
このサンプルで、もっとも注目してほしい重要なものは @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@Name("register") public class RegisterAction implements Register { @In
private User user; @Persistenc
eContext private EntityManager em; @Logger
private Log log; public Stri
ng register() { List existing = em.createQuery( "sele
ct username from User where username=#{user.username}") .getResultList(); if (existing.size()==0) { em.persist(user); log.i
nfo("Registered new user #{user.username}"); retur
n "/registered.xhtml"; } else { Faces
Messages.instance().add("User #{user.username} already exists"); return null; } } }
![]() | EJB |
![]() | |
![]() | EJB 標準 |
![]() | Seam |
![]() | アクションリスナーメソッドは、データベースとやり取りするために、 標準 EJB3 |
![]() | Seam では EJB-QL 内で JSF EL 式を使用することができます。 バックグラウンドで行われるため見えませんが、 これにより普通の JPA |
![]() | The |
![]() | JSF アクションリスナーメソッドは、次にどのページを表示するかを決定するストリング値の結果 (outcome) を返します。 null 結果 (outcome) (あるいは、void アクションリスナーメソッド) は、 前のページを再表示します。 普通の JSF では、 結果 (outcome) から JSF ビュー id を決定するために、 常に JSF ナビゲーション規則 を使用することが普通です。 複雑なアプリケーションにとって、この間接的方法は、実用的な良い慣行です。 しかし、このようなとても簡単なサンプルのために、 Seam は、結果 (outcome) として JSF ビュー id の使用を可能とし、 ナビゲーション規則の必要性を取り除きました。 結果 (outcome) としてビュー id を使用する場合、 Seam は、常にブラウザリダイレクトを行うことに留意してください。 |
![]() | Seam provides a number of built-in components to help solve common problems. The |
ここで、@Scope を明示的に指定していないことに留意してください。 各 Seam コンポーネントタイプは明示的にスコープが指定されない場合デフォルトのスコープが適用されます。 ステートレスセッション Bean のデフォルトスコープはステートレスコンテキストです。 これは単に理にかなった値です。
このセッション Bean のアクションリスナーは、この小さなアプリケーションのビジネスロジックと永続ロジックを提供しています。 さらに複雑なアプリケーションでは、個別のサービスレイヤが必要かもしれません。 Seamで、これをするのは簡単ですが、 ほとんどの Web アプリケーションでは過剰です。 Seam は、アプリケーションのレイヤ化のために特殊な方法を強要しているのではなく、アプリケーションをより簡単に、また望むならばより複雑にすることを可能にしています。
このアプリケーションについて、私たちはこれまで実際に必要とされるよりはるかに複雑にしてきました。 Seam アプリケーションコントローラを利用していたならば、アプリケーションコードのほとんどを排除できたかもしれない。しかしながら、 当時、説明する多くのアプリケーションがありませんでした。
当然、セッション Bean には、ローカルインタフェースが必要です。
Java コードは以上です。 次は ビューを見ましょう。
Seam アプリケーションのビューページは、 JSF をサポートする多くの技術を使用して実装されています。 このサンプルでは、JSP より優れていると考えている Facelets を使用しています。
例 1.4. register.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title
>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Username: <h:inputText value="#{user.username}" required="true"/>
Real Name: <h:inputText value="#{user.name}" required="true"/>
Password: <h:inputSecret value="#{user.password}" required="true"/>
</h:panelGrid>
</s:validateAll>
<h:messages/>
<h:commandButton value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html
>
ここで Seam 固有となるのは <s:validateAll> タグのみです。 この JSF コンポーネントは 含まれるすべての入力フィールドをエンティティ Bean で指定される Hibernate Validator アノテーションに対して検証するよう JSF に指示しています。
例 1.5. registered.xhtml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title
>Successfully Registered New User</title>
</head>
<body>
<f:view>
Welcome, #{user.name}, you are successfully registered as #{user.username}.
</f:view>
</body>
</html>
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 ファイルを使用します。
例 1.6. components.xml
<?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">
<core:init jndi-pattern="@jndiPattern@"/>
</components
>
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 デプロイメント記述子が必要です。
例 1.7. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Seam -->
<listener>
<listener-class
>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<!-- JSF -->
<listener>
<listener-class
>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
<context-param>
<param-name
>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value
>.xhtml</param-value>
</context-param>
<servlet>
<servlet-name
>Faces Servlet</servlet-name>
<servlet-class
>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup
>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name
>Faces Servlet</servlet-name>
<url-pattern
>*.seam</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout
>10</session-timeout>
</session-config>
</web-app
>
この web.xml ファイルは Seam と JSF を設定します。 ここで見る設定は Seam アプリケーションではいつも同じです。
ほとんどの Seam アプリケーションはプレゼンテーション層として JSF ビューを使用します。 従って通常 faces-config.xml が必要です。 この場合ビュー定義に Facelets を使用しますので、JSF にテンプレートエンジンとして Faceles を使用することを指定する必要があります。
例 1.8. faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<!-- Facelets support -->
<application>
<view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config
>
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 をアクションコードに埋め込んだため、 ナビゲーション規則さえ不要です。
ejb-jar.xml ファイルは、 アーカイブ中のすべてのセッション Bean に SeamInterceptor を付加することによって EJB3 と統合します。
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0">
<interceptors>
<interceptor>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name
>*</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar
>
persistence.xml ファイルは、EJB 永続プロバイダに、 データソースの場所を指示します。また、ベンダー固有の設定を含んでいます。 このサンプルでは起動時に自動スキーマエキスポートを可能としています。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="userDatabase">
<provider
>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source
>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence
>
最後に、EARとして アプリケーションがデプロイされるため、デプロイメント記述子も必要になります。
例 1.9. ユーザー登録アプリケーション
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/application_5.xsd"
version="5">
<display-name
>Seam Registration</display-name>
<module>
<web>
<web-uri
>jboss-seam-registration.war</web-uri>
<context-root
>/seam-registration</context-root>
</web>
</module>
<module>
<ejb
>jboss-seam-registration.jar</ejb>
</module>
<module>
<ejb
>jboss-seam.jar</ejb>
</module>
<module>
<java
>jboss-el.jar</java>
</module>
</application
>
このデプロイメント記述子はエンタープライズアーカイブのモジュールとリンクし、 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 から構成されています。
Message エンティティ Bean は、 タイトル、テキスト、掲示メッセージの日付と時間、 そして、メッセージが既読か否かを示すフラグを定義しています。
例 1.10. Message.java
@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
private Long id;
private String title;
private String text;
private boolean read;
private Date datetime;
@Id @GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@NotNull @Length(max=100)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@NotNull @Lob
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@NotNull
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
@NotNull
@Basic @Temporal(TemporalType.TIMESTAMP)
public Date getDatetime() {
return datetime;
}
public void setDatetime(Date datetime) {
this.datetime = datetime;
}
}
前述のサンプル同様、 一つのセッション 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
private List<Message
> messageList;
@DataModelS
election
@Out(required=false)
private Message message;
@PersistenceContext(type=EXTENDED)
private EntityManager em;
@Factory("messageList")
public void findMessages()
{
messageList = em.createQuery("select msg from Message msg order by msg.datetime desc")
.getResultList();
}
public void select()
{
message.setRead(true);
}
public void delete()
{
messageList.remove(message);
em.remove(message);
message=null;
}
@Remove
public void destroy() {}
}![]() |
|
![]() |
|
![]() | The |
![]() | このステートフル Bean は、EJB3 拡張永続コンテキスト を持っています。 この Bean が存在する限り、 クエリー検索された messages は、管理された状態に保持されます。 従って、 それに続くステートフル Bean へのメソッド呼び出しは、 明示的に |
![]() | 初めて JSP ページに画面遷移するとき、 |
![]() |
|
![]() |
|
![]() | すべてのステートフルセッション Bean の Seam コンポーネントは、 |
これがセッションスコープの Seam コンポーネントであることに留意してください。 ユーザーログインセッションと関連しログインセッションからのすべての要求は、 同じコンポーネントのインスタンスを共有します。 (Seam アプリケーションでは、セッションスコープのコンポーネントは控えめに使用してください。)
もちろん、すべてのセッション Bean はインタフェースを持ちます。
例 1.12. MessageManager.java
@Local
public interface MessageManager
{
public void findMessages();
public void select();
public void delete();
public void destroy();
}
ここからは、サンプルコード中のローカルインタフェースの記述を省略します。
components.xml、persistence.xml、web.xml、ejb-jar.xml、faces-config.xml そして application.xml は前述までのサンプルとほぼ同じなので、スキップして JSP に進みましょう。
この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 は、選択 Message を message という名前のコンテキスト変数にアウトジェクトします。 次に、 EJB コンテナはトランザクションをコミットし、 Message に対する変更がデータベースにフラッシュされます。 最後に、 このページが再度レンダリングされてメッセージ一覧を再表示、 その下に選択メッセージが表示されます。
ユーザーが <h:commandButton> をクリックすると、 JSF は、delete() アクションリスナーを呼び出します。 Seam はこの呼び出しをインタセプトし、 選択された行データを messageList コンポーネントの message 属性にインジェクトします。 アクションリスナーが起動し、 選択 Message が一覧から削除され、 EntityManager の remove() が呼び出されます。 呼び出しの終わりに、 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-state name="start"> <transition to="todo"/> </start-state> <task-node
name="todo"> <task na
me="todo" description="#{todoList.description}"> <assi
gnment actor-id="#{actor.id}"/> </task> <transition to="done"/> </task-node> <end-state
name="done"/> </process-definition >
![]() |
|
![]() |
|
![]() |
|
![]() | タスクを生成するとき、タスクはユーザーあるいはユーザーグループに割り当てる必要があります。 このサンプルでは、タスクは、現在のユーザーに割り当てられています。 現在のユーザーは |
![]() |
|
JBossIDE に提供されたプロセス定義エディタを使用してプロセス定義を見た場合、 以下のようになります。

このドキュメントは、ノードのグラフとして ビジネスプロセス を定義します。 これは現実にあり得る最小のビジネスプロセスです。実行されなければならない タスク は、一つだけです。 タスクが完了したとき ビジネスプロセスは終了します。
最初の JavaBean はログイン画面 login.jsp を処理します。 処理は単に actor コンポーネントを使用して jBPM actor id を初期化するだけです。 実際のアプリケーションではユーザー認証も必要です。
例 1.15. Login.java
@Name("login")
public class Login {
@In
private Actor actor;
private String user;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String login()
{
actor.setId(user);
return "/todo.jsp";
}
}
ここでは、組み込み Actor コンポーネントをインジェクトするために、 @In を使用しているのがわかります。
次の JSP 自体は重要ではありません。
例 1.16. login.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title
>Login</title>
</head>
<body>
<h1
>Login</h1>
<f:view>
<h:form>
<div>
<h:inputText value="#{login.user}"/>
<h:commandButton value="Login" action="#{login.login}"/>
</div>
</h:form>
</f:view>
</body>
</html
>
二つめの JavaBean は、ビジネスプロセスインスタンスの開始とタスクの終了を担当します。
例 1.17. TodoList.java
@Name("todoList")
public class TodoList {
private String description;
public Stri
ng getDescription()
{
return description;
}
public void setDescription(String description) {
this.description = description;
}
@CreateProc
ess(definition="todo")
public void createTodo() {}
@StartTask
@EndTask
public void done() {}
}![]() | The description property accepts user input form the JSP page, and exposes it to the process definition, allowing the task description to be set. |
![]() | Seam |
![]() | Seam |
より現実的なサンプルでは、 @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フォームの中に定義されています。
例 1.19. todo.jsp
<h:form id="list">
<div>
<h:outputText value="There are no todo items." rendered="#{empty taskInstanceList}"/>
<h:dataTable value="#{taskInstanceList}" var="task"
rendered="#{not empty taskInstanceList}">
...
</h:dataTable>
</div>
</h:form
>
一覧の各要素は jBPM クラス TaskInstance のインスタンスです。 以下のコードは単に、一覧中の各タスクの興味深いプロパティを表示しています。 記述内容 (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
name="displayGuess" view-id="/numberGuess.jspx">
<redirect/>
<transit
ion name="guess" to="evaluateGuess">
<acti
on expression="#{numberGuess.guess}"/>
</transition>
<transition name="giveup" to="giveup"/>
<transition name="cheat" to="cheat"/>
</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="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
>![]() |
|
![]() |
|
![]() | transition の |
![]() |
|
以下は 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 ページはごく普通のものです。
例 1.22. win.jspx
<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="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
>You won!</title>
<link href="niceforms.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1
>You won!</h1>
<f:view>
Yes, the answer was <h:outputText value="#{numberGuess.currentGuess}" />.
It took you <h:outputText value="#{numberGuess.guessCount}" /> guesses.
<h:outputText value="But you cheated, so it doesn't count!"
rendered="#{numberGuess.cheat}"/>
Would you like to <a href="numberGuess.seam"
>play again</a
>?
</f:view>
</body>
</html>
</jsp:root>
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
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;
}
}
![]() | 最初に、JSP ページが |
pages.xml ファイルは Seam 対話 (conversation) を開始し ( 詳細は後述 )、対話のページフローを使用するためのページフロー定義を規定します。
例 1.24. pages.xml
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
<page view-id="/numberGuess.jspx">
<begin-conversation join="true" pageflow="numberGuess"/>
</page>
</pages
>
見てわかるように、この 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} をテストします。 まだ数字あてをするならば ( lastGuess が false )、最初の 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@Name("hotelSearch") @Scope(ScopeType.SESSION) @Restrict("#{i
dentity.loggedIn}") public class HotelSearchingAction implements HotelSearching { @PersistenceContext private EntityManager em; private String searchString; private int pageSize = 10; private int page; @DataModel
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; }
@Remove public void destroy() {} }
![]() | EJB 標準 |
![]() |
|
![]() | |
![]() | EJB 標準の |
アプリケーションの中心となるページは 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}"
style="width: 165px;">
<a:support event="onkeyup" actionListener="#{hotelSearch.find}"
reRender="searchResults" />
</h:inputText>
 
<a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}"
reRender="searchResults"/>
 
<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
> 
<h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
</fieldset>
</h:form>
</div>
<a:outputPanel id="searchResults">
<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>
#{
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
> ![]() | RichFaces Ajax |
![]() | RichFaces Ajax |
![]() | RichFaces Ajax |
![]() | Seam どのようにナビゲーションが起こるかと思うならば、 |
このページは、タイプしたときに検索結果が動的に表示し、 ホテルの選択をさせ、 HotelBookingAction の selectHotel() メソッドに選択結果を渡します。 そこでは、かなり興味深いことが起こっています。
対話と関連する永続データを自然にキャッシュするために予約サンプルアプリケーションがどのように対話スコープのステートフル Bean を利用するか見てみましょう。 以下のサンプルコードは結構長いですが、対話の各種ステップを実装するスクリプト化された動作の一覧と考えると理解できます。 ストーリーを読むように徹底的に読んでください。
例 1.27. HotelBookingAction.java
@Stateful
@Name("hotelBooking")
@Restrict("#{identity.loggedIn}")
public class HotelBookingAction implements HotelBooking
{
@Persistenc
eContext(type=EXTENDED)
private EntityManager em;
@In
private User user;
@In(required=false) @Out
private Hotel hotel;
@In(required=false)
@Out(requir
ed=false)
private Booking booking;
@In
private FacesMessages facesMessages;
@In
private Events events;
@Logger
private Log log;
private boolean bookingValid;
@Begin
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
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
public void destroy() {}
![]() | この Bean は、EJB3 拡張永続コンテキスト を使用します。 その結果、エンティティインスタンスは、 ステートフルセッション Bean のライフサイクル全体の管理を維持します。 |
![]() | |
![]() | |
![]() | |
![]() | Seam は、対話コンテキストを破棄するときこの EJB remove メソッドは呼び出されるでしょう。 このメソッドを定義することを忘れないでください。 |
HotelBookingAction はホテル選択、予約、予約確認を実装したすべてのアクションリスナーを持っており、 そしてこの操作に関連する状態をインスタンスに保持しています。 このコードが、 HttpSession 属性から get/set するものと比較してよりクリーンで簡単なコードであることに同意してもらえると思います。
さらに良いことに、ユーザーは、ログインセッション毎に複数の分離された対話を持つことが可能です。 試してみてください。 ログインして、検索して、複数のブラウザタブに異なるホテルのページを表示させてください。 同時に二つの異なるホテル予約を作成することが可能です。 対話を長時間放置した場合、 Seam は最終的に対話をタイムアウトし状態を破棄します。 対話が終了した後に、その対話ページに戻るボタンを押し処理実行を試みた場合、 Seam は対話が既に終了したことを検出し検索ページにリダイレクトします。
WAR は seam-debug.jar も含みます。 Seam デバッグページは WEB-INF/lib に Facelets と合わせてこの jar がデプロイされ、init コンポーネントの debug プロパティが以下のように設定された場合に有効になります。
<core:init jndi-pattern="@jndiPattern@" debug="true"/>
このページで現在ログインしているセッションに関連するすべての Seam コンテキスト中の Seam コンポーネントを見たり検査することができます。 使い方は単にブラウザから http://localhost:8080/seam-booking/debug.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
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);
}
@Begin(nested=true)
public String selectPreference()
{
log.info("Room selected");
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
Redirect=true)
public String cancel()
{
log.info("ending conversation");
return "cancel";
}
@Destroy @Remove
public void destroy() {}
}
![]() | The |
![]() | |
![]() |
|
![]() | |
ネストされた対話にあるとき対話スタックにプッシュされます。 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}"/>
<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>
<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>
<h:commandLink id="selectRoomPreference"
action="#{roomPreference.selectPreference}"
>Select</h:commandLink>
</h:column>
</h:dataTable>
</div>
<div class="entry">
<div class="label"
> </div>
<div class="input">
<s:button id="cancel" value="Revise Dates" view="/book.xhtml"/>
</div>
</div
>
</h:form>
</div>
![]() | EL から要求されるとき、 |
![]() |
|
![]() | 日付の変更は単純に |
今や対話のネスティングの方法がわかったので、部屋が選ばれたらどのように予約を確認することができるかを見てみましょう。 これは 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
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);
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
rue, beforeRedirect=true)
public void cancel() {}
@Destroy @Remove
public void destroy() {}
}
![]() | Annotating an action with |
![]() |
|
![]() | |
気軽にアプリケーションをデプロイし、たくさんのウィンドウやタブを開きさまざまな好みの部屋によるさまざまなホテルの組み合わせを試してみて下さい。 予約確認はネストされた対話モデルのおかげで正しいホテルと好みの部屋をもたらします。
DVD ストアのデモアプリケーションは、 タスク管理とページフローのための jBPM の実践的な使用法を見せてくれます。
ユーザー画面は、検索やショッピングカート機能の実装のために jPDL ページフローを利用しています。

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

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

この Blog サンプルは、"引っぱり (PULL) " - スタイル MVC の使用を実演しています。 ここで、ビューのためのデータ取得とデータ準備のアクションメソッドリスナーを使用する代わりに、 ビューは、レンダリングしているコンポーネントからデータを引き出します (PULL) 。
index.xhtml facelets ページの一部は最新のブログエントリの一覧を表示しています。
例 1.31.
<h:dataTable value="#{blog.recentBlogEntries}" var="blogEntry" rows="3">
<h:column>
<div class="blogEntry">
<h3
>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.excerpt==null ? blogEntry.body : blogEntry.excerpt}"/>
</div>
<p>
<s:link view="/entry.xhtml" rendered="#{blogEntry.excerpt!=null}" propagation="none"
value="Read more...">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
<p>
[Posted on 
<h:outputText value="#{blogEntry.date}">
<f:convertDateTime timeZone="#{blog.timeZone}" locale="#{blog.locale}" type="both"/>
</h:outputText
>]
 
<s:link view="/entry.xhtml" propagation="none" value="[Link]">
<f:param name="blogEntryId" value="#{blogEntry.id}"/>
</s:link>
</p>
</div>
</h:column>
</h:dataTable
>
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.
例 1.32.
@Name("blog")
@Scope(ScopeType.STATELESS)
@AutoCreate
public class BlogService
{
@In EntityM
anager entityManager;
@Unwrap
public Blog getBlog()
{
return (Blog) entityManager.createQuery("select distinct b from Blog b left join fetch b.blogEntries")
.setHint("org.hibernate.cacheable", true)
.getSingleResult();
}
}![]() | このコンポーネントは Seam 管理永続コンテキスト (seam-managed persistence context) を使用しています。 これまで見てきた他のサンプルとは異なり、この永続コンテキストは、EJB3 コンテナの代わりに Seam により管理されます。 永続コンテキストは Web 要求全体におよび、ビューにおいてフェッチしていない関連にアクセスするときに発生する例外を回避することが可能です。 |
![]() | The |
これは、これまでのところ良いですが、 検索結果ページのようなフォームサブミットの結果のブックマークではどうでしょうか?
この Blog サンプルは、 各ページの右上にユーザーの Blog 記事の検索を可能にする小さなフォームを持ちます。 これは、facelet テンプレート、template.xhtml に含まれる menu.xhtml ファイルに定義されます。
例 1.33.
<div id="search">
<h:form>
<h:inputText value="#{searchAction.searchPattern}"/>
<h:commandButton value="Search" action="/search.xhtml"/>
</h:form>
</div
>
ブックマーク可能検索結果ページの実装のために、 検索フォームのサブミットを処理した後に、 ブラウザリダイレクトを実行する必要があります。 アクション結果 (outcome) として JSF ビュー ID を使用しているので、 Seam は、フォームがサブミットされたとき、自動的に ビュー ID にリダイレクトします。 別の方法として、以下のようなナビゲーションルールを定義することも可能です。
<navigation-rule>
<navigation-case>
<from-outcome
>searchResults</from-outcome>
<to-view-id
>/search.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
フォームは、以下と似たようなものになるでしょう。
<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:
例 1.34.
<pages>
<page view-id="/search.xhtml">
<rewrite pattern="/search/{searchPattern}"/>
<rewrite pattern="/search"/>
<param name="searchPattern" value="#{searchService.searchPattern}"/>
</page>
...
</pages
>
検索ページへの要求があるときや検索ページへのリンクが生成されるときはいつでも、ページパラメータは 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 
<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.xml と import-test.sql ファイルが使用されます。 import-test.sql 中のデータベーススキーマとテストデータは常にテストが実行される前にデータベースにエキスポートされます。 myproject-dev-ds.xml、persistence-dev.xml と import-dev.sql はアプリケーションを開発データベースにデプロイするときに使用します。 seam-gen に既存データベースで作業しているかを伝えるかどうかによってスキーマはデプロイ時に自動的にエキスポートされる場合があります。 myproject-prod-ds.xml、 persistence-prod.xml と import-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 new-form
C:\Projects\jboss-seam>seam new-form
Buildfile: C:\Projects\jboss-seam\seam-gen\build.xml
validate-workspace:
validate-project:
action-input:
[input] Enter the Seam component name
hello
[input] Enter the local interface name [Hello]
[input] Enter the bean class name [HelloBean]
[input] Enter the action method name [hello]
[input] Enter the page name [hello]
setup-filters:
new-form:
[echo] Creating a new stateful session bean component with an action method
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[copy] Copying 1 file to C:\Projects\hello\view
[copy] Copying 1 file to C:\Projects\hello\src\hot\com\hello\test
[echo] Type 'seam restart' and go to http://localhost:8080/hello/hello.seam
BUILD SUCCESSFUL
Total time: 5 seconds
C:\Projects\jboss-seam>再びアプリケーションを再起動させ、 http://localhost:8080/helloworld/hello.seam に進みます。 生成されたコードを見てみましょう。 テストを実行します。 何か新しいフィールドをフォームと Seam コンポーネントに追加してみましょう。 (Java コードを変更したら常にデプロイを再起動することを忘れないようにしてください。)
手動でデータベースの中にテーブルを生成します。 (別のデータベースに切り替える必要がある場合はもう一度 seam setup を実行します。) ここで次のように入力します。
seam generate-entities
デプロイを再起動して、 http://localhost:8080/helloworld に進んでください。 データベースの参照、 既存オブジェクトの編集、 新しいオブジェクトの生成が可能です。 生成されたコードを見ると、おそらくあまりに簡単なのに驚かれたと思います。 Seam はデータアクセスコードが手作業で簡単に記述できるように設計されているからです。 また seam-gen を使用したずるをしたくない人でも大丈夫です。
既存の有効なエンティティクラスを src/main に置いて、以下をタイプしてください。
seam generate-ui
デプロイメントをリスタートして、http://localhost:8080/helloworld に進んでください。
最後に、 標準の Java EE 5 パッケージングを使用してアプリケーションをデプロイできるようにしたいと思います。 まず、 seam unexplode を実行して展開したディレクトリを削除する必要があります。 EAR をデプロイするには、 コマンドプロンプトで seam deploy を入力するか、 あるいは生成されたプロジェクトの build スクリプトの deploy ターゲットを実行します。 seam undeploy または undeploy ターゲットを使うとアンデプロイができます。
デフォルトでは、アプリケーションは dev profile でデプロイされます。 EAR は persistence-dev.xml ファイルと import-dev.sql ファイルを含み、 myproject-dev-ds.xml ファイルがデプロイされます。 プロファイルは変更可能で、 以下のように入力して prod profile を使用します。
seam -Dprofile=prod deploy
アプリケーション用に新しいデプロイメントプロファイルを定義することもできます。 プロジェクトに適切な名前が付いたファイルを追加します — 例えば、persistence-staging.xml、import-staging.sql と myproject-staging-ds.xml などです。 -Dprofile=staging を使ってプロファイルの名前を選択します。
展開形式のディレクトリで Seam アプリケーションをデプロイする場合、 開発時に増分ホットデプロイのサポートを受けるでしょう。 components.xml に以下の行を追加することで、 Seam と Facelets の両方でデバッグモードを有効にする必要があります。
<core:init debug="true"
>
以下のファイルは、 Webアプリケーションを完全に再起動することなく置き換えがされるかもしれません。
facelets ページ
pages.xml ファイル
ただし、 いずれかの Java コードを変更する必要がある場合は、 アプリケーションを完全に再起動する必要があります。 (JBoss ではトップレベルのデプロイメント記述子を更新することでこれを行えます。 EAR デプロイメントなら application.xml、 WAR デプロイメントなら web.xml です。)
しかし、 早いペースの編集/コンパイル/テストのサイクルを望むならば、 Seam は JavaBean コンポーネントの増分再デプロイメントをサポートしています。 この機能を有効にするためには、 JavaBean コンポーネントを WEB-INF/dev ディレクトリにデプロイする必要があります。 その結果、コンポーネントは WAR あるいは EAR クラスローダではなく、 特殊な Seam クラスローダによってロードされるようになります。
以下の制約を知っている必要があります。
コンポーネントは JavaBean コンポーネントでなければならず、 EJB3 Bean は不可です。 (この制約は修正中です。)
エンティティはホットデプロイされることはありません。
components.xml でデプロイされたコンポーネントはホットデプロイできない場合があります。
ホットデプロイ可能なコンポーネントは、 WEB-INF/dev の外部にデプロイされたクラスからは見えません。
Seam デバッグモードは有効で jboss-seam-debug.jar は WEB-INF/lib になければなりません。
web.xml に 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 4.0 は Seam 互換のデフォルト設定で出荷されていません。 Seam を実行するために EJB3 プロファイルを選択して JEMS 1.2 インストーラ を使用してインストールしなければなりません。 Seam は EJB3 サポートを含まないインストールでは動作しません。 JEMS インストーラは http://labs.jboss.com/jemsinstaller/downloads からダウンロード可能です。
JBoss 4.0 の Web 設定は server/default/deploy/jbossweb-tomcat55.sar にあります。 jsf-libs ディレクトリから myfaces-api.jar と myfaces-impl.jar を削除する必要があります。 そして、ディレクトリに jsf-api.jar、jsf-impl.jar、el-api.jar と el-ri.jar をコピーする必要があります。 JSF の JAR は Seam lib ディレクトリにあります。 el の JAR は Seam 1.2 リリースから取得可能です。
さらに conf/web.xml を編集する必要があります。 myfaces-impl.jar を jsf-impl.jar に交換してください。
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 の記述と似ています。
開始前に JDK 5、JBoss AS 4.2、Eclipse 3.3、the JBoss Tools プラグイン ( 最低限、Seam Tools、Visual Page Editor、jBPM Tools と JBoss AS Tools) そして TestNG プラグイン for Eclipse がきちんとインストールされていることを確認してください。
TODO - アップデートサイトがどこであるかの詳細
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% は同じものです。
従来のアクションスタイルの Web フレームワークに慣れている場合、 おそらくどのように Java のステートレスアクションメソッドを持つ簡単な Web ページが生成されるのだろうかと思われるでしょう。
最初に、New -> Seam Action と選択してください。

ここでは、Seam コンポーネント名を登録してください。 JBoss Tools は他のフィールドのために気の利いたデフォルトを選択します。

最後に Finish を押します。
さあ、http://localhost:8080/helloworld/ping.seam に進んで、クリックボタンを押してください。 プロジェクトの src directory ディレクトリを見れば、このアクションに隠されたコードを見ることができます。 ping() メソッドにブレークポイントを置いて、 クリックボタンを押してください。
最後に、helloworld-test プロジェクトを開いて、PingTest クラスを指定して、それを右クリックして、Run As -> TestNG Test と選択してください。

最初のステップはフォームを生成することです。 New -> Seam Form と選択してください。

ここでは、Seam コンポーネント名を登録してください。 JBoss Tools は他のフィールドのために気の利いたデフォルトを選択します。

http://localhost:8080/helloworld/hello.seam と進んでください。 そして生成されたコードを見てください。 テストを起動してください。 フォームと Seam コンポーネントに新しいフィールドを追加してみてください。(Seam がコンポーネントをホットリロード 項3.6. 「Seam と JBoss Tools を使用した増分ホットデプロイメント」 するので、 src/hot のコードを変更するたびにアプリケーションサーバーをリスタートする必要がないことに留意してください。)
手動でデータベースの中にテーブルを生成します。 (別のデータベースに切り替える必要がある場合、新たなプロジェクトを作成して、正しいデータベースを選択してください。) そして、New -> Seam Generate Entities と選択します。

JBoss Tools には、データベーススキーマからエンティティ、コンポーネント、ビューのリバースエンジニアリングをするか、あるいは既存 JPA エンティティからコンポーネントとビューのリバースエンジニアリングをするかのオプションがあります。 ここではデータベースからのリバースエンジニアリングを行います。
デプロイメントの再起動:

http://localhost:8080/helloworld にすすんでください。 データベースをブラウズ、既存オブジェクトの編集、そして新規オブジェクトの作成が可能です。 生成されたコードを見ると、何てシンプルなんだと驚かれるはずです! Seam はマニュアルで簡単にデータアクセスコードが書けるよう設計されています。 リバースエンジニアリングを使用したカンニングをしたくない人にも簡単です。
JBoss Tools は以下について増分ホットデプロイメントをサポートします。
facelets ページ
pages.xml ファイル
out of the box.
ただし Java コードを変更したいのならば、Full Publish することでアプリケーションを再起動する必要があります。
しかし、 早いペースの編集/コンパイル/テストのサイクルを望むならば、 Seam は JavaBean コンポーネントの増分再デプロイメントをサポートしています。 この機能を有効にするためには、 JavaBean コンポーネントを WEB-INF/dev ディレクトリにデプロイする必要があります。 その結果、コンポーネントは WAR あるいは EAR クラスローダではなく、 特殊な Seam クラスローダによってロードされるようになります。
以下の制約を知っている必要があります。
コンポーネントは JavaBean コンポーネントでなければならず、 EJB3 Bean は不可です。 (この制約は修正中です。)
エンティティはホットデプロイできません。
components.xml でデプロイされたコンポーネントはホットデプロイできない場合があります。
ホットデプロイ可能なコンポーネントは、 WEB-INF/dev の外部にデプロイされたクラスからは見えません。
Seam デバッグモードは有効で jboss-seam-debug.jar は WEB-INF/lib になければなりません。
web.xml に Seam フィルタをインストールしなければなりません。
ロードとデバッグが有効な環境のシステムであればエラーが発生するかもしれません。
JBoss Tools を使用して WAR プロジェクトを生成する場合、 増分ホットデプロイメントは、src/hot ソースディレクトリにあるクラスはそのまま使用可能です。 しかし、 JBoss Tools は EAR プロジェクトに対する増分ホットデプロイに対応していません。
Seam における 2 つの中心的概念は、 コンテキスト の概念と コンポーネント の概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。
Seam に組み込まれたコンテキストから説明を始めましょう。
Seam コンテキストはフレームワークによって生成、破棄されます。 アプリケーションは Java API 呼び出しによってコンテキストの区分 (demarcation) を明示的に制御することはできません。 コンテキストは通常、暗黙的ですが、場合によってコンテキストはアノテーションによって区分されます。
基本の Seam コンテキストは以下の通りです。
ステートレスコンテキスト
Event (or request) context
ページコンテキスト
対話コンテキスト
セッションコンテキスト
ビジネスプロセスコンテキスト
アプリケーションコンテキスト
これらのコンテキストのいくつかは、サーブレットや関連する仕様に由来していることがわかります。 しかし、このうち 2 つは目新しいかもしれません。 対話コンテキスト (conversation context) とビジネスプロセスコンテキストです。 Web アプリケーション中での状態管理がとても脆弱でエラーが発生しやすい 1 つの理由は、 3 つの組み込みコンテキスト (要求、セッション、アプリケーション) がビジネスロジックの観点から特定の意味を持たないからです。 例えば、実際のアプリケーションのワークフローの観点から見るとユーザーログインセッションは極めて自由裁量な構造です。 そのため、ほとんどの Seam コンポーネントは、対話コンテキストあるいはビジネスプロセスコンテキストのスコープに配置されます。 なぜなら、それらはアプリケーションの観点からとても意味のあるコンテキストだからです。
順に、それぞれのコンテキストを見ていきましょう。
Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (this is really a non-context). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they are important and often useful.
イベントコンテキストは「最も狭い」状態を持つコンテキストで、 他の種類のイベントを網羅する Web 要求コンテキストの概念の一般化です。 それにもかかわらず、JSF 要求のライフサイクルと関連づけられたイベントコンテキストは、 イベントコンテキストの最も重要な用例であり、最もよく利用されるものです。 要求終了時に、イベントコンテキストに関連するコンポーネントは破棄されますが、 それらの状態は、少なくとも要求のライフサイクルの間では有効かつ明確です。
RMI 経由あるいは Seam Remoting により Seam コンポーネントを呼び出すとき、 イベントコンテキストは、その呼び出しだけのために生成、破棄されます。
ページコンテキストは、レンダリングされたページの特定のインスタンスと状態との関連づけを可能にします。 イベントリスナー内で状態の初期化が可能で、 実際にページをレンダリングしている間に、 ページに由来するどんなイベントからも状態にアクセスが可能です。 これは特にサーバサイドのデータ変化にリストが連動しているクリッカブルリストのような機能に役立ちます。 状態は実際にはクライアントのためにシリアライズされます。 そのため、この構造は複数ウインドの操作や戻るボタンに対して極めて堅牢です。