-->@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 を使うことも可能)
例 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 アプリケーションコントローラを利用していたならば、アプリケーションコードのほとんどを排除できたかもしれない。しかしながら、 当時、説明する多くのアプリケーションがありませんでした。
Seam アプリケーションのビューページは、 JSF をサポートする多くの技術を使用して実装されています。 このサンプルでは、JSP より優れていると考えている Facelets を使用しています。
ここで 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.
ミニアプリケーションのプレゼンテーションレイヤは WAR にデプロイされます。 したがって、Web デプロイメント記述子が必要です。
この web.xml
ファイルは Seam と JSF を設定します。 ここで見る設定は Seam アプリケーションではいつも同じです。
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
>
最後に、EARとして アプリケーションがデプロイされるため、デプロイメント記述子も必要になります。
このデプロイメント記述子はエンタープライズアーカイブのモジュールとリンクし、 WEBアプリケーションをコンテキストルート /seam-registration
にバインドします。
これで私たちはアプリケーションにあるすべてのファイルを見ました!
この掲示板サンプルは、 一つのエンティティ Bean である Message
、 一つのセッション Bean である MessageListBean
、 そして一つの JSP から構成されています。
メッセージの一覧をサーバ要求にまたがってメモリにキャッシュしたいので、 ステートフルセッション Bean でこれを行います。
これがセッションスコープの Seam コンポーネントであることに留意してください。 ユーザーログインセッションと関連しログインセッションからのすべての要求は、 同じコンポーネントのインスタンスを共有します。 (Seam アプリケーションでは、セッションスコープのコンポーネントは控えめに使用してください。)
もちろん、すべてのセッション Bean はインタフェースを持ちます。
ここからは、サンプルコード中のローカルインタフェースの記述を省略します。
components.xml
、persistence.xml
、web.xml
、ejb-jar.xml
、faces-config.xml
そして application.xml
は前述までのサンプルとほぼ同じなので、スキップして JSP に進みましょう。
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 String 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
>
Seam は、jPDL プロセス定義を使うことでページフロー定義を可能にします。 この簡単な数字当てゲームサンプルからどのようにこれが実現されているかがわかります。
このサンプルは 一つのJavaBean、三つの JSP ページ、それと jPDL プロセスフロー定義で実装されています。 ページフローから始めましょう。
以下は 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 コンポーネントは純粋なビジネスロジックです! ユーザーインタラクションのフローについて理解する必要はまったくありません。 これによりコンポーネント再利用性を本当に向上させます。
プロジェクトの構成はこれまでのものと同じです。 このアプリケーションをインストールするには、項1.1. 「Seam サンプルを使用する」 を参照してください。 うまくアプリケーションが起動したならば、 ブラウザから http://localhost:8080/seam-booking/
を指定してアクセス可能です。
このアプリケーションは以下の機能を実装するビジネスロジックのために 6 つのセッション Bean を使用しています。
AuthenticatorAction
はログイン認証ロジックを提供します。
BookingListAction
は、その時のログインユーザーのために現状の予約を取得します。
ChangePasswordAction
は、その時のログインユーザーのパスワードを変更します。
HotelBookingAction
は、アプリケーションの中核的機能を実装します。 この機能は 対話 として実装されるため、 このアプリケーションの中でもっとも興味を引くクラスです。
HotelSearchingAction
はホテル検索を実装しています。
RegisterAction
は、新しいシステムユーザーを登録します。
三つのエンティティ Bean はアプリケーション永続ドメインモデルを実装しています。
Hotel
はホテルを表現するエンティティ Bean です。
Booking
は、現状の予約を表すエンティティ Bean です。
User
は、ホテル予約ができるユーザーを表すエンティティ Bean です。
手書きの 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 { @PersistenceContext(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 は対話が既に終了したことを検出し検索ページにリダイレクトします。
例 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("availableRooms") 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 voidconfirm() { // 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 を利用します。 ビジネスプロセスは、異なるプロセス定義を選択することにより動的に変更されるかもしれません。
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.
例 1.32.
@Name("blog") @Scope(ScopeType.STATELESS) @AutoCreate public class BlogService { @In EntityManager 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 |
これは、これまでのところ良いですが、 検索結果ページのようなフォームサブミットの結果のブックマークではどうでしょうか?
フォームは、以下と似たようなものになるでしょう。
<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;
}
}
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>
...
<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
>
<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
>
@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
が発生した時点で対処してください。
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 を押すだけで大丈夫です。
既存のデータモデルで作業をしている場合、 データベースに既にテーブルが存在していることを 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>
別の方法として、Eclise の外部から seam explode
とタイプすることでプロジェクトのデプロイが可能です。
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 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>
手動でデータベースの中にテーブルを生成します。 (別のデータベースに切り替える必要がある場合はもう一度 seam setup
を実行します。) ここで次のように入力します。
seam generate-entities
<core:init debug="true"
>
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 を押してください。
MySQL JDBC Driver テンプレートを選択してください。
Edit Jar/Zip を選択することでコンピュータ上の jar の位置を指定してください。
接続のためのユーザー名とパスワードを確認して正しければ、Ok を押してください。
既存のデータモデルで作業をしている場合、 データベースに既にテーブルが存在していることを JBoss Tools に必ず知らせてください。
接続のためのユーザー名とパスワードを確認して、Test Connection ボタンを使用して接続をテストします。 動作したならば、Finish を押します。
最後に、生成された Bean のパッケージ名を確認して、問題なければ、Finish をクリックします。
-Xms512m -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=512
-Xms256m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256
JBoss Server View にサーバを配置し、サーバの上で右クリックして Edit Launch Configuration を選択してください。
今すぐに変更を行いたくない場合は特に行う必要はありません — OutOfMemoryException
が発生した時点で対処してください。
JBoss の起動、プロジェクトのデプロイのためには、作成したサーバの上の右クリックして Start をクリックしてください。 また、デバッグモードで起動するには Debug をクリックしてください。
最初に、New -> Seam Action と選択してください。
ここでは、Seam コンポーネント名を登録してください。 JBoss Tools は他のフィールドのために気の利いたデフォルトを選択します。
最後に、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
のコードを変更するたびにアプリケーションサーバーをリスタートする必要がないことに留意してください。)
Seam における 2 つの中心的概念は、 コンテキスト の概念と コンポーネント の概念です。 コンポーネントは、ステートフルなオブジェクト、通常は EJB です。 コンポーネントのインスタンスは、コンテキストと関連づけられ、そのコンテキスト中で名前を与えられます。 バイジェクション (Bijection) は、内部のコンポーネント名 (インスタンス変数) をコンテキスト中の名前にエイリアスし、 Seam によるコンポーネントツリーの動的な組み立て、再組み立てを可能にするメカニズムを提供します。
Seam に組み込まれたコンテキストから説明を始めましょう。
対話の観点からアプリケーションについて考えることに慣れるには時間がかかるかもしれません。 しかし、慣れてしまうと、このコンセプトが大好きになり、もう対話なしでは考えられなくなるだろうと思います。
ある対話は単に 1 つの要求の間続いています。 複数の要求をまたぐ対話は、Seam によって提供されたアノテーションを使って、区分を示されなければなりません。
より広い対話の "内部" で対話を発生させるような ネスト も可能です。 これは拡張機能です。
User user = (User) Contexts.getSessionContext().get("user");
Contexts.getSessionContext().set("user", user);
しかしながら、通常、インジェクションを通してコンテキストからコンポーネントを取得し、 アウトジェクションを通してコンポーネントインスタンスをコンテキストに配置します。
デフォルトで、ステートフルセッション Bean は対話コンテキストとバインドします。 ページもしくはステートレスコンテキストとバインドできません。
セッションスコープのステートレスセッション Bean への同時並行要求は、 そのBeanへのSeamインタセプタが無効にされていない限り、常に Seam によってシリアライズされます。
エンティティ Bean コンポーネントはバイジェクションもコンテキスト区分もサポートしません。 また、エンティティ Bean トリガのデータ妥当性検証の呼び出しもサポートしていません。
デフォルトで、エンティティ Bean は対話コンテキストとバインドします。 ステートレスセッション Bean とはバインドしません。
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
しかし、もっと良い方法は、ejb-jar.xml
にインタセプタを定義することです。
<interceptors>
<interceptor>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name
>*</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor
>
すべての Seam コンポーネントは名前が必要です。 @Name
アノテーションを使用してコンポーネントに名前を割り当てます。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
@Name
はコンポーネント名を定義する唯一の方法ではありませんが、 いつも、どこかで名前を指定する必要があります。 もしそうしないと、他の Seam アノテーションはどれも機能しないでしょう。
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}"/>
<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
ファイルに追加する名前空間を含めます。
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}
@Roles
アノテーションは、欲しいだけ多くの追加のロールの指定を可能にします。
@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}
@In
アノテーションは値がインスタンス変数にインジェクトされることを指定しています。
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
...
}
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
(コンポーネントライフサイクルとインジェクションについては次章により多くの情報があります。)
@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;
}
...
}
@Create
メソッドは Seam がコンポーネントをインスタンス化した後に呼ばれます。 コンポーネントは 1 つの @Create
メソッドのみ定義可能です。
@Destroy
メソッドは Seam コンポーネントがバインドするコンテキストが終了するときに呼ばれます。 コンポーネントは 1 つの @Destroy
メソッドのみ定義可能です。
さらに、ステートフルセッション Bean コンポーネントはパラメータ無しの@Remove
を付けることが必須 です。このメソッドはコンテキストが終了するときに Seam により呼ばれます。
@Install
アノテーションは、 特定のデプロイメントシナリオでは必須で別の場合はそうでないようなコンポーネントの条件付インストレーションを可能にします。 これは以下の場合に便利です。
@Install
は 優先順位 と 依存性 を指定することで動作します。
JMS キューと対話する messageSender
という名前のコンポーネントがあるとします。
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
以下のようなうっとうしいコードを見るのに飽き飽きしていませんか。
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);
}
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);
}
User
と Product
が、 現在のコンテキストで有効な 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);
}
@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;
}
...
}
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
@Factory(scope=CONVERSATION)
public List<Customer
> getCustomerList() {
return ... ;
}
二番目のスタイルは、 値をコンテキスト変数そのものにバインドした void
タイプのメソッドです。
@DataModel List<Customer
> customerList;
@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}
管理コンポーネントは @Unwrap
メソッドを持つすべてのコンポーネントです。 このメソッドは、クライアントに見えなくなる値を返し、 毎回 コンテキスト変数が参照されれば呼び出されます。
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer
> getCustomerList() {
return ... ;
}
}
@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
ファイルは次の三つの異なる場所に置くことができます。
例えば、次の 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
>
<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>
<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.xml
で com.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
>
次のように完全修飾名を指定することでその型を上書きすることも可能です。
<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
>
<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
>
<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."/>
例に示す通り、コンポーネントを宣言するには、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
>
@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>
{
...
}
その要素名はコンポーネント名をハイフンで連結した形式になっています。 その要素の属性名はプロパティ名をハイフンで連結した形式になっています。
package org.jboss.seam.example.seampay;
...
@Entity
public class Payment
implements Serializable
{
...
}
コンテキスト依存コンポーネントモデルを補完するものとして、Seamアプリケーションの特徴となっている極度の疎結合を促進させる二つの基本概念が存在します。 最初のものは、イベントがJSFライクなメソッドバインディング式(method binding expression) を通じてイベントリスナーへマップできるような強力なイベントモデルです。 二番目のものは、ビジネスロジックを実装するコンポーネントに対して横断的関心事 (cross-cutting concerns) を適用するためにアノテーションやインタセプタを広範囲に使用しているということです。
これらの多様なイベントすべてはJSF ELメソッドバインディング式を通じてSeamコンポーネントへマップされます。JSFイベントは、JSFテンプレートで次のように定義されます。
<h:commandButton value="Click me!" action="#{helloWorld.sayHello}"/>
jBPM遷移イベントは、jBPMプロセス定義またはページフロー定義で規定されます。
<start-page name="hello" view-id="/hello.jsp">
<transition to="hello">
<action expression="#{helloWorld.sayHello}"/>
</transition>
</start-page
>
JSF イベントや jPBM イベントの詳細については本ガイド以外でも見つけることができるので、 ここでは Seam によって定義される別の二種類のイベントについて見ていきます。
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}"/>
</pages
>
あるいは、 *
ワイルドカードを使ってパターンに一致するすべてのビュー ID
を指定することもできます。
<pages>
<page view-id="/hello/*" action="#{helloWorld.sayHello}"/>
</pages
>
ページアクションのメソッドはJSF outcomeを返すことができます。もしも、そのoutcome がnullでなければ、Seamはビューをナビゲートするためその定義済みナビゲーション規則を使います。
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
>
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
>
もし name
属性が指定されていたら、要求パラメータは PAGE
コンテキストを使って伝播します(モデルプロパティへはマッピングされません)。
<pages>
<page view-id="/hello.jsp" action="#{helloWorld.sayHello}">
<param name="firstName" />
<param name="lastName" />
</page>
</pages
>
書き換えはpages.xml
内のビューで発見される書き換えパターンを元に発生します。SeamのURL書き換えは同一のパターンに基づいて入力方向と出力方向の両方をURL書き換えを実施します。
<page view-id="/home.xhtml">
<rewrite pattern="/home" />
</page>
書き換え規則は、以下の規則に示すように、これらのクエリーパラメータを考慮することができます。
<page view-id="/home.xhtml">
<rewrite pattern="/home/{color}" />
<rewrite pattern="/home" />
</page>
<page view-id="/search.xhtml">
<rewrite pattern="/search-{conversationId}" />
<rewrite pattern="/search" />
</page>
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
>
Seamアプリケーションではfaces-config.xml
で定義される標準のJSFナビゲーション規則を使用できます。しかし、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
>
<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
>
<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
>
<page view-id="/editDocument.xhtml">
<navigation>
<rule if-outcome="success">
<redirect view-id="/#{userAgent}/displayDocument.xhtml"/>
</rule>
</navigation>
</page
>
イベントリスナー (observers) をcomponents.xml
に指定します。
<components>
<event type="hello">
<action execute="#{helloListener.sayHelloBack}"/>
<action execute="#{logger.logHello}"/>
</event>
</components
>
@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!");
}
}
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack() {
FacesMessages.instance().add("Hello to you too!");
}
}
@Name("helloWorld")
public class HelloWorld {
private String name;
public void sayHello() {
FacesMessages.instance().add("Hello World, my name is #0.", name);
Events.instance().raiseEvent("hello", name);
}
}
@Name("helloListener")
public class HelloListener {
@Observer("hello")
public void sayHelloBack(String name) {
FacesMessages.instance().add("Hello #0!", name);
}
}
Seamは特殊なフレームワークの統合のためにアプリケーションが利用可能な多くの組み込みイベントを定義します。そのイベントとは次のようなものです。
org.jboss.seam.noConversation
— 長期対話が存在しない状態で長期対話が要求されたときに呼ばれます
org.jboss.seam.preSetVariable.<name>
— コンテキスト変数 <name> 設定されたときに呼ばれます
org.jboss.seam.postSetVariable.<name>
— コンテキスト変数 <name> が設定されたときに呼ばれます
org.jboss.seam.preRemoveVariable.<name>
— コンテキスト変数 <name> が設定されなくなったら呼ばれます
org.jboss.seam.postRemoveVariable.<name>
— コンテキスト変数 <name> が設定されなくなったら呼ばれます
org.jboss.seam.preDestroyContext.<SCOPE>
— <SCOPE> コンテキストが破壊される前に呼ばれます
org.jboss.seam.postDestroyContext.<SCOPE>
— <SCOPE> コンテキストが破壊された後に呼ばれます
org.jboss.seam.beginConversation
— called whenever a long-running conversation begins
org.jboss.seam.endConversation
— called whenever a long-running conversation ends
org.jboss.seam.beginPageflow
— called when a pageflow begins
org.jboss.seam.beginPageflow.<name>
— called when the pageflow <name> begins
org.jboss.seam.endPageflow.<name>
— called when the pageflow <name> ends
org.jboss.seam.createProcess.<name>
— called when the process <name> is created
org.jboss.seam.endProcess.<name>
— called when the process <name> ends
org.jboss.seam.initTask.<name>
— called when the task <name> is associated with the conversation
org.jboss.seam.startTask.<name>
— called when the task <name> is started
org.jboss.seam.endTask.<name>
— called when the task <name> is ended
org.jboss.seam.postCreate.<name>
— called when the component <name> is created
org.jboss.seam.preDestroy.<name>
— called when the component <name> is destroyed
org.jboss.seam.beforePhase
— called before the start of a JSF phase
org.jboss.seam.afterPhase
— called after the end of a JSF phase
org.jboss.seam.postInitialization
— called when Seam has initialized and started up all components
org.jboss.seam.postAuthenticate.<name>
— called after a user is authenticated
org.jboss.seam.preAuthenticate.<name>
— called before attempting to authenticate a user
org.jboss.seam.notLoggedIn
— called there is no authenticated user and authentication is required
org.jboss.seam.rememberMe
— occurs when Seam security detects the username in a cookie
org.jboss.seam.exceptionHandled.<type>
— キャッチされなかった例外がSeamによって処理されるときに呼ばれます
org.jboss.seam.exceptionHandled
— キャッチされなかった例外がSeamによって処理されるときに呼ばれます
org.jboss.seam.exceptionNotHandled
— キャッチされなかった例外のためのハンドラが存在しなかったときに呼ばれます
org.jboss.seam.afterTransactionSuccess
— Seamアプリケーションフレームワークでトランザクションが成功するときに呼ばれます
Seamコンポーネントは、他のコンポーネント駆動イベントを観察する (observe) のとまったく同様に これらのどのイベントでも観察することが可能です。
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";
}
}
}
@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
{
...
}
Seamの例外処理を有効にするには、主となるサーブレットフィルタをweb.xml
で宣言したことを確認する必要があります。
<filter>
<filter-name
>Seam Filter</filter-name>
<filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name
>Seam Filter</filter-name>
<url-pattern
>*.seam</url-pattern>
</filter-mapping
>
例外ハンドラを機能させる場合は、 web.xml
の Facelets 開発モードおよび components.xml
の Seam デバッグモードも無効にする必要があります。
@HttpError(errorCode=404)
public class ApplicationException extends Exception { ... }
@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
>
<exception class="org.jboss.seam.security.NotLoggedInException" log="false">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
<exception class="org.jboss.seam.security.NotLoggedInException" logLevel="info">
<redirect view-id="/register.xhtml">
<message severity="warn"
>You must be a member to use this feature</message>
</redirect>
</exception
>
<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
>
<exception class="org.jboss.seam.framework.EntityNotFoundException">
<redirect view-id="/error.xhtml">
<message
>Not found</message>
</redirect>
</exception
>
<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
>
<exception class="javax.faces.application.ViewExpiredException">
<redirect view-id="/error.xhtml">
<message
>Your session has timed out, please try again</message>
</redirect>
</exception
>
本章では、 そろそろ Seam の対話モデルについて詳細に理解していくことにします。
事のはじまりは、 3 つの思いつきが融合した結果、 Seam 「対話」の概念となったことです。
ワークスペース という思いつき、 これは 2002 年にビクトリア州政府 (オーストラリア) のプロジェクトで思いつきました。 このプロジェクトで、私は Struts の上にワークスペース管理を実装せざるを得なくなりました。2 度と繰り返したいとは思わないような経験でした。
楽観的セマンティクスで動作するアプリケーショントランザクションという思いつきに加え、 ステートレスなアーキテクチャをベースとする既存のフレームワークでは拡張された永続コンテキストの効率的な管理は実現できないことを実感した事実でした。 (Hibernate チームは LazyInitializationException
に対する非難は聞き飽きていましたし、 実際にはこれは Hibernate に問題があるのではなく、 むしろ Spring フレームワークや J2EE における従来の stateless session facade (anti) パターンなどステートレスアーキテクチャでサポートされる極端に限定的な永続コンテキストモデルに問題があったのです。)
ワークフロータスクという思いつき
こうした思いつきを統一しフレームワークで強力なサポートを提供することで、 以前よりすっきりしたコードでより豊かで効率的なアプリケーションをビルドできるパワフルな構成概念を得ました。
これまでに見た例は、以下の規則に従う非常に単純な対話モデルを利用します。
non-faces 要求全体に Seam 対話を伝播させたい場合、 要求パラメータとして Seam 対話 ID (conversation id) を明示的にコード化する必要があります。
<a href="main.jsf?#{manager.conversationIdParameter}=#{conversation.id}"
>Continue</a
>
<h:outputLink value="main.jsf">
<f:param name="#{manager.conversationIdParameter}" value="#{conversation.id}"/>
<h:outputText value="Continue"/>
</h:outputLink
>
<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
>
<h:commandLink action="main" value="Exit">
<s:conversationPropagation type="none"/>
</h:commandLink
>
対話コンテキストの伝播を無効にすることと、 対話を終了することとは全く異なることですので注意してください。
<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
>
次に @End
が出てくると、 対話スタックから「ポップ(Pop)」されることで、 その取り出された対話は破棄されます。 対話は任意の深さにネストすることができます。
対話スタックの最下位にある対話がルートの対話になります。 この対話を破棄すると常に派生した対話はすべて破棄されます。 @End(root=true)
を指定すると宣言的にこれを行うことができます。
これらのオプションがうまくいかない場合、 Seam では pages.xml
ファイルに ページアクション を定義することができます。
<pages>
<page view-id="/messageList.jsp" action="#{messageManager.list}"/>
...
</pages
>
ページのレンダリング前に行いたいことが対話の開始だけなら、 組み込みアクションメソッドを次のように使用できます。
<pages>
<page view-id="/messageList.jsp" action="#{conversation.begin}"/>
...
</pages
>
また、 この組み込みアクションは JSF コントロールからも呼び出すことができ、 同様に #{conversation.end}
を使って対話を終了することができます。
<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
>
この機能を補ってさらに対話伝播の管理をより簡略化するために、 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"/>
組み込みの対話スコープ 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");
}
}
JSF EL 式を faces メッセージサマリーに含ませることもできます。
facesMessages.add("Document #{document.title} was updated");
たとえば、 通常の方法でメッセージを表示することができます。
<h:messages globalOnly="true"/>
<conversation name="PlaceBid"
parameter-name="auctionId"
parameter-value="#{auction.auctionId}"/>
上記の定義でまず判るのは対話に名前があることです。 この場合 PlaceBid
になります。 名前は特定の指定対話を固有に識別するため page
定義により参加する指定対話の識別に使用されます。
次に指定対話に参加するページを定義します。 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
>
@Begin(join = true)
public void placeBid()
<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"/>
<pages>
<page view-id="/main.xhtml">
<description
>Search hotels: #{hotelBooking.searchString}</description>
</page>
<page view-id="/hotel.xhtml">
<description
>View hotel: #{hotel.name}</description>
</page>
<page view-id="/book.xhtml">
<description
>Book hotel: #{hotel.name}</description>
</page>
<page view-id="/confirm.xhtml">
<description
>Confirm: #{booking.description}</description>
</page>
</pages
>
このファイルが期待する場所になくても Seam アプリケーションは正常に動作を続行します。 動作しない機能はワークスペースの切り替え機能のみです。
次の断片を JSP または facelets のページに含ませて、 現在の対話またはその他いずれのアプリケーションのページにも切り替えられるドロップダウンメニューを取得します。
<h:selectOneMenu value="#{switcher.conversationIdOrOutcome}">
<f:selectItem itemLabel="Find Issues" itemValue="findIssue"/>
<f:selectItem itemLabel="Create Issue" itemValue="editIssue"/>
<f:selectItems value="#{switcher.selectItems}"/>
</h:selectOneMenu>
<h:commandButton action="#{switcher.select}" value="Switch"/>
この例では、 ユーザーに新しい対話を開始させる 2 つの追加アイテムに加えて、 各対話のためのアイテムを含むメニューがあります。
対話一覧は対話切り替えに非常によく似ていますが、 表形式で表示される点が異なります。
<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
>
これを回避するには、 イベントスコープコンポーネントを使ってコンポーネントバインディングを格納し、 それを必要とする対話スコープコンポーネントにインジェクトします。
@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;
...
}
<h:dataTable id="lineItemTable" var="lineItem" value="#{orderHome.lineItems}">
<h:column>
Row: #{uiComponent['lineItemTable'].rowIndex}
</h:column>
...
</h:dataTable
>
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 要求を送信し、 これによりサーバーで非同期にアクションが実行され (クライアントへの応答が即時となる)、 クライアントは次にサーバーに更新をポーリングします。 それぞれのアクションがすべて実行する (いずれもタイムアウトしない) ことが重要となるような長時間実行のアクションがある場合に適した方法となります。
まず、 簡単な「連続」要求を使用するのかどうか、 あるいはポーリングの方法を使用するのかどうかを決める必要があります。
最後に、 クライアントライブラリは未完了の重複要求を最新のものを残してすべて停止するオプションを提供することができます。
ポールスタイルのデザインを使用する場合は細かな調整があまり必要ありません。 アクションメソッド @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;
}
<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
>
<script type="text/javascript"> A4J.AJAX.onError = function(req,status,message) { alert("message"); }; </script >
JBoss jBPM はJava SE や EE 環境のためのビジネスプロセス管理エンジンです。 jBPM はビジネスプロセスやユーザーインタラクションを、 待ち状態、デシジョン、タスク、WEBページなどノードの図式として表現を可能にします。 図式は簡単でとても読みやすい jPDL と呼ばれる XML 表現を使用して定義されており、 Eclipse プラグインを利用して編集、グラフィックによる視覚化が可能です。 jPDL は拡張可能な言語でありWEB アプリケーションのページフローを定義することから、典型的なワークフローの管理、SOA 環境におけるサービスのオーケストレーションまで対応します。
Seam アプリケーションは jBPM を2 つの異なる問題に使用します。
複雑なユーザーインタラクションを含むページフローを定義します。 jPDL プロセス定義は対話のためのページフローを定義します。 Seamの対話はシングルユーザーとの相対的に短期な対話のインタラクションであると考えられます。
ビジネスプロセスを包括的に定義します。 ビジネスプロセスは、複数ユーザーの複数の対話の範囲を含むかもしれません。 その状態は jBPM データベースの中で永続的なので長期的であると考えられます。 複数ユーザーのアクティビティの調整は、 シングルユーザーとのインタラクションについて動作を記述するよりずっと複雑な問題です。 そこで、jBPM は複数の並行な実行パスを扱うようなタスク管理のための洗練された機能を提供します。
この二つを混乱させないでください!まったく異なるレベルまたは粒度で動作します。Pageflow, conversation と task すべて、一人のユーザーの一つのインタラクションを指しています。ビジネスプロセスはたくさんのタスクに広がります。さらに二つのjBPMアプリケーションは完全に直行しています。それらを一緒にも利用できますし、個別にも利用できます。まったく使わないということもできます。
Seamを使うためにJPDLのことは知らなくていいです。もしページフローの定義やSeamナビゲーションルールの定義に何の問題もなく、アプリケーションがプロセスドリブンよりかはデータドリブンである場合には、おそらくjBPMは必要ないでしょう。しかし、私たちはユーザー間のインタラクションを、よくまとまったグラフィカルな図で考えることは、より堅牢なアプリケーションを構築しやすくしてくれます。
Seam にはページフローを定義する 2 つの方法があります。
簡単なアプリケーションではステートレスなナビゲーションモデルで十分です。 とても複雑なアプリケーションは場所に応じて両方を使用します。 それぞれのモデルはそれぞれの強みも弱みもあります。
これは 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}";
}
<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
>
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
これは、checkout
状態 から以前のどの状態 にでも戻るボタンでの遷移が可能です。
<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
>
実際には両ナビゲーションモデルとも使い道がありますが、どんなときにどちらのモデルの方が適切かを理解するために、これから簡単に学んでいきます。
<bpm:jbpm />
Seam の jBPM 関連のコンポーネントをインストールしページフロー定義の場所を指示する必要があります。 この components.xml
に Seam 設定を指定することができます。
<bpm:jbpm>
<bpm:pageflow-definitions>
<value
>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm
>
@Begin
、@BeginTask
あるいは、 @StartTask
アノテーションを使用して、 プロセス定義の名前を指定することによって、 jPDL ベースのページフローを開始します:
@Begin(pageflow="numberguess")
public void begin() { ... }
もしくは、pages.xmlを使用してページフローを開始できます。
<page>
<begin-conversation pageflow="numberguess"/>
</page
>
<pageflow-definition name="viewEditDocument">
<start-state name="start">
<transition name="documentFound" to="displayDocument"/>
<transition name="documentNotFound" to="notFound"/>
</start-state>
<page name="displayDocument" view-id="/document.jsp">
<transition name="edit" to="editDocument"/>
<transition name="done" to="main"/>
</page>
...
<page name="notFound" view-id="/404.jsp">
<end-conversation/>
</page>
</pageflow-definition
>
各 <page>
ノードは、システムがユーザー入力を待っている状態を表します。
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
遷移名は、numberGuess.jsp
において、 ボタン あるいは、リンクをクリックすることによって起動された JSF 結果 (outcome) の名前です。
<h:commandButton type="submit" value="Guess" action="guess"/>
<h:commandButton type="submit" value="Guess"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
ボタンにアクションメソッドを呼ばせることも可能です。 この場合アクション結果 (outcome) が遷移を決定します。
<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
<transition name="correctGuess" to="win"/>
<transition name="incorrectGuess" to="evaluateGuess"/>
</page
>
一般的にページフローを定義するときにjPDLのより強力な機能は必要としていません。しかし<decision>
ノードは必要です。
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision
>
<end-conversation>
、または@End
を使用して対話を終了します。 (実際、可読性のために両方 の使用を勧めます。)
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation/>
</page
>
オプションとしてtransition
名を指定してタスクを終了することができます。 この場合Seam はビジネスプロセスにおいて現在のタスク終了の信号を送るでしょう。
<page name="win" view-id="/win.jsp">
<redirect/>
<end-task transition="success"/>
</page
>
jBPM を設定し、そのjBPMにビジネスプロセス定義の場所を指示する必要があります。
<bpm:jbpm>
<bpm:process-definitions>
<value
>todo.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
ビジネスプロセスインスタンスを初期化するためには @CreateProcess
アノテーションを使用します。
@CreateProcess(definition="todo")
public void createTodo() { ... }
また、 pages.xmlを使用してビジネスプロセスの初期化も行えます:
<page>
<create-process definition="todo" />
</page
>
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task
>
この場合、 単純に現在のユーザーにタスクを割り当てます。 タスクをプールに割り当てることもできます。
<task name="todo" description="#{todoList.description}">
<assignment pooled-actors="employees"/>
</task
>
<h:dataTable value="#{pooledTaskInstanceList}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
<s:link>
の代わりに普通の JSF <h:commandLink>
を使用することもできます。
<h:commandLink action="#{pooledTask.assignToCurrentActor}"
>
<f:param name="taskId" value="#{task.id}"/>
</h:commandLink
>
pooledTask
コンポーネントは単純にタスクを現在のユーザーに割り当てる組み込みコンポーネントです。
taskInstanceListForType
コンポーネントは、 現在のユーザーに割り当てられた特定タイプのタスクを含んでいます。
<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
タスクの作業を開始させるために、リスナーメソッドに @StartTask
あるいは @BeginTask
を使用します。
@StartTask
public String start() { ... }
また、 タスクの実行を pages.xml を使用して始めることもできます:
<page>
<start-task />
</page
>
これらのアノテーションはビジネスプロセス全体に関して意味を持つ特殊な種類の対話を開始します。 この対話による処理はビジネスプロセスコンテキストの中で保持する状態にアクセスできます。
@EndTask
を使用して対話を終了する場合にSeam はタスクの完了サインを送信します。
@EndTask(transition="completed")
public String completed() { ... }
<page>
<end-task transition="completed" />
</page
>
この時点でjBPM はビジネスプロセス定義を引継ぎ、実行を続行します。 (さらに複雑なプロセスではプロセスの実行が可能となる前にタスクが完了される必要があります。)
複雑なビジネスプロセスの管理を実現する各種の高度な機能の全体的な概要については jBPM ドキュメントを参照してください。
Seam は EJB 3.0 で導入される Java Persistence API および Hibernate3 の二つの最も一般的な Java 用永続アーキテクチャに対して広範なサポートを提供します。 Seam 固有の状態管理アーキテクチャにより、 いかなるウェブアプリケーションフレームワークからも高度な ORM 統合を実現します。
Seam はトランザクション独立性の問題と関連フェッチの問題の両方を解決しながら、 「open session in view」に関する問題を回避します。 解決法は二つに分けられます。
Seam トランザクション管理はデフォルトではすべての JSF 要求に有効になっています。 この機能を 無効にしたい 場合は components.xml
で行うことができます。
<core:init transaction-management-enabled="false"/>
<transaction:no-transaction />
<transaction:ejb-transaction />
components.xml に次を追加して JPA RESOURCE_LOCAL トランザクション管理を設定します。 #{em}
は persistence:managed-persistence-context
コンポーネント名です。 管理永続コンテキスト名が entityManager
なら entity-manager
属性を省略することができます。 (Seam 管理の永続コンテキスト を参照)
<transaction:entity-transaction entity-manager="#{em}"/>
Hibernate 管理トランザクションを設定するには 次を components.xml で宣言します。 #{hibernateSession}
はプロジェクトの persistence:managed-hibernate-session
コンポーネント名です。 管理 Hibernate セッション名が session
なら session
属性を省略することができます。 (Seam 管理の永続コンテキスト を参照)
<transaction:hibernate-transaction session="#{hibernateSession}"/>
Seam 管理トランザクションを明示的に無効にするには次を components.xml で宣言します。
<transaction:no-transaction />
Spring 管理トランザクションについては Spring の PlatformTransactionManagement を使用する を参照してください。
管理永続コンテキストの設定は簡単です。 components.xml
内に次のように記述します。
<persistence:managed-persistence-context name="bookingDatabase"
auto-create="true"
persistence-unit-jndi-name="java:/EntityManagerFactories/bookingData"/>
<property name="jboss.entity.manager.factory.jndi.name"
value="java:/EntityManagerFactories/bookingData"/>
これで次のように EntityManager
をインジェクトできます。
@In EntityManager bookingDatabase;
Seam 管理 Hibernate セッションも同様にcomponents.xml
で次のように記述することができます。
<persistence:hibernate-session-factory name="hibernateSessionFactory"/>
<persistence:managed-hibernate-session name="bookingDatabase"
auto-create="true"
session-factory-jndi-name="java:/bookingSessionFactory"/>
java:/bookingSessionFactory
は hibernate.cfg.xml
で指定されるセッションファクトリ名にします。
<session-factory name="java:/bookingSessionFactory">
<property name="transaction.flush_before_completion"
>true</property>
<property name="connection.release_mode"
>after_statement</property>
<property name="transaction.manager_lookup_class"
>org.hibernate.transaction.JBossTransactionManagerLookup</property>
<property name="transaction.factory_class"
>org.hibernate.transaction.JTATransactionFactory</property>
<property name="connection.datasource"
>java:/bookingDatasource</property>
...
</session-factory
>
これで、 次のコードを使って JavaBean コンポーネントに管理 Hibernate Session
をインジェクトできます。
@In Session bookingDatabase;
@In EntityManager em; //a Seam-managed persistence context
@Begin(flushMode=MANUAL)
public void beginClaimWizard() {
claim = em.find(Claim.class, claimId);
}
これで claim
オブジェクトは対話の残りの間、 永続コンテキストによって管理され続けます。 この claim に変更を加えることができます。
public void addPartyToClaim() {
Party party = ....;
claim.addParty(party);
}
ただし、 これらの変更は明示的にフラッシュが発生するよう強制するまではデータベースに対してフラッシュされません。
@End
public void commitClaim() {
em.flush();
}
当然、 pages.xml から flushMode
を MANUAL
にセットすることができます。 たとえばナビゲーション規則では以下のようになります。
<begin-conversation flush-mode="MANUAL" />
いずれの Seam 管理永続コンテキストに対しても手動によるフラッシュモードを使用するよう設定することができます。
<components xmlns="http://jboss.com/products/seam/components" xmlns:core="http://jboss.com/products/seam/core"> <core:manager conversation-timeout="120000" default-flush-mode="manual" /> </components >
EntityManager
インタフェースにより getDelegate()
メソッドを通じてベンダー固有の API にアクセスすることができます。 必然的に、 Hibernate が最も関心の高いベンダーとなり、 org.hibernate.Session
が最も強力となるデリゲートインタフェースになります。 これ以外を使用するのがばかばかしくなるほどです。 これは偏見抜きの意見です。 別の JPA プロバイダを使用しなければならない場合は 代替の JPA プロバイダを使用する をご覧ください。
ただし、 Hibernate またはそれ以外のものいずれを使用するかに限らず、 いずれは Seam コンポーネントでデリゲートを使用したくなる場合がくるでしょう。 以下にその一例を示します。
@In EntityManager entityManager;
@Create
public void init() {
( (Session) entityManager.getDelegate() ).enableFilter("currentVersions");
}
型キャストは Java 言語の中でも間違いなく繁雑な構文になるため、 できる限り避けるのが一般的です。 デリゲートで取得する別の方法を次に示します。 まず、 以下の行を components.xml
に追加します。
<factory name="session"
scope="STATELESS"
auto-create="true"
value="#{entityManager.delegate}"/>
これでセッションを直接インジェクトできるようになります。
@In Session session;
@Create
public void init() {
session.enableFilter("currentVersions");
}
User user = em.createQuery("from User where username=#{user.username}")
.getSingleResult();
User user = em.createQuery("from User where username=:username")
.setParameter("username", user.getUsername())
.getSingleResult();
User user = em.createQuery("from User where username=" + user.getUsername()) //BAD!
.getSingleResult();
<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<my:validateCountry/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<my:validateZip/>
</h:inputText>
</div>
<h:commandButton/>
</h:form
>
Location
クラスで制約を定義するところから始めてみます。
public class Location {
private String country;
private String zip;
@NotNull
@Length(max=30)
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@Length(max=6)
@Pattern("^\d*$")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
たしかに上記が正当ですが、 実際には Hibernate Validator に組み込みのものを使わずにカスタムな制約を使う方がスマートかもしれません。
public class Location {
private String country;
private String zip;
@NotNull
@Country
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@ZipCode
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<s:validate/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<s:validate/>
</h:inputText>
</div>
<h:commandButton/>
</h:form
>
しかし、 例と比べてそれほど冗長性が軽減されているわけではないので、 <s:validateAll>
を使ってみます。
<h:form>
<h:messages/>
<s:validateAll>
<div>
Country:
<h:inputText value="#{location.country}" required="true"/>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true"/>
</div>
<h:commandButton/>
</s:validateAll>
</h:form
>
このタグは単純に <s:validate>
をフォーム内のすべての入力に追加します。 フォームが大きくなる場合は、 入力の手間をかなり省くことができることになります。
<h:inputText value="#{location.zip}" required="true" label="Zip:">
<s:validate/>
</h:inputText
>
validator.length={0} 長さは {min} と {max} の間でなければなりません
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib">
<div>
<s:label styleClass="#{invalid?'error':''}">
<ui:insert name="label"/>
<s:span styleClass="required" rendered="#{required}"
>*</s:span>
</s:label>
<span class="#{invalid?'error':''}">
<h:graphicImage value="/img/error.gif" rendered="#{invalid}"/>
<s:validateAll>
<ui:insert/>
</s:validateAll>
</span>
<s:message styleClass="error"/>
</div>
</ui:composition
>
<s:decorate>
を使って各フォームフィールドにこのテンプレートを含ませることができます。
<h:form>
<h:messages globalOnly="true"/>
<s:decorate template="edit.xhtml">
<ui:define name="label"
>Country:</ui:define>
<h:inputText value="#{location.country}" required="true"/>
</s:decorate>
<s:decorate template="edit.xhtml">
<ui:define name="label"
>Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true"/>
</s:decorate>
<h:commandButton/>
</h:form
>
最後に、 ユーザーがフォーム内を行ったり来たりするのに応じて RichFaces Ajax を使って検証メッセージを表示させることができます。
<h:form>
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label"
>Country:</ui:define>
<h:inputText value="#{location.country}" required="true">
<a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label"
>Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true">
<a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form
>
<h:form id="form">
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label"
>Country:</ui:define>
<h:inputText id="country" value="#{location.country}" required="true">
<a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label"
>Zip code:</ui:define>
<h:inputText id="zip" value="#{location.zip}" required="true">
<a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form
>
public class Location {
private String name;
private String zip;
// Getters and setters for name
@NotNull
@Length(max=6)
@ZipCode(message="#{messages['location.zipCode.invalid']}")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
location.zipCode.invalid = #{location.name} に対する有効な郵便番号ではありません
JBoss Seamのもう一つの特徴として、RAD(高速アプリケーション開発)機能があります。RADと意味は異なりますが、ここで一つ興味深いのは動的言語です。最近まで、動的言語を取り入れるにはまったく異なる開発プラットフォームが必要でした。(そうした開発プラットフォームは使いやすいAPI一式とランタイムを備えているため、もはや古いレガシーJava[原文のまま]の APIを使う気にはなりませんでした。こうしてユーザーは開発プラットフォームごとに独自のAPIを使わざるを得ず、それは開発プラットフォーム側としては幸いな話でした。) Java バーチャルマシンの最上位に構築された動的言語の中でも、特にGroovyは、これまでの縦割り型手法を一新するものです。
JBoss Seam は静的言語と動的言語をシームレスに融合することで、Java EE の世界と動的言語の世界を一体化させています。JBoss Seam によって、アプリケーション開発者はコンテキストを切り替えることなく、最良のツールを使う使い開発することができます。動的な Seam コンポーネントを書くことは、従来のSeam コンポーネントを書くのとまったく同じようなものです。アノテーション、APIなど、これまでとすべて同じものを使うことができます。
SeamコンポーネントをGroovyで記述するのは、Javaで記述するのと同じです。Seamコンポーネントとしてクラスに目印をつけるために、アノテーションを使います。
@Scope(ScopeType.SESSION)
@Name("bookingList")
class BookingListAction implements Serializable
{
@In EntityManager em
@In User user
@DataModel List<Booking> bookings
@DataModelSelection Booking booking
@Logger Log log
@Factory public void getBookings()
{
bookings = em.createQuery('''
select b from Booking b
where b.user.username = :username
order by b.checkinDate''')
.setParameter("username", user.username)
.getResultList()
}
public void cancel()
{
log.info("Cancel booking: #{bookingList.booking.id} for #{user.username}")
Booking cancelled = em.find(Booking.class, booking.id)
if (cancelled != null) em.remove( cancelled )
getBookings()
FacesMessages.instance().add("Booking cancelled for confirmation number #{bookingList.booking.id}", new Object[0])
}
}
JBoss Seamでは元々、増分ホットデプロイメントモード(開発のみ)を使った.groovy
ファイル(つまりコンパイルされていないもの)のデプロイをサポートしています。これによって、修正/テストサイクルが非常に速くなります。.groovyデプロイメントでは、項2.8. 「Seam と増分ホットデプロイメント」に従って設定し、Groovyコード(.groovy
ファイル)をWEB-INF/dev
ディレクトリにデプロイします。アプリケーションを再起動することなく、GroovyBeanコンポーネントが追加されます。(もちろんアプリケーションサーバーの再起動も不要です。)
ネイティブ.groovyファイルのデプロイでは、従来のSeamホットデプロイメントと同じように以下の制約があります。
コンポーネントは必ずJavaBeans もしくは GroovyBeansとし、EJB3 beanを使うことはできません。
エンティティはホットデプロイできません。
ホットデプロイ可能なコンポーネントは、 WEB-INF/dev
の外部にデプロイされたクラスからは見えません。
Seam デバックモードを有効にしなければなりません。
SeamはJSFに替わるプレゼンテーション層のための手段としてWicketをサポートします。Seam上で動作するwicket
の例を見ていくことにしましょう。例は予約サンプルをWicketに移行させたものです。
WicketサポートはSeamにとって新しいものであり、それゆえJSFを使って実現可能ないくつかの機能を、Wicketを使ってまだ実現することができません(例えば、ページフロー)。またドキュメントはJSFを想定して書かれているため、Wicketサポートを使いこなすためには、ドキュメントの読み替えが必要になってくることに気づかれることでしょう。
Wicketサポートが提供する機能は、バイジェクションとオーケストレーションの二つに分類されます。これらについては、後で詳細に説明します。
アノテーションはすべての親クラスに対する呼び出しが終わった後で動作します。これは、this()
やsuper()
の中ではいかなる属性もインジェクションされていないということを意味します。
WicketからSeamコンポーネントを操作するためにすることは、@In
を使ってインジェクションを行うことだけです。
@In(create=true)
private HotelBooking hotelBooking;
Wicketコンポーネントから、Seamコンテキストに対してオブジェクトをアウトジェクトすることも可能です。
@Out(scope=ScopeType.EVENT, required=false)
private String verify;
Wicketコンポーネントに対して@Restrict
アノテーションを使用することができます。このアノテーションはクラスに記述することも、その内部クラスに記述することもできます。@Restrict
が指定されると、コンポーネントは自動的にログインユーザーのみの使用に限定されます。value
属性にEL式を使うことで、限定方法を指定することもできます。詳しくは、章 15. セキュリティを参照して下さい。
例:
@Restrict
public class Main extends WebPage {
...
Seamは、制限を、自動的にその入れ子クラスに対しても適用します。
Wicketコンポーネントの中で、@Begin
や@End
を使用することにより、対話の境界を定めることができます。これらのアノテーションの使い方は、Seamコンポーネントの中で使うのと同じです。@Begin
や@End
は、すべてのメソッドで定義することができます。
ifOutcome
属性はサポートされません。
例:
item.add(new Link("viewHotel") {
@Override
@Begin
public void onClick() {
hotelBooking.selectHotel(hotel);
setResponsePage(org.jboss.seam.example.wicket.Hotel.class);
}
};
ユーザーの長期対話が有効な状態であっても、そこでは対話を使用せず、ただアクセスだけさせたいページもあるでしょう。そのような場合は、@NoConversationPage
アノテーションを使用することができます。
@Restrict @NoConversationPage(Main.class) public class Hotel extends WebPage {
アプリケーション内のクラス同士を疎結合にさせたい場合は、Seamイベントを使用することができます。もちろんEvents.instance().raiseEvent("foo")
のようにしてイベントを使用することもできますし、@RaiseEvent("foo")
のようにメソッドにアノテーションをつけることもできます。後者の場合、メソッドがnullでない結果 (outcome)を返し、例外が発生しない場合のみ、イベントが発生します。
Wicketクラスの中で@CreateProcess
、@ResumeTask
、@BeginTask
、@EndTask
、@StartTask
、@Transition
を使用して、タスクやプロセスの制御を行うことも可能です。
TODO - BPM制御を実装する - JBSEAM-3194
また、クラスにgetHomePage()
メソッドを実装することによって、ホームページを指定することができます。
public class WicketBookingApplication extends SeamWebApplication {
@Override
public Class getHomePage() {
return Home.class;
}
@Override
protected Class getLoginPage() {
return Home.class;
}
}
<components xmlns="http://jboss.com/products/seam/components"
xmlns:wicket="http://jboss.com/products/seam/wicket">
<wicket:web-application application-class="org.jboss.seam.example.wicket.WicketBookingApplication" />
</components
Seam は特殊なインタフェースやスーパークラスを拡張することなく、 純粋な Java クラスにアノテーションを付記することにより簡単にアプリケーションを作成することができます。 しかし、 components.xml
の設定 (簡単な場合には) や機能の拡張により再利用する事ができる既成のコンポーネントを提供することで、 お決まりのプログラムについてさらに簡単に作成できるようにすることができます。
Seamアプリケーションフレームワークは、 JPA やHibernae を使ったデータベースへのアクセスに関わる基本的なプログラムのコード量を削減することができます。
We should emphasize that the framework is extremely simple, just a handful of simple classes that are easy to understand and extend. The "magic" is in Seam itself—the same magic you use when creating any Seam application even without using this framework.
<framework:entity-home name="personHome"
entity-class="eg.Person"
entity-manager="#{personDatabase}">
<framework:id
>#{param.personId}</framework:id>
</framework:entity-home
>
上記が「XML でのプログラミング」に偏重しているように思える場合は、 代りにコードを拡張して使用することもできます。
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {
@In EntityManager personDatabase;
public EntityManager getEntityManager() {
return personDatabase;
}
}
@Stateful
@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
}
クラスをステートレスセッション Bean にすることもできます。 この場合、 その名前が entityManager
であってもインジェクションを使って永続コンテキストを提供しなければなりません。
@Stateless
@Name("personHome")
public class PersonHome extends EntityHome<Person
> implements LocalPersonHome {
@In EntityManager entityManager;
public EntityManager getPersistenceContext() {
entityManager;
}
}
HomeとQueryはセッション、イベント、それに対話スコープで機能するように作成されています。 どのスコープを使用するかは、アプリケーションのステートモデルに依存します。
SeamアプリケーションフレームワークはSeamが管理している永続性コンテキストでのみ動作します。 デフォルトで、entityManager
という名前の永続性コンテキストを探します。
Homeオブジェクトは、特定のエンティティクラスに対する永続性操作を提供します。Person
クラスについて考えてみましょう。
@Entity
public class Person {
@Id private Long id;
private String firstName;
private String lastName;
private Country nationality;
//getters and setters...
}
構成ファイルで、下のようにpersonHome
コンポーネントを定義することができます。
<framework:entity-home name="personHome" entity-class="eg.Person" />
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {}
Home は JSF ページから、下のように直接利用することができます。
<h1
>Create Person</h1>
<h:form>
<div
>First name: <h:inputText value="#{personHome.instance.firstName}"/></div>
<div
>Last name: <h:inputText value="#{personHome.instance.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}"/>
</div>
</h:form
>
通常、Person
はperson
で参照できた方が便利ですので、components.xml
に下のように一行加えて、そのようにしましょう。
<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person" />
(構成ファイルを使用している場合、) PersonHome
に @Factory
を追加します。
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {
@Factory("person")
public Person initPerson() { return getInstance(); }
}
(機能を拡張している場合) これで、下のように JSF ページの記述が簡単になります。
<h1
>Create Person</h1>
<h:form>
<div
>First name: <h:inputText value="#{person.firstName}"/></div>
<div
>Last name: <h:inputText value="#{person.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}"/>
</div>
</h:form
>
<pages>
<page view-id="/editPerson.jsp">
<param name="personId" value="#{personHome.id}"/>
</page>
</pages
>
これで、JSFページにこれらの機能を追加することができます。
<h1>
<h:outputText rendered="#{!personHome.managed}" value="Create Person"/>
<h:outputText rendered="#{personHome.managed}" value="Edit Person"/>
</h1>
<h:form>
<div
>First name: <h:inputText value="#{person.firstName}"/></div>
<div
>Last name: <h:inputText value="#{person.lastName}"/></div>
<div>
<h:commandButton value="Create Person" action="#{personHome.persist}" rendered="#{!personHome.managed}"/>
<h:commandButton value="Update Person" action="#{personHome.update}" rendered="#{personHome.managed}"/>
<h:commandButton value="Delete Person" action="#{personHome.remove}" rendered="#{personHome.managed}"/>
</div>
</h:form
>
要求パラメータ無しでページにリンクした場合、「Person作成」としてページが表示され、personId
を要求パラメータとして渡した場合には、「Person編集」としてページが表示されます。
Person
エントリの nationality を初期化して作成しなければならない場合を考えてみましょう。これも簡単にできます。 構成ファイルを使う場合は;
<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}"/>
<component name="newPerson"
class="eg.Person">
<property name="nationality"
>#{country}</property>
</component
>
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
}
もちろん、Country
オブジェクトは、例えばCountryHome
という別の Home オブジェクトの管理下のオブジェクトとすることもできます。
アソシエーションの管理など、 より洗練された操作を実現するのも PersonHome
にメソッドを追加するだけでできるようになります。
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
public void migrate()
{
getInstance().setCountry(country);
update();
}
}
Homeオブジェクトは操作が成功したときに自動的にフェースメッセージを表示します。 これを、カスタマイズするには、下のように構成を設定します。
<factory name="person"
value="#{personHome.instance}"/>
<framework:entity-home name="personHome"
entity-class="eg.Person"
new-instance="#{newPerson}">
<framework:created-message
>New person #{person.firstName} #{person.lastName} created</framework:created-message>
<framework:deleted-message
>Person #{person.firstName} #{person.lastName} deleted</framework:deleted-message>
<framework:updated-message
>Person #{person.firstName} #{person.lastName} updated</framework:updated-message>
</framework:entity-home>
<component name="newPerson"
class="eg.Person">
<property name="nationality"
>#{country}</property>
</component
>
@Name("personHome")
public class PersonHome extends EntityHome<Person
> {
@In Country country;
@Factory("person")
public Person initPerson() { return getInstance(); }
protected Person createInstance() {
return new Person(country);
}
protected String getCreatedMessage() { return createValueExpression("New person #{person.firstName} #{person.lastName} created"); }
protected String getUpdatedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} updated"); }
protected String getDeletedMessage() { return createValueExpression("Person #{person.firstName} #{person.lastName} deleted"); }
}
しかし、メッセージ定義における最良の方法は (デフォルトで messages
という名前の) Seam に対して既知のリソースバンドルに定義することでしょう。
Person_created=New person #{person.firstName} #{person.lastName} created Person_deleted=Person #{person.firstName} #{person.lastName} deleted Person_updated=Person #{person.firstName} #{person.lastName} updated
この方法を使えば、国際化に対応することができますし、コードや構成ファイルとプレゼンテーション層とを切り離すことができます。
最後のステップは<s:validateAll>
と<s:decorate>
を使って、ページにバリデーション機能を追加することですが、これは皆さんへの宿題としておきましょう。
データベース中のPerson
のすべてのインスタンスのリストが必要な場合、Queryオブジェクトを使って、下のようにすることができます。
<framework:entity-query name="people"
ejbql="select p from Person p"/>
また、これ(この結果)をJSFページから使うことができます。
<h1
>List of people</h1>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable
>
<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20"/>
<pages>
<page view-id="/searchPerson.jsp">
<param name="firstResult" value="#{people.firstResult}"/>
</page>
</pages
>
ページングを管理するJSFのコードは若干繁雑ですが、許容範囲内です。
<h1
>Search for people</h1>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable>
<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="First Page">
<f:param name="firstResult" value="0"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.previousExists}" value="Previous Page">
<f:param name="firstResult" value="#{people.previousFirstResult}"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Next Page">
<f:param name="firstResult" value="#{people.nextFirstResult}"/>
</s:link>
<s:link view="/search.xhtml" rendered="#{people.nextExists}" value="Last Page">
<f:param name="firstResult" value="#{people.lastFirstResult}"/>
</s:link
>
<component name="examplePerson" class="Person"/>
<framework:entity-query name="people"
ejbql="select p from Person p"
order="lastName"
max-results="20">
<framework:restrictions>
<value
>lower(firstName) like lower( concat(#{examplePerson.firstName},'%') )</value>
<value
>lower(lastName) like lower( concat(#{examplePerson.lastName},'%') )</value>
</framework:restrictions>
</framework:entity-query
>
上記の例ではexampleオブジェクトの使用について留意してください。
<h1
>Search for people</h1>
<h:form>
<div
>First name: <h:inputText value="#{examplePerson.firstName}"/></div>
<div
>Last name: <h:inputText value="#{examplePerson.lastName}"/></div>
<div
><h:commandButton value="Search" action="/search.jsp"/></div>
</h:form>
<h:dataTable value="#{people.resultList}" var="person">
<h:column>
<s:link view="/editPerson.jsp" value="#{person.firstName} #{person.lastName}">
<f:param name="personId" value="#{person.id}"/>
</s:link>
</h:column>
</h:dataTable
>
基礎となるエンティティが変化する場合にリフレッシュするには org.jboss.seam.afterTransactionSuccess
イベントを監視します。
<event type="org.jboss.seam.afterTransactionSuccess">
<action execute="#{people.refresh}" />
</event
>
または、 PersonHome
で person エンティティの永続化、 更新、 あるいは削除が行われた場合にクエリーをリフレッシュするだけなら次のようにします。
<event type="org.jboss.seam.afterTransactionSuccess.Person">
<action execute="#{people.refresh}" />
</event
>
このセクションの例では構成による再利用を示していますが、Queryオブジェクトの再利用は機能を拡張して行う事も同様に可能です。
例として、SeamのRegistrationの例のRegisterAction
をSeamアプリケーションフレームワークで書き直すと以下のようになります。
@Stateless
@Name("register")
public class RegisterAction extends EntityController implements Register
{
@In private User user;
public String register()
{
List existing = createQuery("select u.username from User u where u.username=:username")
.setParameter("username", user.getUsername())
.getResultList();
if ( existing.size()==0 )
{
persist(user);
info("Registered new user #{user.username}");
return "/registered.jspx";
}
else
{
addFacesMessage("User #{user.username} already exists");
return null;
}
}
}
Seam では、Seam コンポーネントあるいは jBPM プロセス定義から JBoss Rules (Drools) の RuleBase を容易に呼び出せます。
<drools:rule-base name="policyPricingRules">
<drools:rule-files>
<value
>policyPricingRules.drl</value>
</drools:rule-files>
</drools:rule-base
>
Drools DSLを利用するのであれば DSL 定義も指定しなければなりません。
<drools:rule-base name="policyPricingRules" dsl-file="policyPricing.dsl">
<drools:rule-files>
<value
>policyPricingRules.drl</value>
</drools:rule-files>
</drools:rule-base
>
<drools:rule-agent name="insuranceRules"
configurationFile="/WEB-INF/deployedrules.properties" />
プロパティファイルはその RulesAgent に固有のプロパティを含んでいます。 Drools サンプルディストリビューションからの設定ファイルの例を示します。
newInstance=true url=http://localhost:8080/drools-jbrms/org.drools.brms.JBRMS/package/org.acme.insurance/fmeyer localCacheDir=/Users/fernandomeyer/projects/jbossrules/drools-examples/drools-examples-brms/cache poll=30 name=insuranceconfig
また、 設定ファイルを避けコンポーネントで直接オプションを設定することも可能です。
<drools:rule-agent name="insuranceRules"
url="http://localhost:8080/drools-jbrms/org.drools.brms.JBRMS/package/org.acme.insurance/fmeyer"
local-cache-dir="/Users/fernandomeyer/projects/jbossrules/drools-examples/drools-examples-brms/cache"
poll="30"
configuration-name="insuranceconfig" />
次に、各対話に対して org.drools.WorkingMemory
インスタンスを有効化する必要があります。 (各 WorkingMemory
は、現在の対話に関連する fact を蓄積します。)
<drools:managed-working-memory name="policyPricingWorkingMemory" auto-create="true" rule-base="#{policyPricingRules}"/>
policyPricingWorkingMemory
に、 ruleBase
設定プロパティにより、 RuleBase への参照を与えていることに留意してください。
WorkingMemory
を、 任意の Seam コンポーネントにインジェクトし、 fact をアサートし、そしてルールを実行することができます。
@In WorkingMemory policyPricingWorkingMemory;
@In Policy policy;
@In Customer customer;
public void pricePolicy() throws FactException
{
policyPricingWorkingMemory.assertObject(policy);
policyPricingWorkingMemory.assertObject(customer);
policyPricingWorkingMemory.fireAllRules();
}
<decision name="approval">
<handler class="org.jboss.seam.drools.DroolsDecisionHandler">
<workingMemoryName
>orderApprovalRulesWorkingMemory</workingMemoryName>
<assertObjects>
<element
>#{customer}</element>
<element
>#{order}</element>
<element
>#{order.lineItems}</element>
</assertObjects>
</handler>
<transition name="approved" to="ship">
<action class="org.jboss.seam.drools.DroolsActionHandler">
<workingMemoryName
>shippingRulesWorkingMemory</workingMemoryName>
<assertObjects>
<element
>#{customer}</element>
<element
>#{order}</element>
<element
>#{order.lineItems}</element>
</assertObjects>
</action>
</transition>
<transition name="rejected" to="cancelled"/>
</decision
>
<assertObjects>
エレメントは WorkingMemory
に fact としてアサートされるオブジェクトの集合または 1 オブジェクトを返す EL 式を指定します。
jBPM タスク割り当てのために Drools の使用もサポートしています。
<task-node name="review">
<task name="review" description="Review Order">
<assignment handler="org.jboss.seam.drools.DroolsAssignmentHandler">
<workingMemoryName
>orderApprovalRulesWorkingMemory</workingMemoryName>
<assertObjects>
<element
>#{actor}</element>
<element
>#{customer}</element>
<element
>#{order}</element>
<element
>#{order.lineItems}</element>
</assertObjects>
</assignment>
</task>
<transition name="rejected" to="cancelled"/>
<transition name="approved" to="approved"/>
</task-node
>
package org.jboss.seam.examples.shop import org.jboss.seam.drools.Decision global Decision decision rule "Approve Order For Loyal Customer" when Customer( loyaltyStatus == "GOLD" ) Order( totalAmount <= 10000 ) then decision.setOutcome("approved"); end
package org.jboss.seam.examples.shop import org.jbpm.taskmgmt.exe.Assignable global Assignable assignable rule "Assign Review For Small Order" when Order( totalAmount <= 100 ) then assignable.setPooledActors( new String[] {"reviewers"} ); end
Drools については http://www.drools.org を参照してください。
Seam はシンプルなルールを実装するのには十分な Drools の依存性を同梱しています。 Drools に機能を追加したい場合は完全なディストリビューションをダウンロードしてから必要に応じて追加の依存性を追加してください。
Drools には Java 1.4 用にコンパイルされた MVEL が同梱され、 Java 1.4、 Java 5、 Java 6 と互換性があります。 使用しているバージョン用にコンパイルされたものと MVEL jar を変更したい場合があるかもしれません。
SeamのセキュリティAPIはSeamベースのアプリケーションに種々のセキュリティ関連機能を提供します。 これらの機能には、以下のようなものがあります。
@Name("authenticator")
public class Authenticator {
@In EntityManager entityManager;
@In Credentials credentials;
@In Identity identity;
public boolean authenticate() {
try {
User user = (User) entityManager.createQuery(
"from User where username = :username and password = :password")
.setParameter("username", credentials.getUsername())
.setParameter("password", credentials.getPassword())
.getSingleResult();
if (user.getRoles() != null) {
for (UserRole mr : user.getRoles())
identity.addRole(mr.getName());
}
return true;
}
catch (NoResultException ex) {
return false;
}
}
}
第2のモードはユニークなトークンをクッキーとして記憶しておいて、そのサイトに入ると、passwordの入力をする事なく自動的にユーザーの認証をする機能をサポートするものです。
<div>
<h:outputLabel for="name" value="User name"/>
<h:inputText id="name" value="#{credentials.username}"/>
</div>
<div>
<h:outputLabel for="password" value="Password"/>
<h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
</div
>
<div class="loginRow">
<h:outputLabel for="rememberMe" value="Remember me"/>
<h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>
</div
>
まず最初に、トークンを保持する新たなエンティティを作ります。 以下に、一般的なエンティティの構造を示します。
@Entity
public class AuthenticationToken implements Serializable {
private Integer tokenId;
private String username;
private String value;
@Id @GeneratedValue
public Integer getTokenId() {
return tokenId;
}
public void setTokenId(Integer tokenId) {
this.tokenId = tokenId;
}
@TokenUsername
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@TokenValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
<security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>
ここまでが終了したら、最後はcomponents.xml
にRememberMe
コンポーネントの設定をする事です。 mode
はautoLogin
に設定されていなければなりません。
<security:remember-me mode="autoLogin"/>
これで、remember meをチェックしているユーザーがサイトを再訪した時に、自動的に認証されるようになります。
ユーザーがサイトを再訪した時に確実に自動的に認証される様にするために、components.xml
に以下が含まれている必要があります。
<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}"/>
<action execute="#{identity.tryLogin()}"/>
</event>
<event type="org.jboss.seam.security.loginSuccessful">
<action execute="#{redirect.returnToCapturedView}"/>
</event
>
<pages>
...
<exception class="org.jboss.seam.security.NotLoggedInException">
<redirect view-id="/login.xhtml">
<message
>You must be logged in to perform this action</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.AuthorizationException">
<end-conversation/>
<redirect view-id="/security_error.xhtml">
<message
>You do not have the necessary security privileges to perform this action.</message>
</redirect>
</exception>
</pages
>
ほとんどのwebアプリケーションでは、より洗練されたログインリダイレクトを必要としますが、Seamではこの様なケースに対応できるような機能も持たせています。
<pages login-view-id="/login.xhtml">
<page view-id="/members/*" login-required="true"/>
...
</pages
>
<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}"/>
</event>
<event type="org.jboss.seam.security.postAuthenticate">
<action execute="#{redirect.returnToCapturedView}"/>
</event
>
ログインリダイレクトは対話スコープで実装されていますので、authenticate()
の中で対話を終了させてはいけません。
<web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
<web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>
<security:identity-manager identity-store="#{ldapIdentityStore}"/>
下記の例ではユーザーに関してはLdapIdentityStore
を、またロールに関する処理にはJpaIdentityStore
を使用するようidentityManager
を設定しています。
<security:identity-manager
identity-store="#{ldapIdentityStore}"
role-identity-store="#{jpaIdentityStore}"/>
先に述べたように、特定のアノテーションを使用してユーザーとロールを保持するエンティティBeanを設定します。 下の表に、これらのアノテーションとその詳細な説明について示します。
表 15.2. エンティティのロールのアノテーション
アノテーション |
状態 |
詳細 |
---|---|---|
|
要求条件 |
ユーザーのロール名を保持しているフィールドあるいはメソッドをマークします。 |
|
オプション |
ロールのグループメンバーを保持しているフィールドあるいはメソッドをマークします。 |
|
オプション |
ロールが条件付きか否かを示すフィールドあるいはメソッドをマークします。 |
この単純な例では、クロス参照テーブルUserRoles
を通じてmany-to-many関連でリンクされているuserとroleのテーブルで構成されています。
@Entity
public class User {
private Integer userId;
private String username;
private String passwordHash;
private Set<Role
> roles;
@Id @GeneratedValue
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
@UserPrincipal
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@UserPassword(hash = "md5")
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
@UserRoles
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "UserRoles",
joinColumns = @JoinColumn(name = "UserId"),
inverseJoinColumns = @JoinColumn(name = "RoleId"))
public Set<Role
> getRoles() { return roles; }
public void setRoles(Set<Role
> roles) { this.roles = roles; }
}
@Entity public class Role { private Integer roleId; private String rolename; @Id @Generated public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } @RoleName public String getRolename() { return rolename; } public void setRolename(String rolename) { this.rolename = rolename; } }
この例では、前の最少機能の例にすべてのオプションフィールドと、ロールにグループメンバーを許可する機能を追加しています。
@Entity
public class User {
private Integer userId;
private String username;
private String passwordHash;
private Set<Role
> roles;
private String firstname;
private String lastname;
private boolean enabled;
@Id @GeneratedValue
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
@UserPrincipal
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@UserPassword(hash = "md5")
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
@UserFirstName
public String getFirstname() { return firstname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
@UserLastName
public String getLastname() { return lastname; }
public void setLastname(String lastname) { this.lastname = lastname; }
@UserEnabled
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@UserRoles
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "UserRoles",
joinColumns = @JoinColumn(name = "UserId"),
inverseJoinColumns = @JoinColumn(name = "RoleId"))
public Set<Role
> getRoles() { return roles; }
public void setRoles(Set<Role
> roles) { this.roles = roles; }
}
@Entity public class Role { private Integer roleId; private String rolename; private boolean conditional; @Id @Generated public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } @RoleName public String getRolename() { return rolename; } public void setRolename(String rolename) { this.rolename = rolename; } @RoleConditional public boolean isConditional() { return conditional; } public void setConditional(boolean conditional) { this.conditional = conditional; } @RoleGroups @ManyToMany(targetEntity = Role.class) @JoinTable(name = "RoleGroups", joinColumns = @JoinColumn(name = "RoleId"), inverseJoinColumns = @JoinColumn(name = "GroupId")) public Set<Role > getGroups() { return groups; } public void setGroups(Set<Role > groups) { this.groups = groups; } }
IdentityManager
にアクセスできるようにするには、下のようにSeamコンポーネントにインジェクトします。
@In IdentityManager identityManager;
あるいは、静的なinstance()
メソッド経由でアクセスします。
IdentityManager identityManager = IdentityManager.instance();
下のテーブルにIdentityManager
のAPIのメソッドを示します。
ID管理APIを使うためには、ユーザーはそのメソッドを呼び出す適切な権限を持っている必要があります。 以下の表にIdentityManager
にある個々のメソッドの起動に必要な権限の一覧を示します。 権限はリテラル文字列で指定します。
表 15.5. ID管理 セキュリティパーミッション
メソッド |
パーミッションの対象 |
パーミッションのアクション |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下の例では、admin
ロールのメンバーすべてが、すべてのID管理関連のメソッドへのアクセス権を付与されているセキュリティルールを示しています。
rule ManageUsers no-loop activation-group "permissions" when check: PermissionCheck(name == "seam.user", granted == false) Role(name == "admin") then check.grant(); end rule ManageRoles no-loop activation-group "permissions" when check: PermissionCheck(name == "seam.role", granted == false) Role(name == "admin") then check.grant(); end
それでは、もっとも簡単な形式の認可、コンポーネントのセキュリティについて@Restrict
アノテーションから見てゆきましょう。
空の@Restrict
はcomponent:methodName
を意味します。 下のようなコンポーネントの例を見てみましょう。
@Name("account")
public class AccountAction {
@Restrict public void delete() {
...
}
}
@Restrict @Name("account")
public class AccountAction {
public void insert() {
...
}
@Restrict("#{s:hasRole('admin')}")
public void delete() {
...
}
}
@Name("account")
public class AccountAction {
@In Account selectedAccount;
@Restrict("#{s:hasPermission(selectedAccount,'modify')}")
public void modify() {
selectedAccount.modify();
}
}
public void deleteCustomer() {
Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer,'delete')}");
}
また、下のようにJavaコードから直接hasRole()
やhasPermission()
メソッドを呼ぶこともできます。
if (!Identity.instance().hasRole("admin"))
throw new AuthorizationException("Must be admin to perform this action");
if (!Identity.instance().hasPermission("customer", "create"))
throw new AuthorizationException("You may not create new customers");
<h:form class="loginForm" rendered="#{not identity.loggedIn}"
>
<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">
Manager Reports
</h:outputLink
>
<h:dataTable value="#{clients}" var="cl">
<h:column>
<f:facet name="header"
>Name</f:facet>
#{cl.name}
</h:column>
<h:column>
<f:facet name="header"
>City</f:facet>
#{cl.city}
</h:column>
<h:column>
<f:facet name="header"
>Action</f:facet>
<s:link value="Modify Client" action="#{clientAction.modify}"
rendered="#{s:hasPermission(cl,'modify')"/>
<s:link value="Delete Client" action="#{clientAction.delete}"
rendered="#{s:hasPermission(cl,'delete')"/>
</h:column>
</h:dataTable
>
<page view-id="/settings.xhtml">
<restrict/>
</page
>
このページは暗黙的に、GET要求に対して/settings.xhtml:render
権限を要求し、フェース要求に対しては/settings.xhtml:restore
権限を要求しています。
<page view-id="/reports.xhtml">
<restrict
>#{s:hasRole('admin')}</restrict>
</page
>
Seamのセキュリティは、エンティティ単位でのread,insert,updateおよびdelete操作に対してのセキュリティ制約をかけることを可能にしています。
エンティティクラスのアクション全部に対してセキュリティをかけたいのであれば、下のようにクラスに@Restrict
アノテーションを付記します。
@Entity
@Name("customer")
@Restrict
public class Customer {
...
}
また、下のようにエンティティのライフサイクルに@Restrict
アノテーションを付記することにより、特定の操作だけに制約を課すことができます。
@PostLoad
- エンティティのインスタンスがデータベースからロードされた後に呼び出されます。このメソッドはread
パーミッションの設定に使用してください。
@PrePersist
- エンティティの新規のインスタンスが (データベースに)挿入される前に呼び出されます。 このメソッドはinsert
パーミッションの設定に使用してください。
@PreUpdate
- エンティティが更新される前に呼び出されます。 このメソッドはupdate
パーミッションの設定に使用してください。
@PreRemove
- エンティティが削除される前に呼び出されます。 このメソッドはdelete
パーミッションの設定に使用してください。
@PrePersist @Restrict
public void prePersist() {}
/META-INF/orm.xml
の使用/META-INF/orm.xml
にコールバックメソッドを指定する事もできます:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
<entity class="Customer">
<pre-persist method-name="prePersist" />
</entity>
</entity-mappings
>
rule InsertMemberBlog no-loop activation-group "permissions" when principal: Principal() memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName()))) check: PermissionCheck(target == memberBlog, action == "insert", granted == false) then check.grant(); end;
最後に、JPAプロバイダをSeamセキュリティと統合するために、リスナークラスをインストールします。
EJB3エンティティBeanのセキュリティチェックはEntityListener
により行われ、下記のようなMETA-INF/orm.xml
の設定でリスナーをインストールすることができます。
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings
>
Seamは@Restrict
に代わるアノテーションをいくつか持っており、これらを使う事により@Restrict
とは異なりEL式の評価を行わないので、コンパイル時の安全性を提供します。
@Insert(Customer.class) public void createCustomer() { ... }
public void updateCustomer(@Update Customer customer) { ... }
下のように、独自のセキュリティアノテーションを作る場合には、単に@PermissionCheck
とアノテートするだけです。
@Target({METHOD, PARAMETER})
@Documented
@Retention(RUNTIME)
@Inherited
@PermissionCheck
public @interface Promote {
Class value() default void.class;
}
もしデフォルトのアクセス権アクション名(アノテーション名の小文字版)を他の値で上書きする必要があれば、@PermissionCheck
アノテーション内にその値を指定することができます。
@PermissionCheck("upgrade")
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:security="http://jboss.com/products/seam/security"
xmlns:drools="http://jboss.com/products/seam/drools"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.1.xsd"
http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd">
<drools:rule-base name="securityRules">
<drools:rule-files>
<value
>/META-INF/security.drl</value>
</drools:rule-files>
</drools:rule-base>
</components
>
デフォルトのルールベースの名前はRuleBasedPermissionResolver
のsecurity-rules
属性で上書きする事ができます。
<security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>
セキュリティルールの内容は? この段階ではDroolsのドキュメントから適当に拝借してくるのが良いかもしれませんが、ここでは非常に単純な例から始めてみる事にしましょう
package MyApplicationPermissions; import org.jboss.seam.security.permission.PermissionCheck; import org.jboss.seam.security.Role; rule CanUserDeleteCustomers when c: PermissionCheck(target == "customer", action == "delete") Role(name == "admin") then c.grant(); end
では、一つづつ見てゆきましょう。 最初はパッケージ宣言です。 Droolのパッケージはルールの集まりで、ルールベースの範囲外とは何ら関わりが無いので、パッケージの名前は任意で構いません。
ルールについてLHSを見ると、二つの条件がある事が分かります。 まず、最初の条件を見てみましょう。
c: PermissionCheck(target == "customer", action == "delete")
Role(name == "admin")
ルールが適用されると、何が起こるのでしょうか? 次にルールのRHS側を見てみましょう。
c.grant()
例えば、ユーザーとロールのパーミッションを一つのエンティティクラスに保存するよう設定する場合は次のようになります。
<security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission"/>
ユーザーパーミッションとロールパーミッションを別のエンティティクラスに保存する場合の設定は次のようになります。
<security:jpa-permission-store user-permission-class="com.acme.model.UserPermission"
role-permission-class="com.acme.model.RolePermission"/>
この例ではユーザーとロールパーミッションが一つのエンティティクラスに保持されています。 下に示したクラスはサンプルのSeamSpaceからのものです。
@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;
}
}
IdentifierStrategy
インタフェースはたいへんに単純で、二つのメソッドを宣言しているだけです。
public interface IdentifierStrategy {
boolean canIdentify(Class targetClass);
String getIdentifier(Object target);
}
Seamは二つのIdentifierStrategy
実装、ClassIdentifierStrategy
とEntityIdentifierStrategy
を提供しています(詳細は次のセクション)。
@Identifier(name = "customer")
public class Customer {
以下のクラスの識別子は"customerAction
"となります:
@Name("customerAction")
public class CustomerAction {
最終的に、以下のクラスの識別子は "Customer
"となります:
public class Customer {
@Identifier(value = EntityIdentifierStrategy.class)
public class Customer {
生成される識別子の例として、下のようなエンティティクラスを考えてみましょう。
@Entity
public class Customer {
private Integer id;
private String firstName;
private String lastName;
@Id
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
id
が1
のCustomer
のインスタンスに対する識別子は"Customer:1
"となります。 もし、エンティティクラスに明示的な識別子名のアノテーションがあれば、
@Entity
@Identifier(name = "cust")
public class Customer {
<security:permission-manager permission-store="#{ldapPermissionStore}"/>
以下の表にPermissionManager
の提供するメソッドの詳細を示します。
<page view-id="/login.xhtml" scheme="https"/>
<s:link view="/login.xhtml" value="Login"/>
<page view-id="*" scheme="http" />
もちろん、HTTPSを使う必要がなければ、デフォルトのschemaを指定する必要もありません。
以下のように、components.xml
に設定することにより、スキーマが変さらになるたびに現在のHTTPセッションを自動的に無効にする事ができます。
<web:session invalidate-on-scheme-change="true"/>
このオプションはHTTPSのページから、HTTPのページへの重要データの漏れや、セッションIDの盗聴に対する脆弱性を減少させます。
もし個別にHTTPとHTTPSの使用を設定する必要があるのであれば、pages.xml
のpages
エレメントにhttp-port
あるいは https-port
を設定することにより行えます。
<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"
no-conversation-view-id="/home.xhtml"
login-view-id="/login.xhtml"
http-port="8080"
https-port="8443"
>
キャプチャチャレンジをフォームに追加するのは以下のようにいたって簡単です:
<h:graphicImage value="/seam/resource/captcha"/>
<h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true">
<s:validate />
</h:inputText>
<h:message for="verifyCaptcha"/>
内蔵コンポーネントをオーバーライドする事により、キャプチャのアルゴリズムをカスタマイズすることができます。
@Name("org.jboss.seam.captcha.captcha")
@Scope(SESSION)
public class HitchhikersCaptcha extends Captcha
{
@Override @Create
public void init()
{
setChallenge("What is the answer to life, the universe and everything?");
setCorrectResponse("42");
}
@Override
public BufferedImage renderChallenge()
{
BufferedImage img = super.renderChallenge();
img.getGraphics().drawOval(5, 3, 60, 14); //add an obscuring decoration
return img;
}
}
Seam では容易に国際化されたアプリケーションを作成できます。 まず、 アプリケーションの国際化および地域化に必要とされるすべての段階について見ていきます。 そのあと Seam が同梱するコンポーネントの説明をします。
JEE アプリケーションは数多くのコンポーネントから構成され、 それらのコンポーネントすべてがローカライズを行うアプリケーションに対して正しく設定されていなければなりません。
<Connector port="8080" URIEncoding="UTF-8"/>
別の方法もあり、 こちらの方がよいでしょう。 JBoss AS に要求パラメータのエンコーディングはその要求から取得されることを指示することができます。
<Connector port="8080" useBodyEncodingForURI="true"/>
このツールの使い方は Java 5 の場合はここ または Java 6 の場合はここ に記載されています。 たとえば、 あるファイルを UTF-8 から変換するには次のようにします。
$ native2ascii -encoding UTF-8 messages_cs.properties > messages_cs_escaped.properties
<h:selectOneMenu value="#{localeSelector.language}">
<f:selectItem itemLabel="English" itemValue="en"/>
<f:selectItem itemLabel="Deutsch" itemValue="de"/>
<f:selectItem itemLabel="Francais" itemValue="fr"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}"
value="#{messages['ChangeLanguage']}"/>
あるいは、faces-config.xml
に対応されたすべてのロケールの組み合わせが欲しければ、 以下を使ってください。
<h:selectOneMenu value="#{localeSelector.localeString}">
<f:selectItems value="#{localeSelector.supportedLocales}"/>
</h:selectOneMenu>
<h:commandButton action="#{localeSelector.select}"
value="#{messages['ChangeLanguage']}"/>
ユーザーがドロップダウンからアイテムを選択してコマンドボタンをクリックすると、 その後のセッションに対して Seam と JSF のロケールはオーバライドされます。
<international:locale-config default-locale="fr_CA" supported-locales="en fr_CA fr_FR"/>
必然的にロケールをサポートすることを宣言する場合、 一致するリソースバンドルを与えた方がよいでしょう。 次は言語固有のラベルの定義方法について説明します。
従って、messages_en.properties
では、
Hello=Hello
そして、messages_en_AU.properties
では、
Hello=G'day
<core:resource-loader>
<core:bundle-names>
<value>mycompany_messages</value>
<value>standard_messages</value>
</core:bundle-names>
</core:resource-loader>
pages.xml
に明示的なバンドル名を指定することもできます。
<page view-id="/welcome/hello.jsp" bundle="HelloMessages"/>
これで HelloMessages.properties
に定義されたメッセージを /welcome/hello.jsp
で使うことができます。
<h:outputText value="#{messages['Hello']}"/>
<h:outputText value="#{messages.Hello}"/>
さらに良いことに、メッセージそのものには EL 式を含むことも可能です。
Hello=Hello, #{user.firstName} #{user.lastName}
Hello=G'day, #{user.firstName}
@In private Map<String, String> messages;
@In("#{messages['Hello']}") private String helloMessage;
facesMessages
コンポーネントはユーザーに成功か失敗かを表示するとても便利な方法です。 上述した機能は、faces messages にも有効です。
@Name("hello")
@Stateless
public class HelloBean implements Hello {
@In FacesMessages facesMessages;
public String sayIt() {
facesMessages.addFromResourceBundle("Hello");
}
}
これは、ユーザーのロケールに応じて、Hello, Gavin King
あるいは、 G'day, Gavin
と表示されます。
<theme:theme-selector cookie-enabled="true">
<theme:available-themes>
<value>default</value>
<value>accessible</value>
<value>printable</value>
</theme:available-themes>
</theme:theme-selector>
最初にリストされたテーマがデフォルトテーマであることに注意してください。
css ../screen.css template /template.xhtml
通常、 テーマリソースバンドルのエントリは CSS スタイルや画像へのパスや facelet テンプレートの名前になるでしょう (通常はテキストであるローカライゼーションリソースバンドルとは違って)。
これでJSPやfaceletページにおいてこれらのエントリを使えます。 例えば、faceletページでスタイルシートを適用するには:
<link href="#{theme.css}" rel="stylesheet" type="text/css" />
あるいは、サブディレクトリにページ定義が存在している場合は次のようになります。
<link href="#{facesContext.externalContext.requestContextPath}#{theme.css}"
rel="stylesheet" type="text/css" />
最も強力な使い方として、 faceletでは <ui:composition>
によってテンプレートを適用できます。
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
template="#{theme.template}">
ちょうどロケールセレクタのように、 ユーザーが自由にテーマを変更できるよう、組み込みのテーマセレクタがあります。
<h:selectOneMenu value="#{themeSelector.theme}">
<f:selectItems value="#{themeSelector.themes}"/>
</h:selectOneMenu>
<h:commandButton action="#{themeSelector.select}" value="Select Theme"/>
多くの人が一緒に作業するウェブサイトでは、 フォーラムへの投稿、wikiページ、blog、コメントなどでフォーマット済みテキストを簡単に入力するために人間が扱いやすいマークアップ言語が必要になります。 Seamには Seam Text と呼ばれる言語に従ったフォーマット済みテキストを Web 表示するための仕組みがあって、 <s:formattedText/>
によってそれを制御します。 Seam TextはANTLRベースのパーサを利用して実装されていますが、 Seam Textを利用するにあたってANTLRについての知識は不要なので安心してください。
It's easy to make *emphasis*, |monospace|, ~deleted text~, super^scripts^ or _underlines_.
これを <s:formattedText/>
で表示させると、 以下のHTMLが生成されます:
<p>
It's easy to make <i
>emphasis</i
>, <tt
>monospace</tt>
<del
>deleted text</del
>, super<sup
>scripts</sup
> or <u
>underlines</u
>.
</p
>
空行は新しいパラグラフを作成するときに使用します。 また、+
は見出しに使用します:
+This is a big heading You /must/ have some text following a heading! ++This is a smaller heading This is the first paragraph. We can split it across multiple lines, but we must end it with a blank line. This is the second paragraph.
(段落内の改行は単に無視されます。 新しい段落としてテキストを記述したい場合は空行が必要なことを忘れないでください。) これが結果の HTML です:
<h1
>This is a big heading</h1>
<p>
You <i
>must</i
> have some text following a heading!
</p>
<h2
>This is a smaller heading</h2>
<p>
This is the first paragraph. We can split it across multiple
lines, but we must end it with a blank line.
</p>
<p>
This is the second paragraph.
</p
>
順序付きリストは #
文字で作成できます。順序なしリストは =
文字を使います:
An ordered list: #first item #second item #and even the /third/ item An unordered list: =an item =another item
<p>
An ordered list:
</p>
<ol
>
<li
>first item</li>
<li
>second item</li>
<li
>and even the <i
>third</i
> item</li>
</ol>
<p>
An unordered list:
</p>
<ul>
<li
>an item</li>
<li
>another item</li>
</ul
>
The other guy said: "Nyeah nyeah-nee /nyeah/ nyeah!" But what do you think he means by "nyeah-nee"?
<p>
The other guy said:
</p>
<q
>Nyeah nyeah-nee
<i
>nyeah</i
> nyeah!</q>
<p>
But what do you think he means by <q
>nyeah-nee</q
>?
</p
>
*
, |
, #
などの特殊文字や、<
, >
, &
などのHTMLで利用される文字は \
でエスケープします:
You can write down equations like 2\*3\=6 and HTML tags like \<body\ > using the escape character: \\.
<p>
You can write down equations like 2*3=6 and HTML tags
like <body> using the escape character: \.
</p
>
また、 バックティック (`) を使ってコードのブロックを囲むことができます。
My code doesn't work: `for (int i=0; i<100; i--) { doSomething(); }` Any ideas?
<p>
My code doesn't work:
</p>
<pre
>for (int i=0; i<100; i--)
{
doSomething();
}</pre>
<p>
Any ideas?
</p
>
This is a |<tag attribute="value"/>| example.
Go to the Seam website at [= >http://jboss.com/products/seam].
Go to [the Seam website= >http://jboss.com/products/seam].
上級者向けですが、この構文を利用したwikiワードのリンクを解釈できるようSeam Textパーサをカスタマイズすることも可能です。
Seam now includes a component set for generating documents using iText. The primary focus of Seam's iText document support is for the generation of PDF doucuments, but Seam also offers basic support for RTF document generation.
|
詳細 ドキュメントは 属性
メタデータの属性
使い方 <p:document xmlns:p="http://jboss.com/products/seam/pdf" |
|
詳細 テキストでの使用が多く、 文章をひとかたまりごとに区切るため理にかなったグループでテキストの断片の流れを作り、形成、スタイル化することができます。 属性
使い方 <p:paragraph alignment="justify"> |
|
詳細
属性
使い方 <p:paragraph> |
|
詳細
属性
使い方
|
|
詳細 フォントタグはその中のすべてのテキストに使用されるデフォルトフォントを定義します。 属性
使い方 <p:font name="courier" style="bold" size="24"> |
|
詳細
使い方 <p:newPage /> |
|
詳細
リソースはアプリケーションコードで動的に生成することもできます。 属性
使い方 <p:image value="/jboss.jpg" /> <p:image value="#{images.chart}" /> |
|
詳細
属性
使い方 <p:listItem |
|
詳細 The 属性
使い方 <p:facet name="header"> |
|
詳細 現在のページ番号は 使い方 <p:footer borderWidthTop="1" borderColorTop="blue" |
|
詳細 生成されるドキュメントが本または論文の構造をとる場合、 属性
使い方 <p:document xmlns:p="http://jboss.com/products/seam/pdf" |
|
詳細 いずれの章やセクションも |
<p:document xmlns:p="http://jboss.com/products/seam/pdf"
xmlns:ui="http://java.sun.com/jsf/facelets"
title="Hello">
<p:list style="numbered">
<ui:repeat value="#{documents}" var="doc">
<p:listItem
>#{doc.name}</p:listItem>
</ui:repeat>
</p:list>
</p:document
>
|
属性
使い方 <p:list style="numbered"> |
|
詳細
属性
使い方 ... |
|
詳細
属性
使い方 <p:table columns="3" headerRows="1"> |
|
詳細
属性
使い方 <p:cell |
|
詳細 棒グラフを表示します。 属性
使い方 <p:barchart title="Bar Chart" legend="true" |
|
詳細 折れ線グラフを表示します。 属性
使い方 <p:linechart title="Line Chart" |
|
詳細 円グラフを表示します。 属性
使い方 <p:piechart title="Pie Chart" circular="false" direction="anticlockwise" |
|
詳細 カテゴリデータはシリーズに分割できます。 シリーズタグを使ってデータセットをシリーズで分類しそのシリーズ全体にスタイリングを適用します。 属性
使い方 <p:series key="data1"> |
|
詳細 データタグはグラフ内で表示される各データポイントを表現します。 属性
使い方 <p:data key="foo" value="20" sectionPaint="#111111" |
|
詳細 色コンポーネントは色埋めされた形を描く場合に参照できる色または階調を宣言します。 属性
使い方 <p:color id="foo" color="#0ff00f"/> |
|
詳細 グラフ内に先途を描くのに使用する線を表します。 属性
使い方 <p:stroke id="dot2" width="2" cap="round" join="bevel" dash="2 3" /> |
|
詳細 バーコードイメージを表示します。 属性
使い方 <p:barCode type="code128" |
ドキュメントの生成は特に設定を必要とすることなく、 そのまま動作させることができます。 ただし、 本格的なアプリケーションには必要となる設定ポイントがいくつかあります。
<servlet>
<servlet-name
>Document Store Servlet</servlet-name>
<servlet-class
>org.jboss.seam.document.DocumentStoreServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name
>Document Store Servlet</servlet-name>
<url-pattern
>*.pdf</url-pattern>
</servlet-mapping
>
<components xmlns="http://jboss.com/products/seam/document"
xmlns:pdf="http://jboss.com/products/seam/document">
<document:document-store use-extensions="true" />
</components
>
<document:document-store use-extensions="true" error-page="/documentMissing.seam" />
Seamでは素晴らしいJExcelAPI ライブラリを通じて、Microsoft® Excel® スプレッドアプリケーションのスプレッドシートを作成することもできます。作成されたドキュメントはthe Microsoft® Excel® spreadsheet applicationの95、97、2000、XP、2003の各バージョンと互換性があります。現在のところは、ライブラリの機能の内、限定的にいくつかの機能が使えるのみですが、ライブラリで可能なすべてのことをSeamでも可能にすることが最終的な目標です。Seamで可能な操作、今のところ不可能な操作について詳しく知りたい場合は、JExcelAPIのドキュメントを参照して下さい。
<excel:excelFactory>
<property name="implementations">
<key
>myExcelExporter</key>
<value
>my.excel.exporter.ExcelExport</value>
</property>
</excel:excelFactory
>
そして、excel名前空間をcomponentsタグに登録して下さい。
xmlns:excel="http://jboss.com/products/seam/excel"
ドキュメントを.xls拡張子とともに出力するためのサーブレットの設定の仕方については項18.5. 「iText を設定する」を参照して下さい。
もし、IEで(特にhttpsで)、作成されたファイルを開いた際に問題が発生した場合は、ブラウザの制限が厳しすぎないか(http://www.nwnetworks.com/iezones.htm/を参照)、web.xmlでのセキュリティ制約が厳しすぎないか、またはその両方を確認して下さい。
シートを利用するための基本は簡単です。それは、使いなれた<h:dataTable>
に似ており、List
、Set
、Map
、Array
、DataModel
に結びつけることができます。
<e:workbook xmlns:e="http://jboss.com/products/seam/excel">
<e:worksheet>
<e:cell column="0" row="0" value="Hello world!"/>
</e:worksheet>
</e:workbook>
これはあまり使える例ではありません。もう少し一般的な例を見てみましょう。
<e:workbook xmlns:e="http://jboss.com/products/seam/excel">
<e:worksheet value="#{data}" var="item">
<e:column>
<e:cell value="#{item.value}"/>
</e:column>
</e:worksheet>
</e:workbook>
workbook要素は、一番外側の要素であり、worksheet要素やスタイルシートのためのlink要素の親となります。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:cell value="Hello World" row="0" column="0"/>
</e:worksheet>
<e:workbook>
|
子要素
ファセット
|
<e:workbook>
<e:worksheet name="foo" startColumn="1" startRow="1">
<e:column value="#{personList}" var="person">
<f:facet name="header">
<e:cell value="Last name"/>
</f:facet>
<e:cell value="#{person.lastName}"/>
</e:column>
</e:worksheet>
<e:workbook>
column要素は、worksheet要素の子要素で、cell要素、image要素、formula要素、hyperlink要素の親要素です。これらはワークシートのデータ列挙を制御するためのタグになります。書式の制御については項19.14.5. 「列の設定」を参照して下さい。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:column value="#{personList}" var="person">
<f:facet name="header">
<e:cell value="Last name"/>
</f:facet>
<e:cell value="#{person.lastName}"/>
</e:column>
</e:worksheet>
<e:workbook>
これは、ヘッダーと値の列挙からなる列を定義します。
Cells are nested within columns (for iteration) or inside worksheets (for direct placement using the column
and row
attributes) and are responsible for outputting the value (usually though en EL-expression involving the var
-attribute of the datatable. See ???
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet
>
<e:column value="#{personList}" var="person">
<f:facet name="header">
<e:cell value="Last name"/>
</f:facet>
<e:cell value="#{person.lastName}"/>
</e:column>
</e:worksheet>
</e:workbook
>
これは、ヘッダーと値の列挙からなる列を定義します。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:column value="#{personList}" var="person"
>
<e:cell value="#{person.age">
<e:numericValidation condition="between" value="4"
value2="18"/>
</e:cell>
</e:column>
</e:worksheet>
</e:workbook
>
これは、値が4と18の間の値でなければならないというバリデーション条件をセルに付加します。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:column value="#{personList}" var="person"
>
<e:cell value="#{person.position">
<e:rangeValidation startColumn="0" startRow="0"
endColumn="0" endRow="10"/>
</e:cell>
</e:column>
</e:worksheet>
</e:workbook
>
これは、値がA1:A10の範囲にある値のどれかでなければならないというバリデーション条件をセルに付加します。
|
属性
子要素
ファセット
|
e:listValidation要素は、複数のe:listValidationItem要素を保持するための、ただの入れ物です。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:column value="#{personList}" var="person"
>
<e:cell value="#{person.position">
<e:listValidation>
<e:listValidationItem value="manager"/>
<e:listValidationItem value="employee"/>
</e:listValidation>
</e:cell>
</e:column>
</e:worksheet>
</e:workbook
>
これは、セルの値が"manager"もしくは"employee"でなければならないというバリデーション条件をセルに付加します。
書式マスクがあった場合、まずそれが組み込みの形式であるかどうかがチェックされます。組み込みの形式は例えば、"format1"や"accounting_float"のようなものです( jxl.write.NumberFormats を参照して下さい。)
マスクがリストになかった場合は、カスタムマスクとして扱われます( java.text.DecimalFormat を参照下さい)。例えば"0.00"であったり、自動的に一番近いものに変換された書式が採用されます。
書式マスクがあった場合、まずそれが組み込みの形式であるかどうかがチェックされます。組み込みの形式は例えば、"format1"や"format2"のようなものです( jxl.write.DecimalFormats を参照して下さい)。
マスクがリストになかった場合は、カスタムマスクとして扱われます( java.text.DateFormat を参照して下さい)。例えば"dd.MM.yyyy"であったり、自動的に一番近いものに変換された書式が採用されます。
formula要素は、column要素の中(値の列挙のため)、あるいはworksheet要素の中(column
属性とrow
属性を用いた直接的な配置のため)に置かれ、ある範囲のセル値の計算や関数の適用を行います。formula要素は本質的にセルなので、利用可能な属性を見るためには項19.6. 「cell要素」を参照して下さい。formula要素は、テンプレートにも適用することができ、通常のセルと同様、独自のフォントなどの定義を持つこともできることに注意して下さい。
The formula of the cell in placed in the value
-attribute as a normal the Microsoft® Excel® spreadsheet application notation. Note that when doing cross-sheet formulas, the worksheets must exist before referencing a formula against them. The value is a string.
<e:workbook>
<e:cellTemplate name="fooTemplate">
<e:font color="red"/>
</e:cellTemplate>
<e:worksheet name="fooSheet">
<e:cell column="0" row="0" value="1"/>
</e:worksheet>
<e:worksheet name="barSheet">
<e:cell column="0" row="0" value="2"/>
<e:formula column="0" row="1"
value="fooSheet!A1+barSheet1!A1"
templates="fooTemplate">
<e:font fontSize="12"/>
</e:formula>
</e:worksheet>
</e:workbook
>
これは、BarSheetのB1セルに、FooSheetとBarSheetのA1セル同士を加算する式を定義します。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:image startRow="0" startColumn="0" rowSpan="4"
columnSpan="4" URI="http://foo.org/logo.jpg"/>
</e:worksheet>
</e:workbook
>
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:hyperLink startRow="0" startColumn="0" endRow="4"
endColumn="4" URL="http://seamframework.org"
description="The Seam Framework"/>
</e:worksheet>
</e:workbook
>
|
属性
子要素
ファセット
|
|
属性
子要素
ファセット
|
facet要素の内容には、以下のように#で区切られたさまざまなコマンドを含めることができます。
#date# |
現在の日付を挿入します。 |
#page_number# |
現在のページを挿入します。 |
#time# |
現在の時刻を挿入します。 |
#total_pages# |
総ページ数を挿入します。 |
#worksheet_name# |
シート名を挿入します。 |
#workbook_name# |
ブック名を挿入します。 |
#bold# |
強調文字に切り替えます。次の#bold#まで有効となります。 |
#italics# |
斜体文字に切り替えます。次の#italic#まで有効となります。 |
#underline# |
下線を引きます。次の#underline#まで有効となります。 |
#double_underline# |
二重下線を引きます。次の#double_underline#まで有効となります。 |
#outline# |
アウトラインフォントに切り替えます。次の#outline#まで有効となります。 |
#shadow# |
影付き文字に切り替えます。次の#shadow#まで有効となります。 |
#strikethrough# |
取り消し線を引きます。次の#strikethrough#まで有効となります。 |
#subscript# |
下付き文字に切り替えます。次の#subscript#まで有効となります。 |
#superscript# |
上付き文字に切り替えます。次の#superscript#まで有効となります。 |
#superscript# |
フォント名を設定します。#font_name=Verdana#のように設定します。 |
#font_size# |
フォントサイズを設定します。#font_size=12#のように設定します。 |
<e:workbook>
<e:worksheet
>
<e:header>
<f:facet name="left">
This document was made on #date# and has #total_pages# pages
</f:facet>
<f:facet name="right">
#time#
</f:facet>
</e:header>
<e:worksheet>
</e:workbook>
printArea要素とprintTitle要素はワークシートやワークシートテンプレートの子要素であり、印刷範囲や印刷タイトルを設定します。
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet
>
<e:printTitles firstRow="0" firstColumn="0"
lastRow="0" lastColumn="9"/>
<e:printArea firstRow="1" firstColumn="0"
lastRow="9" lastColumn="9"/>
</e:worksheet>
</e:workbook>
ワークシートコマンド要素はworkbook要素の子要素であり、通常一回だけ実行されます。
|
属性
子要素
ファセット
|
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet
>
<e:groupRows startRow="4" endRow="9" collapse="true"/>
<e:groupColumns startColumn="0" endColumn="9" collapse="false"/>
</e:worksheet>
</e:workbook>
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet
>
<e:rowPageBreak row="4"/>
</e:worksheet>
</e:workbook
>
|
属性
子要素
ファセット
|
<e:workbook>
<e:worksheet>
<e:mergeCells startRow="0" startColumn="0" endRow="9" endColumn="9"/>
</e:worksheet>
</e:workbook
>
<h:form id="theForm">
<h:dataTable id="theDataTable" value="#{personList.personList}"
var="person">
...
</h:dataTable>
</h:form>
これをMicrosoft® Excel®のスプレッドシートとして表示させたい場合は、
<h:commandLink
value="Export"
action="#{excelExporter.export('theForm:theDataTable')}"
/>
フォーマット処理については項19.14. 「フォントとレイアウト」を参照して下さい。
外部スタイルシートはe:linkタグによって参照されます。e:linkタグは、workbook要素の子要素となります。
|
属性
子要素
ファセット
|
<e:workbook>
<e:link URL="/css/excel.css"/>
</e:workbook
>
以下のXLS-CSS属性のグループは、フォントとその属性を定義します。
xls-font-family |
フォント名です。フォントがOSによってサポートされていることを確認して下さい。 |
xls-font-size |
フォントサイズです。値は数値です。 |
xls-font-color |
フォントの色です( jxl.format.Colour を参照して下さい)。 |
xls-font-bold |
フォントを強調文字にするかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
xls-font-italic |
フォントを斜体文字にするかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
xls-font-script-style |
フォントの上付きや下付きを設定します( jxl.format.ScriptStyle を参照して下さい。) |
xls-font-underline-style |
フォントの下線を設定します( jxl.format.UnderlineStyle を参照して下さい)。 |
xls-font-struck-out |
フォントに取り消し線をつけるかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
xls-font |
すべてのフォント設定を略記で行います。フォント名は'Times New Roman'のようにスペース区切りにして、記述の一番最後に置きます。"italic"や"bold"や"struckout"などを使用することができます。 Example style="xls-font: red bold italic 22 Verdana" |
This group of XLS-CSS attributes define the borders of the cell
xls-border-left-color |
セルの左ボーダーの色です( jxl.format.Colour を参照して下さい)。 |
xls-border-left-line-style |
セルの左ボーダーのスタイルです( jxl.format.LineStyle を参照して下さい)。 |
xls-border-left |
A shorthand for setting color and line style of the left edge of the cell, e.g style="xls-border-left: red thick" |
xls-border-top-color |
セルの上ボーダーの色です( jxl.format.Colour を参照して下さい)。 |
xls-border-top-line-style |
セルの上ボーダーのスタイルです( jxl.format.LineStyle を参照して下さい)。 |
xls-border-top |
A shorthand for setting color and line style of the top edge of the cell, e.g style="xls-border-left: red thick" |
xls-border-right-color |
セルの右ボーダーの色です( jxl.format.Colour を参照して下さい)。 |
xls-border-right-line-style |
セルの右ボーダーのスタイルです( jxl.format.LineStyle を参照して下さい)。 |
xls-border-right |
A shorthand for setting color and line style of the right edge of the cell, e.g style="xls-border-right: red thick" |
xls-border-bottom-color |
セルの下ボーダーの色です( jxl.format.Colour を参照して下さい)。 |
xls-border-bottom-line-style |
セルの下ボーダーのスタイルです( jxl.format.LineStyle を参照して下さい)。 |
xls-border-bottom |
A shorthand for setting color and line style of the bottom edge of the cell, e.g style="xls-border-bottom: red thick" |
xls-border |
A shorthand for setting color and line style all edges of the cell, e.g style="xls-border: red thick" |
This group of XLS-CSS attributes define the background of the cell
xls-background-color |
背景の色です( jxl.format.LineStyle )を参照して下さい。 |
xls-background-pattern |
背景のパターンです( jxl.format.Pattern を参照して下さい)。 |
xls-background |
背景の色とパターンの設定を略記で行います。記述のルールについては上を参照して下さい。 |
This group of XLS-CSS attributes define the column widths etc.
xls-column-width |
列幅です。最初は大きめの値(5000まで)を使って下さい。XHTMLモードで使用され、e:columnタグで適用されます。 |
xls-column-widths |
列幅です。最初は大きめの値(5000まで)を使って下さい。データテーブルの出力時に使用され、データテーブルのスタイル属性で適用されます。数値、あるいは、元の設定を有効にするためには"*"を指定して下さい。 Example style="xls-column-widths: 5000, 5000, *, 10000" |
xls-column-autosize |
列幅の自動調整を行うかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
xls-column-hidden |
列を非表示にするかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
This group of XLS-CSS attributes define the cell properties
xls-alignment |
セル値の水平方向の配置を設定します( jxl.format.Alignment を参照して下さい)。 |
xls-force-type |
セルのデータ型を強制します。値は文字列で、"general"、"number"、"text"、"date"、"formula"、"bool"のどれかです。データ型は自動的に検出されるので、この属性を使うことはまれです。 |
xls-format-mask |
セルの書式マスクを設定します。項19.6.2. 「書式マスク」を参照して下さい。 項19.6.2. 「書式マスク」 |
xls-indentation |
セル値のインデントを設定します。値は数値です。 |
xls-locked |
セルをロックするかどうかを設定します。ブックレベルでのロックとともに使用します。"true"もしくは"false"を設定して下さい。 |
xls-orientation |
セル値の方向を設定します( jxl.format.Orientation を参照して下さい)。 |
xls-vertical-alignment |
セル値の垂直方向の配置を設定します( jxl.format.VerticalAlignment を参照して下さい)。 |
xls-shrink-to-fit |
セル値をセルに合わせて縮小するかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
xls-wrap |
セル値を折り返すかどうかを設定します。"true"もしくは"false"を設定して下さい。 |
YARFRAWライブラリを通してSeamにRSSフィードを統合する事は、今では容易に行えます。RSSサポートは現在、最新のリリースにおいて"テクノロジ プレビュー"の状態にあります。
RSSサポートを可能にするためには、あなたのアプリケーションのWEB-INF/lib
ディレクトリにjboss-seam-rss.jar
を含めるようにして下さい。RSSライブラリには、依存関係にあるいくつかのライブラリもあり、それらのライブラリはRSSライブラリと同じディレクトリに置かなければなりません。含めなければならないライブラリの一覧は項40.2.6. 「Seam RSS サポート」を参照して下さい。
Seam RSSサポートは、ビュー テクノロジとしてFaceletsの利用を必要とします。
フィードは、フィードと入れ子になったエントリ項目のリストから成るxhtmlページです。
<r:feed
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:r="http://jboss.com/products/seam/rss"
title="#{rss.feed.title}"
uid="#{rss.feed.uid}"
subtitle="#{rss.feed.subtitle}"
updated="#{rss.feed.updated}"
link="#{rss.feed.link}">
<ui:repeat value="#{rss.feed.entries}" var="entry">
<r:entry
uid="#{entry.uid}"
title="#{entry.title}"
link="#{entry.link}"
author="#{entry.author}"
summary="#{entry.summary}"
published="#{entry.published}"
updated="#{entry.updated}"
/>
</ui:repeat>
</r:feed>
フィードは、情報源の属性を説明する最上位のエンティティです。それは、0層以上の入れ子となったエントリを含みます。
|
属性
子要素
ファセット
|
|
属性
子要素
ファセット
|
Seam には 電子メールの送信およびテンプレート作成用のオプションコンポーネントが含まれるようになります。
電子メールのサポートは jboss-seam-mail.jar
により提供されます。 この JAR にはメールの作成に使用されるメール JSF コントロールおよび mailSession
管理コンポーネントが含まれます。
examples/mail プロジェクトには実行可能なデモ用電子メールサポートのサンプルが含まれています。 正しいパッケージングの方法を行い、 また現在サポートされている主要な機能を実際に示すサンプルがいくつか含まれています。
Seamの統合テスト環境で電子メールの動作をテストする事ができます。 項34.3.4. 「Seamメールの統合テスト」参照
Seamの電子メール機能はFaceletsを利用して記述しているので、新たなテンプレート用の言語を学ぶ必要はありません。
<m:message xmlns="http://www.w3.org/1999/xhtml"
xmlns:m="http://jboss.com/products/seam/mail"
xmlns:h="http://java.sun.com/jsf/html">
<m:from name="Peter" address="peter@example.com" />
<m:to name="#{person.firstname} #{person.lastname}"
>#{person.address}</m:to>
<m:subject
>Try out Seam!</m:subject>
<m:body>
<p
><h:outputText value="Dear #{person.firstname}" />,</p>
<p
>You can try out Seam by visiting
<a href="http://labs.jboss.com/jbossseam"
>http://labs.jboss.com/jbossseam</a
>.</p>
<p
>Regards,</p>
<p
>Pete</p>
</m:body>
</m:message
>
<m:body>
は email のボディを囲みます。 HTML 正規タグをボディ内や JSF コンポーネント内に使用することができます。
@In(create=true)
private Renderer renderer;
public void send() {
try {
renderer.render("/simple.xhtml");
facesMessages.add("Email sent successfully");
}
catch (Exception e) {
facesMessages.add("Email sending failed: " + e.getMessage());
}
}
たとえば、 無効な電子メールアドレスを入力すると例外が投げられ、 その例外がキャッチされてユーザーに表示されます。
Seam では電子メールへのファイル添付が容易になっています。 ファイルを操作する際に使用される標準 java タイプのほとんどに対応しています。
jboss-seam-mail.jar
に電子メール 送信をしたい場合、
<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar"/>
<m:attachment value="/WEB-INF/lib/jboss-seam-mail.jar" fileName="this-is-so-cool.jar"/>
java.io.File
, java.net.URL
を添付することもできます。
<m:attachment value="#{numbers}"/>
または、 byte[]
あるいは java.io.InputStream
<m:attachment value="#{person.photo}" contentType="image/png"/>
コード例から、byte[]
やjava.io.InputStream
を使うためには、添付ファイルのMIMEタイプを指定する必要があることが分かると思います。
さらに便利なことに、 使用する通常のタグの前後を <m:attachment>
で囲むだけで Seam 生成 PDF や標準 JSF ビュー を添付することができます。
<m:attachment fileName="tiny.pdf">
<p:document
>
A very tiny PDF
</p:document>
</m:attachment
>
添付したいファイル一式が手元にある場合 (例、 データベースからロードした写真一式)、 <ui:repeat>
を使うだけで添付できます。
<ui:repeat value="#{people}" var="person">
<m:attachment value="#{person.photo}" contentType="image/jpeg" fileName="#{person.firstname}_#{person.lastname}.jpg"/>
</ui:repeat
>
また、添付のイメージファイルをインラインで表示したい場合には
<m:attachment
value="#{person.photo}"
contentType="image/jpeg"
fileName="#{person.firstname}_#{person.lastname}.jpg"
status="personPhoto"
disposition="inline" />
<img src="cid:#{personPhoto.contentId}" />
現在ではほとんどのメールリーダーがHTMLをサポートしていますが、一部でサポートしていないメールリーダーもありますので、メール本体にプレーンなテキストを追加する事もできます。
<m:body>
<f:facet name="alternative"
>Sorry, your email reader can't show our fancy email,
please go to http://labs.jboss.com/jbossseam to explore Seam.</f:facet>
</m:body
>
複数の受信者が属するグループに対して電子メールを送信したい場合には、すべての受信者のメールタグを繰り返しタグ<ui:repeat>
の中に置くことができます。
<ui:repeat value="#{allUsers} var="user">
<m:to name="#{user.firstname} #{user.lastname}" address="#{user.emailAddress}" />
</ui:repeat
>
ただし、 若干異なる内容のメッセージを各受信者に送信する必要がある場合もあります (パスワードのリセットなど)。 最適な方法としては、 メッセージ全体を <ui:repeat>
内に配置することです。
<ui:repeat value="#{people}" var="p">
<m:message>
<m:from name="#{person.firstname} #{person.lastname}"
>#{person.address}</m:from>
<m:to name="#{p.firstname}"
>#{p.address}</m:to>
...
</m:message>
</ui:repeat
>
The mail templating example shows that facelets templating Just Works with the Seam mail tags.
jboss.org の template.xhtml
には次の内容が含まれています。
<m:message>
<m:from name="Seam" address="do-not-reply@jboss.com" />
<m:to name="#{person.firstname} #{person.lastname}"
>#{person.address}</m:to>
<m:subject
>#{subject}</m:subject>
<m:body>
<html>
<body>
<ui:insert name="body"
>This is the default body, specified by the template.</ui:insert>
</body>
</html>
</m:body>
</m:message
>
jboss.org の templating.xhtml
には次の内容が含まれています。
<ui:param name="subject" value="Templating with Seam Mail"/>
<ui:define name="body">
<p
>This example demonstrates that you can easily use <i
>facelets templating</i
> in email!</p>
</ui:define
>
メール送信時にFaceletsやJSFの設定をさらにしておきたい場合には、レンダラーコンポーネントをオーバーライドして、プログラムで設定するようにします。-上級ユーザー用。
Seam は国際化メッセージの送信に対応しています。 デフォルトでは、 JSF で提供されるエンコーディングが使用されますが、 テンプレートで上書きすることができます。
<m:message charset="UTF-8">
...
</m:message
>
本文、タイトルと受信者名はエンコードされます。 テンプレートのエンコーディングを設定して、Faceletsがメールを指定したページをパースする際に正しい文字セットを確実に指定する事が必要になります。
<?xml version="1.0" encoding="UTF-8"?>
場合により、前述以外のヘッダー情報を追加したい場合があると思いますが、Seamはこれらのいくつかをサポートしています(項21.5. 「タグ」参照)。 例として、メールの重要度の設定や、受信者の受取確認の要求等を設定する事ができます。
<m:message xmlns:m="http://jboss.com/products/seam/mail"
importance="low"
requestReadReceipt="true"/>
Otherise you can add any header to the message using the <m:header>
tag:
<m:header name="X-Sent-From" value="JBoss Seam"/>
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="mailServer", propertyValue="localhost"),
@ActivationConfigProperty(propertyName="mailFolder", propertyValue="INBOX"),
@ActivationConfigProperty(propertyName="storeProtocol", propertyValue="pop3"),
@ActivationConfigProperty(propertyName="userName", propertyValue="seam"),
@ActivationConfigProperty(propertyName="password", propertyValue="seam")
})
@ResourceAdapter("mail-ra.rar")
@Name("mailListener")
public class MailListenerMDB implements MailListener {
@In(create=true)
private OrderProcessor orderProcessor;
public void onMessage(Message message) {
// Process the message
orderProcessor.process(message.getSubject());
}
}
You can find more information onmail-ra.rar
at http://wiki.jboss.org/wiki/Wiki.jsp?page=InboundJavaMail.
JBoss ASをアプリケーションサーバーとして使用していない場合にも、mail-ra.rar
を使用する事はできますし、お使いのアプリケーションサーバーが同様の機能を提供しているはずです。
mailSession
コンポーネントはSMTPサーバと通信するときにJavaMailを使用しています。
JEE 環境で作業している、 または Seam 設定のSessionオブジェクトを使用できる場合、 JNDI ルックアップからjavaMailのSessionオブジェクトが使用できます。
mailSessionコンポーネントのプロパティの詳細は項31.8. 「メール関連のコンポーネント」を参照してください。
JBossAS deploy/mail-service.xml
は JNDI にバインドする JavaMailのSessionクラスを設定します。 デフォルトのサービス設定は使用しているネットワークに応じて変更が必要となります。 サービスについての詳細は http://wiki.jboss.org/wiki/Wiki.jsp?page=JavaMail で説明されています。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:mail="http://jboss.com/products/seam/mail">
<mail:mail-session session-jndi-name="java:/Mail"/>
</components
>
ここで Seam に JNDI から java:/Mail
に送られるmailSessionを取得するよう指示します。
mailSessionはcomponents.xml
で設定する事ができます。 ここでは、smtp.example.com
によりSeamがsmtpサーバを使用するように指示しています。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:mail="http://jboss.com/products/seam/mail">
<mail:mail-session host="smtp.example.com"/>
</components
>
Meldwareをメールサーバとして使用したSeamのメールの例です(buni.orgより)。 MeldwareはSMTP
, POP3
, IMAP
、webmail、共有カレンダー、グラフィカルな管理ツールを提供するグループウェアで、JEEアプリケーションとして書かれているので、Seamアプリケーションと共にJBoss ASにデプロイする事ができます。
Seamと共に配布されているMeldwareのバージョンは開発用のバージョンでメールボックス、ユーザー、メールアドレス(アライアス)等がアプリケーションがデプロイされる度に新規に作られます。 製品にMeldwareを使用するのでしたら、buni.orgから最新のバージョンをダウンロードしてインストールしてください。
電子メール に受信者を追加します。 受信者が複数の場合は複数の <m:to> タグを使用します。 このタグは <ui:repeat>. などの繰り返しタグ内に問題なく配置できます。
email に CC の受信者を追加します。 CC が複数の場合は複数の <m:cc> タグを使用します。 このタグは <ui:repeat> などの繰り返しタグ内に問題なく配置できます。
email に BCC の受信者を追加します。 BCC が複数の場合は複数の <m:bcc> タグを使用します。 このタグは <ui:repeat> などの繰り返しタグ内に問題なく配置できます。
メールの本文の設定。 alternative
ファセットによりHTMLメールを作成する際、htmlメールをサポートしていないメールリーダのために、テキスト版を含めることができます。
Seam makes it very easy to perform work asynchronously from a web request. When most people think of asynchronicity in Java EE, they think of using JMS. This is certainly one way to approach the problem in Seam, and is the right way when you have strict and well-defined quality of service requirements. Seam makes it easy to send and recieve JMS messages using Seam components.
But for many usecases, JMS is overkill. Seam layers a simple asynchronous method and event facility over your choice of dispatchers:
java.util.concurrent.ScheduledThreadPoolExecutor
(デフォルト)
EJB タイマーサービス (EJB 3.0 環境向け)
Quartz
<async:timer-service-dispatcher/>
<async:quartz-dispatcher/>
EJBコンポーネントでは、ローカルインタフェースをアノテートしてメソッドが非同期に処理されるよう指定します。
@Local
public interface PaymentHandler
{
@Asynchronous
public void processPayment(Payment payment);
}
(JavaBean コンポーネントでは、 望むならコンポーネントの実装クラスをアノテートすることができます)
@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
public void processPayment(Payment payment)
{
//do some work!
}
}
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String pay()
{
paymentHandler.processPayment( new Payment(bill) );
return "success";
}
}
非同期メソッドは完全に新規のイベントコンテキストで処理され、 呼び出し側のセッションまたは対話コンテキストの状態にはアクセスできません。 しかしビジネスプロセスコンテキストは伝播されます。
非同期メソッド呼び出しは@Duration
、@Expiration
、 @IntervalDuration
アノテーションを使って、 後の実行のためにスケジューリングできます。
@Local
public interface PaymentHandler
{
@Asynchronous
public void processScheduledPayment(Payment payment, @Expiration Date date);
@Asynchronous
public void processRecurringPayment(Payment payment,
@Expiration Date date,
@IntervalDuration Long interval)'
}
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String schedulePayment()
{
paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
return "success";
}
public String scheduleRecurringPayment()
{
paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(),
ONE_MONTH );
return "success";
}
}
@Local
public interface PaymentHandler
{
@Asynchronous
public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}
@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
@In Timer timer;
public Timer processScheduledPayment(Payment payment, @Expiration Date date)
{
//do some work!
return timer; //note that return value is completely ignored
}
}
@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String schedulePayment()
{
Timer timer = paymentHandler.processScheduledPayment( new Payment(bill),
bill.getDueDate() );
return "success";
}
}
@FinalExpiration
アノテーションは反復タスクの終了日を指定します。 QuartzTriggerHandle
のインジェクトが可能なので注意してください。
@In QuartzTriggerHandle timer;
// Defines the method in the "processor" component
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalDuration Long interval,
@FinalExpiration Date endDate,
Payment payment)
{
// do the repeating or long running task until endDate
}
... ...
// Schedule the task in the business logic processing code
// Starts now, repeats every hour, and ends on May 10th, 2010
Calendar cal = Calendar.getInstance ();
cal.set (2010, Calendar.MAY, 10);
processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);
QuartzTriggerHandle handle =
processor.schedulePayment(payment.getPaymentDate(),
payment.getPaymentCron(),
payment);
payment.setQuartzTriggerHandle( handle );
// Save payment to DB
// later ...
// Retrieve payment from DB
// Cancel the remaining scheduled tasks
payment.getQuartzTriggerHandle().cancel();
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalCron String cron,
Payment payment)
{
// do the repeating or long running task
}
... ...
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle =
processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalBusinessDay NthBusinessDay nth,
Payment payment)
{
// do the repeating or long running task
}
... ...
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle =
processor.schedulePayment(new Date(),
new NthBusinessDay(2, "14:00", WEEKLY), payment);
public class NthBusinessDay implements Serializable
{
int n;
String fireAtTime;
List <Date
> additionalHolidays;
BusinessDayIntervalType interval;
boolean excludeWeekends;
boolean excludeUsFederalHolidays;
public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }
public NthBusinessDay ()
{
n = 1;
fireAtTime = "12:00";
additionalHolidays = new ArrayList <Date
> ();
interval = BusinessDayIntervalType.WEEKLY;
excludeWeekends = true;
excludeUsFederalHolidays = true;
}
... ...
}
@Scope(ScopeType.STATELESS)
@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler extends AsynchronousExceptionHandler {
@Logger Log log;
@In Future timer;
@Override
public void handleException(Exception exception) {
log.debug(exception);
timer.cancel(false);
}
}
public void handleAsynchronousException(Exception exception) {
log.fatal(exception);
}
Seam は Seam コンポーネントの JMS メッセージの送受信を容易にしています。
JMS TopicPublisher
や、 TopicSession
をコンポーネントにインジェクトすることが可能です。
@In
private TopicPublisher stockTickerPublisher;
@In
private TopicSession topicSession;
public void publish(StockPrice price) {
try
{
stockTickerPublisher.publish( topicSession.createObjectMessage(price) );
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
@In
private QueueSender paymentQueueSender;
@In
private QueueSession queueSession;
public void publish(Payment payment) {
try
{
paymentQueueSender.send( queueSession.createObjectMessage(payment) );
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
In almost all enterprise applications, the database is the primary bottleneck, and the least scalable tier of the runtime environment. People from a PHP/Ruby environment will try to tell you that so-called "shared nothing" architectures scale well. While that may be literally true, I don't know of many interesting multi-user applications which can be implemented with no sharing of resources between different nodes of the cluster. What these silly people are really thinking of is a "share nothing except for the database" architecture. Of course, sharing the database is the primary problem with scaling a multi-user application—so the claim that this architecture is highly scalable is absurd, and tells you a lot about the kind of applications that these folks spend most of their time working on.
データベースの共有をなるべく少なくできる方法があれば、すべて実行する価値があります。
そこでキャッシュの登場です。しかも1種類ではありません。 Seamアプリケーションを正しく設計すれば、何層にもわたる豊富なキャッシング戦略をアプリケーションのすべての層で利用することができるのです。
データベースは当然ながら独自のキャッシュを持っています。このことはたいへん重要ですが、アプリケーション層のキャッシュのような拡張性はありません。
ORMソリューション(Hibernateやその他のJPA実装など)はデータベースからデータを2次キャッシュに置きます。 これはとても強力な機能なのですが、間違った使われ方をされがちです。 トランザクションのキャッシュデータをクラスタ環境の全ノードで一貫性を持たせ、データベースとも同期させると、非常に重い処理になります。 複数ユーザーで共有され、更新がまれなデータには良いかも知れません。 一般的なステートレスなアーキテクチャでは、2次キャッシュに対話状態をキャッシュしようとしますが、これは良いことではありません。 特にSeamの場合は、誤りです。
Seamの対話コンテキストは、対話状態のキャッシュです。 対話コンテキストに保存したコンポーネントは、ユーザーのインタラクションに関連した状態をキャッシュし、保持します。
特に、Seamが管理する永続コンテキスト(あるいは対話スコープのステートフルセッションBeanに関連付けられたEJBコンテナ管理の拡張永続コンテキスト)は、現在の対話に読み込まれたデータのキャッシュとして振舞います。このキャッシュのヒット率は通常はとても高くなります!クラスタ環境では、Seamが管理する永続コンテキストはSeamによってレプリケーションが最適化され、データベースのトランザクションの一貫性を気にする必要はありません(楽観的ロックで充分です)。一つの永続コンテキストに何千ものオブジェクトを読み込まない限り、このキャッシュの効率についてあまり気にする必要はありません。
トランザクションに関連しない状態をSeamのアプリケーションコンテキストにキャッシュすることもできます。 アプリケーションコンテキストに保持された状態は、クラスタ内の他のノードにはもちろん見えません。
トランザクションの状態は、JBossCacheやJBoss POJO Cache、EHCacheなどを利用するSeamのcacheProvider
コンポーネントにキャッシュできます。 これらのキャッシュがクラスタモードをサポートしていれば、この状態は他のノードにも見えます。
最後に、レンダリングされたJSFページの断片をキャッシュすることができます。 ORMソリューションの2次キャッシュと違い、データが変更されても自動的に無効になることはないので、 明示的に無効化するアプリケーションコードを書くか、適切な有効期限ポリシーを設定する必要があります。
2次キャッシュは非常に複雑な概念ですので、詳細についてはお使いのORMソリューションの文書を参照してください。この章では、cacheProvider
コンポーネントや<s:cache>
によるページ断片のキャッシュなどを直接利用する方法について説明します。
組み込みのcacheProvider
コンポーネントは以下のインスタンスを管理します。
cacheProvider
を使うには、使用するキャッシュ実装の以下のjarファイルをプロジェクトに含めてください。
SeamにEARをデプロイする場合は、キャッシュのjarファイルと設定ファイルをEARに直接含めることをお勧めします。
JBossCacheを使う場合はさらに設定ファイルが必要です。treecache.xml
に適切なキャッシュ設定を記述し、クラスパスに含めます(たとえばEJB JARファイルやWEB-INF/classes
など)。JBossCacheには恐ろしく厄介で紛らわしい設定がたくさんあるので、ここでは説明しません。詳細はJBossCacheの文書を参照してください。
treecache.xml
のサンプルはexamples/blog/resources/treecache.xml
にあります。
EHCacheは設定ファイルがなくてもデフォルトの設定で動作します。
設定ファイルを変更するには、components.xml
のキャッシュ設定を変更してください。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider configuration="META-INF/cache/treecache.xml" />
</components
>
Seamコンポーネントにキャッシュをインジェクトするには以下のように記述します。
@Name("chatroom")
public class Chatroom {
@In CacheProvider cacheProvider;
public void join(String username) {
Set<String
> userList = (Set<String
>) pojoCache.get("chatroom", "userList");
if (userList==null) {
userList = new HashSet<String
>();
cacheProvider.put("chatroom", "userList", userList);
}
userList.put(username);
}
}
キャッシュを複数設定する場合は、components.xml
を使用してください。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider name="myCache" configuration="myown/cache.xml"/>
<cache:jboss-cache-provider name="myOtherCache" configuration="myother/cache.xml"/>
</components
>
<s:cache>
は、あまり変更されないレンタリングコンテンツに使用してください。 たとえば、最新のblogエントリを表示するblogのウェルカムページです。
<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
<h:dataTable value="#{blog.recentEntries}" var="blogEntry">
<h:column>
<h3
>#{blogEntry.title}</h3>
<div>
<s:formattedText value="#{blogEntry.body}"/>
</div>
</h:column>
</h:dataTable>
</s:cache
>
public void post() {
...
entityManager.persist(blogEntry);
cacheProvider.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}
あるいは、変更を即座にユーザーに見せる必要がないのであれば、JBossCacheノードの有効期限を短く設定しても良いでしょう。
SeamとJBossWSを統合することで、標準のJEE のWebサービスにたいして、対話型Webサービスにも対応したSeamのコンテキストフレームワークを十分に活用することができます。本章では、Seam環境でWebサービスが動作するのに必要な手順を説明します。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:seam="http://seambay.example.seam.jboss.org/">
<soapenv:Header>
<seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
</soapenv:Header>
<soapenv:Body>
<seam:confirmAuction/>
</soapenv:Body>
</soapenv:Envelope
>
<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
<env:Header>
<seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
</env:Header>
<env:Body>
<confirmAuctionResponse xmlns="http://seambay.example.seam.jboss.org/"/>
</env:Body>
</env:Envelope
>
@Stateless
@WebService(name = "AuctionService", serviceName = "AuctionService")
public class AuctionService implements AuctionServiceRemote
{
@WebMethod
public boolean login(String username, String password)
{
Identity.instance().setUsername(username);
Identity.instance().setPassword(password);
Identity.instance().login();
return Identity.instance().isLoggedIn();
}
// snip
}
もう一つの例を見てみましょう。このWebサービスメソッドは、AuctionAction.createAuction()
メソッドに委譲されることで新しい対話が始まります。
@WebMethod
public void createAuction(String title, String description, int categoryId)
{
AuctionAction action = (AuctionAction) Component.getInstance(AuctionAction.class, true);
action.createAuction();
action.setDetails(title, description, categoryId);
}
@Begin
public void createAuction()
{
auction = new Auction();
auction.setAccount(authenticatedAccount);
auction.setStatus(Auction.STATUS_UNLISTED);
durationDays = DEFAULT_AUCTION_DURATION;
}
これにより、Webサービスがファサードとして実際の作業を対話型Seamコンポーネントに委譲することで、長い対話を続けていることが判ります。
SeamはJAX-RS 仕様(JSR 311)にあるRESTEasyの実装を組み込んでいます。これをSeamアプリケーションのどこまで”深く”取り入れるかは、以下のように自分で設定することができます。
@Path("/customer")
public class MyCustomerResource {
@GET
@Path("/{customerId}")
@ProduceMime("text/plain")
public String getCustomer(@PathParam("customerId") int id) {
return ...;
}
}
<components
xmlns="http://jboss.com/products/seam/components"
xmlns:resteasy="http://jboss.com/products/seam/resteasy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
http://jboss.com/products/seam/resteasy
http://jboss.com/products/seam/resteasy-2.1.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.1.xsd"
>
次に、先に述べたように/rest
プレフィックスを変更できます。
<resteasy:application-config resource-path-prefix="/restv1"/>
リソース中においてフルパスでマップしたい場合は、ベースパスのストリッピングを無効にすることができます。
<resteasy:application-config strip-seam-resource-path="false"/>
<resteasy:application-config
scan-providers="false"
scan-resources="false"
use-builtin-providers="true">
<resteasy:resource-class-names>
<value
>org.foo.MyCustomerResource</value>
<value
>org.foo.MyOrderResource</value>
</resteasy:resource-class-names>
<resteasy:provider-class-names>
<value
>org.foo.MyFancyProvider</value>
</resteasy:provider-class-names>
</resteasy:application-config
>
<resteasy:application-config>
<resteasy:media-type-mappings>
<key
>txt</key
><value
>text/plain</value>
</resteasy:media-type-mappings>
<resteasy:language-mappings>
<key
>deutsch</key
><value
>de-DE</value>
</resteasy:language-mappings>
</resteasy:application-config
>
@Name("customerResource")
@Path("/customer")
public class MyCustomerResource {
@In
CustomerDAO customerDAO;
@GET
@Path("/{customerId}")
@ProduceMime("text/plain")
public String getCustomer(@PathParam("customerId") int id) {
return customerDAO.find(id).getName();
}
}
対話スコープのリソースコンポーネントと対話のマッピングは現在サポートされていませんが、間もなく利用できるようになる予定です。
プロバイダのクラスもまたSeamコンポーネントとして利用できますが、APPLICATION
-scopedもしくはSTATELESS
である必要があります。
Seam は、 Web ページから AJAX (Asynchronous Javascript and XML) を使用してコンポーネントにリモートアクセスする便利な方法を提供します。 この機能を実現する Seam では、 開発時に労力がかからないようになっています - コンポーネントに必要なものは、 AJAX を通じてアクセス可能とするための単純なアノテーションだけです。 この章では、 AJAX 可能な Web ページを作るために必要なステップについて述べ、 そしてSeam Remoting フレームワークの機能についても詳しく説明します。
リモーティングの機能を使用するには、まずweb.xml
ファイル内でSeam Resourceサーブレットを設定する必要があります。
<servlet>
<servlet-name
>Seam Resource Servlet</servlet-name>
<servlet-class
>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name
>Seam Resource Servlet</servlet-name>
<url-pattern
>/seam/resource/*</url-pattern>
</servlet-mapping
>
<script type="text/javascript" src="seam/resource/remoting/resource/remote.js"
></script
>
<script type="text/javascript"
src="seam/resource/remoting/interface.js?customerAction"
></script
>
同じページから一つ以上のコンポーネントにアクセスしたい場合は、スクリプトタグのパラメータとしてそれらをすべて含めます。
<script type="text/javascript"
src="seam/resource/remoting/interface.js?customerAction&accountAction"
></script
>
代わりに、 必要な Javascript のインポートに s:remote
を使用することもできます。 インポートするコンポーネントあるいはクラス名をそれぞれコンマで区切ります。
<s:remote include="customerAction,accountAction"/>
@Stateless
@Name("helloAction")
public class HelloAction implements HelloLocal {
public String sayHello(String name) {
return "Hello, " + name;
}
}
新しいコンポーネント用にローカルインタフェースも生成する必要があります。 @WebRemote
アノテーションに特に注意してください。 リモートによるメソッドへのアクセスを可能とするために必要です。
@Local
public interface HelloLocal {
@WebRemote
public String sayHello(String name);
}
書く必要があるサーバサイドのコードはこれだけです。 それでは、WEB ページのために - 新しいページを作成して、 helloAction
コンポーネントをインポートしましょう。
<s:remote include="helloAction"/>
ユーザーにとって完全にインテラクティブとなるようページにボタンを追加してみます。
<button onclick="javascript:sayHello()"
>Say Hello</button
>
ボタンをクリックしたとき、実際にボタンに何かを行わせるためのスクリプトをもう少し追加する必要があります。
<script type="text/javascript">
//<![CDATA[
function sayHello() {
var name = prompt("What is your name?");
Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
}
function sayHelloCallback(result) {
alert(result);
}
// ]]>
</script
>
Seam.Component.getInstance("helloAction").sayHello(name, sayHelloCallback);
sayHelloCallback
メソッドが、リモート要求に対するレスポンスを受信した場合、 メソッド呼び出しの結果を表示するアラートメッセージが現れます。
@Name("customer")
@Entity
public class Customer implements Serializable
{
private Integer customerId;
private String firstName;
private String lastName;
@Column public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId} {
this.customerId = customerId;
}
@Column public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
クライアントサイド Customer を生成するために、以下のコードを記述します。
var customer = Seam.Component.newInstance("customer");
そして、ここからは customer オブジェクトのフィールドの設定が可能です。
customer.setFirstName("John");
// Or you can set the fields directly
customer.lastName = "Smith";
function customersCallback(customers) {
for (var i = 0; i < customers.length; i++) {
alert("Got customer: " + customers[i].getName());
}
}
Seam.Remoting.eval("#{customers}", customersCallback);
<s:remote include="customer"/>
<script type="text/javascript"
src="seam/resource/remoting/interface.js?customerAction"
></script
>
<s:remote include="customerAction"/>
@Name("myAction")
public class MyAction implements MyActionLocal {
public void doSomethingWithObject(Object obj) {
// code
}
}
<s:remote include="myAction,myWidget"/>
@Name("paintAction")
public class paintAction implements paintLocal {
public enum Color {red, green, blue, yellow, orange, purple};
public void paint(Color color) {
// code
}
}
paint()
メソッドを red
の色を使って呼び出すには、 String のままでパラメータ値を渡します。
Seam.Component.getInstance("paintAction").paint("red");
Seam.Remoting.setDebug(true);
components.xml を使って設定を行う方法は次のようになります。
<remoting:remoting debug="true"/>
画面の上部右端にデフォルトで表示されるローディングメッセージは、 変更、 レンダリングのカスタマイズ、 完全にオフにするなどが可能です。
デフォルトの "Please Wait..." というメッセージを別のメッセージに変更するには、 Seam.Remoting.loadingMessage
の値を設定します。
Seam.Remoting.loadingMessage = "Loading...";
ローディングメッセージを完全に表示させないようにするには、 displayLoadingMessage()
および hideLoadingMessage()
を何も行わない機能で上書きします。
// don't display the loading indicator
Seam.Remoting.displayLoadingMessage = function() {};
Seam.Remoting.hideLoadingMessage = function() {};
@Name("widget")
public class Widget
{
private String value;
private String secret;
private Widget child;
private Map<String,Widget> widgetMap;
private List<Widget> widgetList;
// getters and setters for all fields
}
リモートメソッドが Widget
のインスタンスを返すけれど secret
フィールドには機密情報が含まれているため公開したくない場合、 次のように制約します。
@WebRemote(exclude = {"secret"})
public Widget getWidget();
@WebRemote(exclude = {"child.secret"})
public Widget getWidget();
@WebRemote(exclude = {"widgetList.secret"})
public Widget getWidget();
@WebRemote(exclude = {"widgetMap[value].secret"})
public Widget getWidget();
次の例では JMS Topic へのサブスクライブ方法を示しています。
function subscriptionCallback(message)
{
if (message instanceof Seam.Remoting.TextMessage)
alert("Received message: " + message.getText());
}
Seam.Remoting.subscribe("topicName", subscriptionCallback);
トピックのサブスクライブを中止するには、 Seam.Remoting.unsubscribe()
を呼び出してトピック名で渡します。
Seam.Remoting.unsubscribe("topicName");
こういった動的なAjaxアプリケーションをGoogle Web Toolkit (GWT)を使って開発したいときのために、SeamはGWTウィジェットが直接Seamコンポーネントと連携できる統合レイヤを用意しています。
GWTを使う上でGWT toolsについては既に知っていることを前提にしています。より細かい情報については http://code.google.com/webtoolkit/を参照ください。この章ではGWTがどう機能し、どのように使うかといったところについては説明しません。
SeamアプリケーションでGWTを使う場合に特別な設定は必要ではありません、しかしSeamリソースサーブレットが動作するように設定されていなければなりません。詳細は章 29. Seam の設定と Seam アプリケーションのパッケージングを参照ください。
public interface MyService extends RemoteService {
public String askIt(String question);
}
非同期インタフェースは一意であるべきですが、AsyncCallback
パラメタを各メソッドで宣言している場合は除きます。
public interface MyServiceAsync extends RemoteService {
public void askIt(String question, AsyncCallback callback);
}
このMyServiceAsync
のサンプルのような非同期インタフェースはGWTで実装され、直接的に実装することはありません。
次のステップでは同期インタフェースを実装するSeamコンポーネントを作成します。
@Name("org.jboss.seam.example.remoting.gwt.client.MyService")
public class ServiceImpl implements MyService {
@WebRemote
public String askIt(String question) {
if (!validate(question)) {
throw new IllegalStateException("Hey, this shouldn't happen, I checked on the client, " +
"but its always good to double check.");
}
return "42. Its the real question that you seek now.";
}
public boolean validate(String q) {
ValidationUtility util = new ValidationUtility();
return util.isValid(q);
}
}
次のステップは、コンポーネントに非同期インタフェースを返すメソッドを書きます。このメソッドはウィジェットクラスで用意し、非同期クライアントのスタブへの参照を取得するために利用されます。
private MyServiceAsync getService() {
String endpointURL = GWT.getModuleBaseURL() + "seam/resource/gwt";
MyServiceAsync svc = (MyServiceAsync) GWT.create(MyService.class);
((ServiceDefTarget) svc).setServiceEntryPoint(endpointURL);
return svc;
}
最後のステップはクライアントスタブのメソッドを呼び出すウィジェットのコードを書きます。次のサンプルではラベルとテキスト入力フィールドとボタンで構成される画面を作成します。
public class AskQuestionWidget extends Composite {
private AbsolutePanel panel = new AbsolutePanel();
public AskQuestionWidget() {
Label lbl = new Label("OK, what do you want to know?");
panel.add(lbl);
final TextBox box = new TextBox();
box.setText("What is the meaning of life?");
panel.add(box);
Button ok = new Button("Ask");
ok.addClickListener(new ClickListener() {
public void onClick(Widget w) {
ValidationUtility valid = new ValidationUtility();
if (!valid.isValid(box.getText())) {
Window.alert("A question has to end with a '?'");
} else {
askServer(box.getText());
}
}
});
panel.add(ok);
initWidget(panel);
}
private void askServer(String text) {
getService().askIt(text, new AsyncCallback() {
public void onFailure(Throwable t) {
Window.alert(t.getMessage());
}
public void onSuccess(Object data) {
Window.alert((String) data);
}
});
}
...
このサンプルの完全なコードはSeamのインストールディレクトリ内の examples/remoting/gwt
ディレクトリにあります。
<taskdef uri="antlib:de.samaflost.gwttasks"
resource="de/samaflost/gwttasks/antlib.xml"
classpath="./lib/gwttasks.jar"/>
<property file="build.properties"/>
その内容を持つ、build.properties
ファイルを作成します。
gwt.home=/gwt_home_dir
もちろん、これはGWTがインストールされた場所を直接指しています。次にターゲットを作成します。
<!-- GWT�zg�)j��ƣ�ƣgY.
GWT�F4oS6gYLGWT�%k&����Y�ŁLB�~Y -->
<target name="gwt-compile">
<!-- Sn4 GWT�� "re homing" WfD~Yng
GWT����`QcfD~Y - URL�����kY�_�kLjcfD~Y -->
<delete>
<fileset dir="view"/>
</delete>
<gwt:compile outDir="build/gwt"
gwtHome="${gwt.home}"
classBase="${gwt.module.name}"
sourceclasspath="src"/>
<copy todir="view">
<fileset dir="build/gwt/${gwt.module.name}"/>
</copy>
</target
>
Spring 統合モジュールにより Seam への Spring ベースとするプロジェクトの移植が容易になり、 Spring アプリケーションは対話や Seam の高度な永続コンテキスト管理など Seam の主要な機能を利用することができるようになります。
注意! Spring 統合コードは jboss-seam-ioc ライブラリに含まれています。 この依存性は本章に記載されているすべての seam-spring 統合技術に必要となります。
Spring の Seam サポートは次のような機能を提供します。
Seam コンポーネントインスタンスを Spring Bean にインジェクトする
Spring Bean を Seam コンポーネントにインジェクトする
Spring Bean を Seam コンポーネントに変換する
Spring Bean を Seam コンテキストに配置できるようにする
Seam コンポーネント で Spring Web アプリケーションを起動する
Spring PlatformTransactionManagement のサポート
Spring の OpenEntityManagerInViewFilter
および OpenSessionInViewFilter
の代替として Seam 管理を提供します。
@Asynchronous
コールに対応する Spring TaskExecutors
のサポート
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:seam="http://jboss.com/products/seam/spring-seam"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://jboss.com/products/seam/spring-seam
http://jboss.com/products/seam/spring-seam-2.1.xsd"
>
これで、 Seam コンポーネントはいずれの Spring Bean にもインジェクション可能となりました。
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
<property name="someProperty">
<seam:instance name="someComponent"/>
</property>
</bean
>
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
<property name="someProperty">
<seam:instance name="#{someExpression}"/>
</property>
</bean
>
Seam コンポーネントインスタンスは、 Spring Bean id で Spring Bean へのインジェクションができるようになります。
<seam:instance name="someComponent" id="someSeamComponentInstance"/>
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="prototype">
<property name="someProperty" ref="someSeamComponentInstance">
</bean>
<seam:instance/>
タグで自動的に Seam コンポーネントをプロキシできるようになります。
<seam:instance id="seamManagedEM" name="someManagedEMComponent" proxy="true"/>
<bean id="someSpringBean" class="SomeSpringBeanClass">
<property name="entityManager" ref="seamManagedEM">
</bean
>
上記の例では Spring Bean から Seam 管理の永続コンテキストを使用する方法のひとつを示しています。 (Spring OpenEntityManagerInView
フィルタの代替として Seam 管理の永続コンテキストを使用するより堅牢な方法については、 Spring で Seam 管理の永続コンテキストを使用する のセクションを参照してください。)
Spring Bean を Seam コンポーネントインスタンスにインジェクトするのはさらに簡単です。 実際、 可能な方法は 2 つあります。
次のセクションでは 2 番目の選択肢について説明します。 もっとも容易な方法は EL を使って Spring Bean にアクセスします。
<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application
>
これを行うと @In
を使って Spring Bean をインジェクトできるようになります。
@In("#{bookingService}")
private BookingService bookingService;
<!-- Only needs to be specified once per bean factory-->
<seam:configure-scopes/>
...
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>
configure-scopes
定義内の prefix
属性を指定することによって、 スコープ名のプレフィックスを変更することができます。 (デフォルトのプレフィックスは seam.
です。)
<bean id="someSpringBean" class="SomeSpringBeanClass" scope="seam.CONVERSATION"/>
...
<bean id="someSingleton">
<property name="someSeamScopedSpringBean">
<seam:instance name="someSpringBean" proxy="true"/>
</property>
</bean
>
Spring は拡張可能なトランザクション管理抽象を提供し、 多くのトランザクション API (JPA、 Hibernate、 JDO、 JTA など) に対応します。 また、 Spring は Websphere や Weblogic などの多くのアプリケーションサーバーの TransactionManagers との堅固な統合を実現します。 Spring トランザクション管理はネストされるトランザクションなど多くの高度な機能のサポートを提供し、 REQUIRES_NEW や NOT_SUPPORTED のような完全 Java EE トランザクション伝播のルールに対応します。 詳細については Spring のドキュメント ここ を参照してください。
Seam が Spring のトランザクションを使用するよう設定するには、 SpringTransaction コンポーネントを以下のように有効にします。
<spring:spring-transaction platform-transaction-manager="#{transactionManager}"/>
spring:spring-transaction
コンポーネントは同期のコールバックに Spring トランザクション同期の機能を利用します。
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
<property name="persistenceContextName" value="entityManager"/>
</bean
>
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
<property name="persistenceContextName" value="entityManager"/>
<property name="persistenceUnitName" value="bookingDatabase:extended"/>
</bean
>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
applicationContext.xml
は次に似たようなものになります。
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="bookingDatabase"/>
</bean>
<bean id="seamEntityManagerFactory" class="org.jboss.seam.ioc.spring.SeamManagedEntityManagerFactoryBean">
<property name="persistenceContextName" value="entityManager"/>
<property name="persistenceUnitName" value="bookingDatabase:extended"/>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
<property name="defaultPersistenceUnitName" value="bookingDatabase:extended"/>
</bean
>
component.xml
は次に似たようなものになります。
<persistence:managed-persistence-context name="entityManager"
auto-create="true" entity-manager-factory="#{entityManagerFactory}"/>
JpaTemplate
および JpaDaoSupport
は Seam 管理永続コンテキスト用となるため、 Seam 管理永続コンテキストに対して同じ方法で設定します。
<bean id="bookingService" class="org.jboss.seam.example.spring.BookingService">
<property name="entityManagerFactory" ref="seamEntityManagerFactory"/>
</bean
>
Seam Spring 統合により Spring のツールを使った Seam 管理 Hibernate セッションへの完全アクセスに対応することもできます。 この統合は JPA 統合 に非常に似ています。
Spring の JPA 統合と同様に Spring の伝播モデルは一トランザクションの一EntityManagerFactory ごとに一つの EntityManager しか Spring ツールに対して利用できるようオープンにしません。 このため、 Seam Session 統合は proxy SessionFactory を Seam 管理の Hibernate セッションコンテキストでラップすることにより動作します。
<bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean">
<property name="sessionName" value="hibernateSession"/>
</bean
>
「sessionName」は persistence:managed-hibernate-session
コンポーネントの名前になります。 これでこの SessionFactory はいずれの Spring 提供ツールでも使用することができるようになります。 この統合は SeamManagedSessionFactory
で getCurrentInstance() を呼び出している場合であれば SessionFactory.getCurrentInstance()
に対する呼び出しにも対応します。
<spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}"/>
<!-- Install a ThreadPoolDispatcher to handle scheduled asynchronous event -->
<core:thread-pool-dispatcher name="threadPoolDispatcher"/>
<!-- Install the SpringDispatcher as default -->
<spring:task-executor-dispatcher task-executor="#{springThreadPoolTaskExecutor}" schedule-dispatcher="#{threadPoolDispatcher}"/>
Hibernate Search は、META-INF/persistence.xml
または hibernate.cfg.xml
のいずれかのファイルで設定します。
Hibernate Search の設定は、ほとんどの設定パラメータにおいて実用的なデフォルト設定がされています。手始めに、以下に最低限の永続ユニットの設定を示します。
<persistence-unit name="sample">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
[...]
<!-- use a file system based index -->
<property name="hibernate.search.default.directory_provider"
value="org.hibernate.search.store.FSDirectoryProvider"/>
<!-- directory where the indexes will be stored -->
<property name="hibernate.search.default.indexBase"
value="/Users/prod/apps/dvdstore/dvdindexes"/>
</properties>
</persistence-unit>
<persistence-unit name="sample">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
[...]
<!-- use a file system based index -->
<property name="hibernate.search.default.directory_provider"
value="org.hibernate.search.store.FSDirectoryProvider"/>
<!-- directory where the indexes will be stored -->
<property name="hibernate.search.default.indexBase"
value="/Users/prod/apps/dvdstore/dvdindexes"/>
<property name="hibernate.ejb.event.post-insert"
value="org.hibernate.search.event.FullTextIndexEventListener"/>
<property name="hibernate.ejb.event.post-update"
value="org.hibernate.search.event.FullTextIndexEventListener"/>
<property name="hibernate.ejb.event.post-delete"
value="org.hibernate.search.event.FullTextIndexEventListener"/>
</properties>
</persistence-unit>
Hibernate Search は、エンティティを Lucence のインデックスに割り当てるために、アノテーションを使用します。詳細については、リファレンスマニュアル を参照してください。
Hibernate Search は、API と JPA/Hibernate のセマンティックスを完全に一体化しています。HQL と検索条件ベースの問い合わせの間の切り替えは、少しのコードを追加するだけで行うことができます。アプリケーションが使用するおもな API は、FullTextSession
API です。(Hibernate の Session
のサブクラスです)。
Hibernate Search が発生したときに、JBoss Seam は FullTextSession を注入します。
@Stateful
@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
@In FullTextSession session;
public void search(String searchString) {
org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
org.hibernate.Query query session.createFullTextQuery(luceneQuery, Product.class);
searchResults = query
.setMaxResults(pageSize + 1)
.setFirstResult(pageSize * currentPage)
.list();
}
[...]
}
FullTextSession
は org.hibernate.Session
を継承しますので、通常の Hibernate Session のように使用することが可能です。
Java Persistance API が使用された場合には、より円滑な統合が提案されます。
@Stateful
@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
@In FullTextEntityManager em;
public void search(String searchString) {
org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
javax.persistence.Query query = em.createFullTextQuery(luceneQuery, Product.class);
searchResults = query
.setMaxResults(pageSize + 1)
.setFirstResult(pageSize * currentPage)
.getResultList();
}
[...]
}
Hibernate Search が発生したときに、JBoss Seam は FullTextEntityManager
を注入します。FullTextEntityManager
は、検索を定義したメソッドを持つ EntityManager
を継承します。また同様にして、FullTextSession
は Session
を継承します。
EJB 3.0 Session またはメッセージ駆動型Bean(Message Driven Bean) の注入が使用されたとき(たとえば、@PersistenceContext アノテーション経由で)、宣言文内で EntityManager
インタフェースを FullTextEntityManager
インタフェースに置き換えることはできません。しかしながら、注入される実装は FullTextEntityManager
の実装になるでしょう。ダウンキャスト自体は可能です。
@Stateful
@Name("search")
public class FullTextSearchAction implements FullTextSearch, Serializable {
@PersistenceContext EntityManager em;
public void search(String searchString) {
org.apache.lucene.search.Query luceneQuery = getLuceneQuery();
FullTextEntityManager ftEm = (FullTextEntityManager) em;
javax.persistence.Query query = ftEm.createFullTextQuery(luceneQuery, Product.class);
searchResults = query
.setMaxResults(pageSize + 1)
.setFirstResult(pageSize * currentPage)
.getResultList();
}
[...]
}
Seam 以外で Hibernate Search に慣れるためには、Search.createFullTextSession
を使用する必要がないことを覚えておいてください。
DVDStore か JBoss Seam の配布物である blog サンプルプログラムで、Hibernate Search の具体的な使用例を確認してください。
Configuration is a very boring topic and an extremely tedious pastime. Unfortunately, several lines of XML are required to integrate Seam into your JSF implementation and servlet container. There's no need to be too put off by the following sections; you'll never need to type any of this stuff yourself, since you can just copy and paste from the example applications!
最初に JSF と Seam を併用する場合に常に必要となる基本の設定について見ていくことにします。
<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
>
また、 Seam には web.xml
ファイルに次の記述も必要になります。
<listener>
<listener-class
>org.jboss.seam.servlet.SeamListener</listener-class>
</listener
>
このリスナーは Seam のブートストラップおよびセッションとアプリケーションコンテキストの破棄を行います。
<context-param>
<param-name
>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value
>client</param-value>
</context-param
>
ビュー状態値の変異性に関する JSF 仕様には不明瞭な部分があります。 Seam は JSF ビュー状態を使用してその PAGE スコープを支えるため、 特定の場合に問題となる可能性があります。 JSF-RI でサーバー側状態保存を使用し PAGE スコープの Bean に任意のページの特定ビューに対するその正確な値を維持させたい場合には次のコンテキストパラメータを指定する必要があります。 そうしなければ、 ユーザー−が「戻る」ボタンを使用したときに PAGE スコープのコンポーネントは最新の値を持つことになります。 つまり「戻る」ページの値から変えていればその値が使われます。 ( 仕様に関する問題 を参照)。 要求ごとに JSFビューのシリアライズが発生し性能に影響するため、 この設定はデフォルトでは有効にされません。
<context-param>
<param-name
>com.sun.faces.serializeServerState</param-name>
<param-value
>true</param-value>
</context-param
>
<application>
<view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
</application
>
<context-param>
<param-name
>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value
>.xhtml</param-value>
</context-param
>
<filter>
<filter-name
>Seam Filter</filter-name>
<filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name
>Seam Filter</filter-name>
<url-pattern
>/*</url-pattern>
</filter-mapping
>
Seam マスターフィルタは web.xml
で指定される 一番目のフィルタでなければなりません。 これによりマスターフィルタが一番最初に実行されるようになります。
Seam フィルタはいくつかの共通の属性を持ちます。 これらに加えて以降で説明するパラメータを components.xml
に設定することができます。
マスターフィルタを組み込むことにより、以下の組み込みフィルタを使用できるようになります。
デフォルトで、すべての要求に対して例外処理フィルタが適用されますが、下のように、 components.xml
に<web:exception-filter>
を記述して、 これを変更することもできます。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:web="http://jboss.com/products/seam/web">
<web:exception-filter url-pattern="*.seam"/>
</components
>
リダイレクトフィルタも、デフォルトですべての要求を対象としますが、 components.xml
の記述を以下のようにして変更することが可能です。
<web:redirect-filter url-pattern="*.seam"/>
送信されたフォームデータのキャラクターエンコーディングをセットします。
デフォルトではこのフィルタはインストールされていませんので、components.xml
に以下の記述が必要です。
<web:character-encoding-filter encoding="UTF-16"
override-client="true"
url-pattern="*.seam"/>
RichFaces Ajax フィルタは RichFaces jar 群がプロジェクトにある場合にのみインストールされます。
デフォルトの設定を上書きするには次のエントリを components.xml
に追加します。 オプションは RichFaces Developer Guide に記載されているものと同じです。
<web:ajax4jsf-filter force-parser="true"
enable-cache="true"
log4j-init-file="custom-log4j.xml"
url-pattern="*.seam"/>
デフォルトではこのフィルタはインストールされていませんので、components.xml
に以下の記述が必要です。
<web:context-filter url-pattern="/media/*"/>
コンテキストフィルタはconversationId
という名前で要求パラメータ中に対話 ID を探そうとします。必ず、要求パラメータに対話 IDを含めるようにしてください。
また、新たな対話 IDをクライアント側に確実に伝える必要があります。 Seamは組み込みコンポーネント conversation
のプロパティとして対話 IDを公開しています。
<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>
JBossアプリケーションサーバーでは、次のパターンが正しいです。
<core:init jndi-name="myEarName/#{ejbName}/local" />
Where myEarName
is the name of the EAR in which the bean is deployed.
<core:init jndi-name="#{ejbName}/local" />
<transaction:ejb-transaction/>
Seam への別の JPA プロバイダ情報の指示は 2 種類いずれかの方法で行うことができます。
<component name="org.jboss.seam.persistence.persistenceProvider"
class="org.jboss.seam.persistence.PersistenceProvider"
scope="stateless">
</component
>
<component name="org.jboss.seam.persistence.persistenceProvider"
class="org.your.package.YourPersistenceProvider">
</component
>
Java EE 5 環境で実行している場合は Seam を使いはじめるのに必要な設定はこれだけです。
EAR にこれらすべてをパッケージ化したらアーカイブの構造は以下のようになります。
my-application.ear/ jboss-seam.jar lib/ jboss-el.jar META-INF/ MANIFEST.MF application.xml my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jsf-facelets.jar jboss-seam-ui.jar login.jsp register.jsp ... my-application.jar/ META-INF/ MANIFEST.MF persistence.xml seam.properties org/ jboss/ myapplication/ User.class Login.class LoginBean.class Register.class RegisterBean.class ...
jBPM または Drools を使用したい場合は EAR の lib ディレクトリに必要な jar 群を含ませなければなりません。
facelets を使用する場合 (推奨) は WARのWEB-INF/lib
ディレクトリに jsf-facelets.jar
を含める必要があります。
Seam デバッグページを使用する (facelets を使用している場合のみ利用可能) 場合には WARの WEB-INF/lib
ディレクトリにjboss-seam-debug.jar
を含めます。
サンプルアプリケーションには EJB 3.0をサポートする Java EEコンテナにデプロイ可能ないくつかの Seam アプリケーションがふくまれています。
Seam は組み込みコンポーネントがインストールされていれば、 hibernate.cfg.xml
ファイルから Hibernate の SessionFactory
をブートストラップします。
<persistence:hibernate-session-factory name="hibernateSessionFactory"/>
この時、インジェクションを使って、Seamの管理する HibernateのSession
を利用するのであれば、 managed sessionを設定する必要があります。
<persistence:managed-hibernate-session name="hibernateSession"
session-factory="#{hibernateSessionFactory}"/>
Seam はもし組み込みコンポーネントがインストールされていれば、persistence.xml
からEntityManagerFactory
JPAをブートストラップします。
<persistence:entity-manager-factory name="entityManagerFactory"/>
インジェクションで Seam 管理のJPA EntityManager
を使用可能にしたい場合は 管理永続コンテキスト を設定する必要があります。
<persistence:managed-persistence-context name="entityManager"
entity-manager-factory="#{entityManagerFactory}"/>
アプリケーションは WARとしてパッケージすることができ、その構成は以下の様になります。
my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jboss-seam.jar jboss-seam-ui.jar jboss-el.jar jsf-facelets.jar hibernate3.jar hibernate-annotations.jar hibernate-validator.jar ... my-application.jar/ META-INF/ MANIFEST.MF seam.properties hibernate.cfg.xml org/ jboss/ myapplication/ User.class Login.class Register.class ... login.jsp register.jsp ...
Hibernate を Tomcat や TestNG のような EE 以外の環境にデプロイしたい場合はもう少し作業が必要となります。
<transaction:entity-transaction entity-manager="#{entityManager}"/>
Hibernate を使用している場合は Seam に次のように Hibernate トランザクション API を使用するよう指示することができます。
<transaction:hibernate-transaction session="#{session}"/>
Seam 予約サンプルアプリケーションには TestNG 統合テストスィートが含まれ、 SeamTest
を通じて JBoss Embedded で実行します。
この予約サンプルアプリケーションは Tomcat にもデプロイ可能です。
Embedded JBoss を正しく動作させるには Seam アプリケーション用の Tomcat にインストールしなければなりません。 Embedded JBoss は JDK 5 または JDK 6 で実行します (JDK 6 の使い方については 項40.1. 「JDK の依存性」 を参照)。 Embedded JBoss は ここ でダウンロードできます。 Embedded JBoss の Tomcat 6 へのインストール手順は非常にシンプルです。 まず、 Embedded JBoss JAR 群と設定ファイル群を Tomcat にコピーします。
jndi.properties
ファイルを除き、 Embedded JBoss の bootstrap
ディレクトリと lib
ディレクトリの配下にある全ファイルとディレクトリを Tomcat の lib
ディレクトリにコピーします。
Tomcat の lib
ディレクトリから annotations-api.jar
ファイルを削除します。
次に、 Embedded JBoss 固有の機能を追加するため 2 つの設定ファイルを更新する必要があります。
Embedded JBoss リスナー EmbeddedJBossBootstrapListener
を conf/server.xml
に追加します。 このファイル内の他のすべてのリスナーの後ろに現れなければなりません。
<Server port="8005" shutdown="SHUTDOWN">
<!-- Comment these entries out to disable JMX MBeans support used for the
administration web application -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>
<!-- Add this listener -->
<Listener className="org.jboss.embedded.tomcat.EmbeddedJBossBootstrapListener"/>
WAR ファイルのスキャンは WebinfScanner
リスナーを conf/context.xml
に追加することで有効になるはずです。
<Context>
<!-- Default set of monitored resources -->
<WatchedResource
>WEB-INF/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<!-- Add this listener -->
<Listener className="org.jboss.embedded.tomcat.WebinfScanner"/>
</Context
>
If you are using JDK 6, you must set the Java option sun.lang.ClassLoader.allowArraySyntax
to true
in Tomcat's startup script (either catalina.bat or catalina.sh):
set JAVA_OPTS=%JAVA_OPTS% -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties" -Dsun.lang.ClassLoader.allowArraySyntax=true
設定オプションの詳細については Embedded JBoss Tomcat 統合 wiki エントリ を参照してください。
Tomcat のような サーブレットエンジンへの WAR ベースのデプロイメントのアーカイブの構造は、以下のようになります。
my-application.war/ META-INF/ MANIFEST.MF WEB-INF/ web.xml components.xml faces-config.xml lib/ jboss-seam.jar jboss-seam-ui.jar jboss-el.jar jsf-facelets.jar jsf-api.jar jsf-impl.jar ... my-application.jar/ META-INF/ MANIFEST.MF persistence.xml seam.properties org/ jboss/ myapplication/ User.class Login.class LoginBean.class Register.class RegisterBean.class ... login.jsp register.jsp ...
ほとんどの Seam サンプルアプリケーションは、ant deploy.tomcat
を実行することによって、 Tomcat にデプロイすることも可能です。
<bpm:jbpm>
<bpm:pageflow-definitions>
<value
>createDocument.jpdl.xml</value>
<value
>editDocument.jpdl.xml</value>
<value
>approveDocument.jpdl.xml</value>
</bpm:pageflow-definitions>
<bpm:process-definitions>
<value
>documentLifecycle.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
<jbpm-configuration>
<jbpm-context>
<service name="persistence">
<factory>
<bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled"
><false/></field>
</bean>
</factory>
</service>
<service name="tx" factory="org.jbpm.tx.TxServiceFactory" />
<service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" />
<service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" />
<service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" />
<service name="authentication"
factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" />
</jbpm-context>
</jbpm-configuration
>
ここでのもっとも重要なことは、jBPMトランザクション制御は無効になっているということです。 Seam、あるいはEJB3がJTAトランザクションを制御するべきです。
デフォルトの SFSB タイムアウトは LRUStatefulContextCachePolicy
キャッシュ設定内の max-bean-life
の値を変更して調整することができます。
<container-cache-conf>
<cache-policy
>org.jboss.ejb.plugins.LRUStatefulContextCachePolicy</cache-policy>
<cache-policy-conf>
<min-capacity
>50</min-capacity>
<max-capacity
>1000000</max-capacity>
<remover-period
>1800</remover-period>
<!-- SFSB timeout in seconds; 1800 seconds == 30 minutes -->
<max-bean-life
>1800</max-bean-life
>
<overager-period
>300</overager-period>
<max-bean-age
>600</max-bean-age>
<resizer-period
>400</resizer-period>
<max-cache-miss-period
>60</max-cache-miss-period>
<min-cache-miss-period
>1</min-cache-miss-period>
<cache-load-factor
>0.75</cache-load-factor>
</cache-policy-conf>
</container-cache-conf
>
<session-config>
<!-- HTTP Session timeout, in minutes -->
<session-timeout
>30</session-timeout>
</session-config
>
使用するアプリケーション用にこの値を上書きするにはこのエントリをそのアプリケーションの web.xml
に含めるだけです。
Seam アプリケーションを porlet で実行させたい場合は Seam および RichFaces 用の拡張を持ち portlet 内で JSF をサポートする JSR-301 の実装 JBoss Portlet Bridge を見てみてください。 詳細は http://labs.jboss.com/portletbridge を参照してください。
# A colon-separated list of annotation types to handle org.jboss.seam.deployment.annotationTypes=com.acme.Foo:com.acme.Bar
するとアプリケーション起動時に @Foo
アノテーションが付くすべてのクラスを捕らえることができます。
@Name("fooStartup") @Scope(APPLICATION) @Startup public class FooStartup { @In("#{deploymentStrategy.annotatedClasses['com.acme.Foo']}") private Set<Class<Object > > fooClasses; @In("#{hotDeploymentStrategy.annotatedClasses['com.acme.Foo']}") private Set<Class<Object > > hotFooClasses; @Create public void create() { for (Class clazz : fooClasses) { handleClass(clazz); } for (Class clazz : hotFooClasses) { handleClass(clazz); } } }
また、 あらゆる リソースを処理することができます。 たとえば、 .foo.xml
拡張子が付くファイルすべてを処理する場合、 カスタムのデプロイメントハンドラを記述する必要があります。
public class FooDeploymentHandler implements DeploymentHandler { private Set<InputStream > files = new HashSet<InputStream >(); public String getName() { return "fooDeploymentHandler"; } public Set<InputStream > getFiles() { return files; } public void handle(String name, ClassLoader classLoader) { if (name.endsWith(".foo.xml")) { files.add(classLoader.getResourceAsStream(name)); } } }
ここではサフィックス .foo.xml
が付くすべてのファイルの一覧を作成しているだけです。
つぎにデプロイメントハンドラを Seam に登録する必要があります。 /META-INF/seam-deployment.properties
で行います。
# For standard deployment org.jboss.seam.deployment.deploymentHandlers=com.acme.FooDeploymentHandler # For hot deployment org.jboss.seam.deployment.hotDeploymentHandlers=com.acme.FooDeploymentHandler
コンマで区切った一覧を使うと複数のデプロイメントハンドラを登録することができます。
@Name("fooStartup") @Scope(APPLICATION) @Startup public class FooStartup { @In("#{deploymentStrategy['fooDeploymentHandler']}") private MyDeploymentHandler myDeploymentHandler; @In("#{hotDeploymentStrategy['fooDeploymentHandler']}") private MyDeploymentHandler myHotDeploymentHandler; @Create public void create() { for (InputStream is : myDeploymentHandler.getFiles()) { handleFooXml(is); } for (InputStream is : myHotDeploymentHandler.getFiles()) { handleFooXml(is); } } }
Seam アプリケーションを記述する場合、数多くのアノテーションを使用することになります。 Seam ではアノテーションを使用して宣言的なプログラミングを実現することができます。使用するアノテーションのほとんどは EJB 3.0 仕様で定義されています。データ検証用のアノテーションは Hibernate Validator パッケージで定義されています。そして、 Seam は Seam 独自のアノテーションセットを持っており、これについてはこの章で説明します。
これらのアノテーションはすべてパッケージ org.jboss.seam.annotations
で定義されます。
アノテーションの最初のグループでは Seam コンポーネントを定義することができます。これらのアノテーションはコンポーネントクラスで見られます。
@Name
@Name("componentName")
クラスに対して Seam コンポーネント名を定義します。このアノテーション は Seam の全コンポーネントに必要になります。
@Scope
@Scope(ScopeType.CONVERSATION)
@Role
@Role(name="roleName", scope=ScopeType.SESSION)
@Roles
@Roles({
@Role(name="user", scope=ScopeType.CONVERSATION),
@Role(name="currentUser", scope=ScopeType.SESSION)
})
@BypassInterceptors
@BypassInterceptors
@JndiName
@JndiName("my/jndi/name")
@Conversational
@Conversational
対話スコープのコンポーネントが対話用であることを指定します。つまり長期対話が起こっていない限り、そのコンポーネントのメソッドを呼ぶことができません。
@PerNestedConversation
@PerNestedConversation
警告:これはおかしな定義です。なぜならこれは要求サイクルのある部分ではコンポーネントが見え、その後見えなくなるということを意味しています。アプリケーションでこの機能を使うことは推奨しません!
@Startup
@Scope(APPLICATION) @Startup(depends="org.jboss.seam.bpm.jbpm")
@Scope(SESSION) @Startup
@Install
@Install(false)
コンポーネントがデフォルトでインストールされる必要があるかどうかを指定します。 @Install
アノテーションが無い場合、コンポーネントはインストールが必要であるという意味になります。
@Install(dependencies="org.jboss.seam.bpm.jbpm")
コンポーネントは依存関係としてリストされているコンポーネント群もインストールされる場合にのみインストールされることを指定します。
@Install(genericDependencies=ManagedQueueSender.class)
特定のクラスで実装されたコンポーネントがインストールされている場合にのみ、コンポーネントがインストールされることを指定します。 依存するコンポーネントの名前が不定である場合に便利です。
@Install(classDependencies="org.hibernate.Session")
コンポーネントが、指定されたクラスがクラスパス内にある場合にのみインストールされることを指定します。
@Install(precedence=BUILT_IN)
そのコンポーネントの優先度を指定します。 同じ名前のコンポーネントが複数存在する場合、 より高い優先度を持つコンポーネントがインストールされます。 定義される優先度の値は次のとおりです (昇順) :
@Synchronized
@Synchronized(timeout=1000)
@ReadOnly
@ReadOnly
JavaBean コンポーネントまたはコンポーネントメソッドが呼び出しの終わりで状態の複製を必要としないことを指定します。
@AutoCreate
@AutoCreate
コンポーネントが自動的に生成されるよう指定します。クライアントが create=true
を指定していなくても生成されます。
次の二つのアノテーションはバイジェクションを制御します。 これらの属性はコンポーネントインスタンス変数またはプロパティのアクセサメソッドに指定できます。
@In
@In
コンポーネントの属性が各コンポーネント呼び出しの開始時にコンテキスト変数からインジェクトされることを指定します。 コンテキスト変数が null の場合、 例外が発生します。
@In(required=false)
コンポーネントの属性が各コンポーネント呼び出しの開始時にコンテキスト変数からインジェクトされることを指定します。 コンテキスト変数は null でも構いません。
@In(create=true)
@In(value="contextVariableName")
アノテーションを付けられたインスタンス変数名を使用せず、 コンテキスト変数名を明示的に指定します。
@In(value="#{customer.addresses['shipping']}")
コンポーネント属性が各コンポーネント呼び出しの開始時に JSF EL 式を評価することでインジェクトされることを指定します。
@Out
@Out
Seam コンポーネントであるコンポーネント属性が呼び出しの終わりでそのコンテキスト変数にアウトジェクトされることを指定します。 属性が null の場合、 例外が発生します。
@Out(required=false)
Seam コンポーネントであるコンポーネント属性が呼び出しの終わりでそのコンテキスト変数にアウトジェクトされることを指定します。 属性は null でも構いません。
@Out(scope=ScopeType.SESSION)
Seam コンポーネントタイプではないコンポーネント属性が呼び出しの終わりで特定スコープにアウトジェクトされることを指定します。
明示的にスコープが指定されていない場合、 代わりに @Out
属性を持つコンポーネント自体のスコープが使用されます (またはコンポーネントがステートレスであれば EVENT
) 。
@Out(value="contextVariableName")
@In(create=true) @Out private User currentUser;
これらのアノテーションは宣言的対話の境界を設定します。 これらは Seam コンポーネントのメソッド上、通常はアクションリスナーメソッドに付与されます。
@Begin
@Begin
このメソッドが例外および null 以外の結果 (outcome) を返したら長期対話が開始することを指定します。
@Begin(join=true)
長期対話がすでに開始されている場合、 対話コンテキストが単に伝播されることを指定します。
@Begin(nested=true)
長期対話がすでに開始されている場合、 新たにネストされた対話コンテキストが開始することを指定します。 次の @End
が出現したときにネストされた対話が終了し、外側の対話が再開します。同じ外側の対話において、複数のネストされた対話が同時に存在することは全く問題ありません。
@Begin(pageflow="process definition name")
この対話のためのページフローを定義する jBPM プロセス定義の名前を定義します。
@Begin(flushMode=FlushModeType.MANUAL)
Seam 管理の永続コンテキストのフラッシュモードを指定します。 flushMode=FlushModeType.MANUAL
は アトミックな対話 (atomic conversation) をサポートします。 この場合、 flush ()
(通常、 対話終了時に呼び出される) の明示的な呼び出しが起きるまで、 すべての書き込み操作は対話コンテキスト内にキューイングされます。
join
— 長期対話が既に始まっている場合の動作を指定します。true
ならば、コンテキストが伝播されます。 false
ならば、例外がスローされます。デフォルトは false
です。nested=true
が指定されている場合は、この設定は無視されます。
nested
— 長期対話が既に開始されている場合、ネストした対話が開始されることを指定します。
flushMode
— この対話で作成される Seam 管理の Hibernate セッション、または JPA 永続コンテキストのフラッシュモードをセットします。
pageflow
— org.jboss.seam.bpm.jbpm.pageflowDefinitions
によってデプロイされた jBPM プロセス定義のプロセス定義名です。
@End
@End
@StartTask
@StartTask
@BeginTask
@BeginTask
@EndTask
@EndTask
@EndTask(transition="transitionName")
@CreateProcess
@CreateProcess(definition="process definition name")
@ResumeProcess
@ResumeProcess(processIdParameter="processId")
@Transition
@Transition("cancel")
メソッドが null 以外の結果を返すときはいつも、現在の jBPM プロセスインスタンス内で遷移にシグナルを送るように、メソッドをマークします。
Seam は特定のアクションリスナーの結果 (outcome) に対して JTA トランザクションのロールバックを強制するアノテーションを提供します。
@Transactional
@Transactional
EJB 3.0 コンポーネントではこのアノテーションを使わずに、 @TransactionAttribute
を使ってください!
@ApplicationException
@ApplicationException
EJB 3.0 コンポーネントではこのアノテーションを使わずに、@javax.ejb.ApplicationException
を代わりに使ってください。
@Interceptors
@Interceptors({DVDInterceptor, CDInterceptor})
EJB 3.0 コンポーネントではこのアノテーションを使わずに、@javax.interceptor.Interceptors
を代わりに使ってください。
これらのアノテーションは主に JavaBean Seam コンポーネントに対して有用です。EJB 3.0 コンポーネントを使う場合は、標準 Java EE5 アノテーションを使うべきです。
以下のアノテーションは、Seam インタセプタクラスで使われます。
EJB インタセプタ定義に必要なアノテーションに関する詳細は EJB 3.0 仕様のドキュメントを参照してください。
@Interceptor
@Interceptor(stateless=true)
このインタセプタはステートレスであることを指定するので、 Seam は複製処理を最適化することができます。
@Interceptor(type=CLIENT)
このインタセプタは EJB コンテナより前に呼ばれる「クライアントサイド」インタセプタであることを指定します。
@Interceptor(around={SomeInterceptor.class, OtherInterceptor.class})
このインタセプタは特定のインタセプタよりスタック内でより高い位置に配置されることを指定します。
@Interceptor(within={SomeInterceptor.class, OtherInterceptor.class})
次のアノテーションは非同期メソッドの宣言に使用されます。 例:
@Asynchronous public void scheduleAlert(Alert alert, @Expiration Date date) { ... }
@Asynchronous public Timer scheduleAlerts(Alert alert,
@Expiration Date date,
@IntervalDuration long interval) { ... }
@Asynchronous
@Asynchronous
@Duration
@Duration
非同期呼び出しのパラメータが、 その呼び出しが処理されるまでの期間であることを指定します (または反復呼び出しの場合は初めての処理が行われるまで) 。
@Expiration
@Expiration
非同期呼び出しのパラメータが、 その呼び出しが処理される (または反復呼び出しの場合は初めての処理が行われる) 日付と時刻であることを指定します。
@IntervalDuration
@IntervalDuration
このアノテーションが付いている反復呼び出しを行う非同期メソッド呼び出しのパラメータが、 各反復呼び出し間の期間であることを指定します。
以下のアノテーションで JSF をより簡単に使えるようになります。
このアノテーションは、 一緒にパッケージングするコンポーネントセットに関する情報を宣言するメカニズムを提供します。 どの Java パッケージに対しても適用できます。
本章では Seam の組み込みコンポーネント、 その設定プロパティについて説明していきます。 組み込みコンポーネントは components.xml
ファイルに記載がなくても作成されますが、 デフォルトのプロパティを上書きするまたは特定タイプの 1 つ以上のコンポーネントを指定する必要がある場合は components.xml
を使用します。
@Name
を使って独自のクラスで組み込みコンポーネントの名前を指定すると、 組み込みコンポーネントを独自の実装に簡単に置き換えることができます。
org.jboss.seam.faces.facesMessages
org.jboss.seam.faces.redirect
org.jboss.seam.faces.httpError
org.jboss.seam.core.events
@Observer
のメソッドまたは components.xml
内のメソッドバインディング経由で監視できるイベントを引き起こす API です。
org.jboss.seam.core.interpolator
org.jboss.seam.core.expressions
org.jboss.seam.core.pojoCache
次のコンポーネントグループは Seam を使用した国際化ユーザーインタフェースのビルドを容易にします。
org.jboss.seam.core.locale
org.jboss.seam.international.timezone
org.jboss.seam.core.resourceBundle
Seam リソースバンドルです。 リソースバンドルはステートレスになります。 Seam リソースバンドルは Java リソースバンドルの一覧内のキーの深さ優先検索を行います。
org.jboss.seam.core.resourceLoader
org.jboss.seam.international.localeSelector
org.jboss.seam.international.timezoneSelector
org.jboss.seam.international.messages
Seam リソースバンドル内で定義されるメッセージテンプレートからレンダリングされる国際化メッセージを含んでいるマップです。
org.jboss.seam.theme.themeSelector
org.jboss.seam.theme.theme
次のコンポーネントグループを使うとアプリケーションまたはユーザーインタフェースにより対話の制御を行うことができるようになります。
org.jboss.seam.core.conversation
現在の Seam 対話の属性をアプリケーション制御するための API です。
setViewId(String outcome)
— 対話スイッチャー、 対話リストまたはブレッドクラムから現在の対話に切り替えて戻した場合に使用する ビュー ID をセットします。
setDescription(String description)
— 対話スイッチャー、 対話リストまたはブレッドクラムで表示される現在の対話の詳細をセットします。
redirect()
— この対話用に明確に定義された最後のビュー ID にリダイレクトします (ログインのチャレンジ後に便利)。
beginPageflow(String pageflowName)
— ページフローを付けて長期実行の対話を開始します (@Begin(pageflow="...")
と同等)。
changeFlushMode(FlushModeType flushMode)
— 対話のフラッシュモードを変更します。
org.jboss.seam.core.conversationList
org.jboss.seam.core.conversationStack
org.jboss.seam.faces.switcher
org.jboss.seam.pageflow.pageflow
org.jboss.seam.bpm.actor
org.jboss.seam.bpm.transition
org.jboss.seam.bpm.businessProcess
org.jboss.seam.bpm.taskInstance
org.jboss.seam.bpm.processInstance
org.jboss.seam.bpm.jbpmContext
org.jboss.seam.bpm.taskInstanceList
org.jboss.seam.bpm.pooledTaskInstanceList
org.jboss.seam.bpm.taskInstanceListForType
org.jboss.seam.bpm.pooledTask
org.jboss.seam.bpm.processInstanceFinder
org.jboss.seam.bpm.processInstanceList
org.jboss.seam.bpm.jbpm
コンポーネントがインストールされるとこれらの全コンポーネントが常にインストールされます。
Seam Email サポートと併用するコンポーネントになります。
org.jboss.seam.mail.mailSession
org.jboss.seam.mail.mailSession.host
— 使用する SMTP サーバーのホスト名です。
org.jboss.seam.mail.mailSession.port
— 使用する SMTP サーバーのポートです。
org.jboss.seam.mail.mailSession.username
— SMTP サーバーの接続に使用するユーザー−名です。
org.jboss.seam.mail.mailSession.password
— SMTP サーバーの接続に使用するパスワードです。
org.jboss.seam.mail.mailSession.debug
— JavaMail のデバッグ機能を有効にします (かなり冗長)。
org.jboss.seam.mail.mailSession.ssl
— SMTP への SSL 接続を有効にします (デフォルトはポート465)。
org.jboss.seam.mail.mailSession.tls
— デフォルトでは true です。 メールセッションで TLS サポートを有効にします。
org.jboss.seam.core.init
org.jboss.seam.core.manager
org.jboss.seam.navigation.pages
org.jboss.seam.bpm.jbpm
JbpmConfiguration
をブートストラップします。 クラス org.jboss.seam.bpm.Jbpm
としてインストールします。
org.jboss.seam.core.conversationEntries
org.jboss.seam.faces.facesPage
org.jboss.seam.persistence.persistenceContexts
org.jboss.seam.jms.queueConnection
Manages a JMS QueueConnection
. Installed whenever managed managed QueueSender
is installed.
org.jboss.seam.jms.topicConnection
Manages a JMS TopicConnection
. Installed whenever managed managed TopicPublisher
is installed.
org.jboss.seam.persistence.persistenceProvider
org.jboss.seam.core.validators
org.jboss.seam.faces.validation
org.jboss.seam.debug.introspector
org.jboss.seam.debug.contexts
org.jboss.seam.exception.exceptions
org.jboss.seam.transaction.transaction
トランザクションを制御し JTA 互換のインタフェースの背後にある基礎となるトランザクション管理の実装を抽出するための API です。
org.jboss.seam.faces.safeActions
着信 URL 内のアクション式が安全かどうかを決定します。 アクション式がビュー内に存在することを確認することでこれを行います。
<component name="bookingDatabase"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName"
>java:/comp/emf/bookingPersistence</property>
</component>
<component name="userDatabase"
class="org.jboss.seam.persistence.ManagedPersistenceContext">
<property name="persistenceUnitJndiName"
>java:/comp/emf/userPersistence</property>
</component
>
Seam コンポーネント名は bookingDatabase
と userDatabase
です。
org.jboss.seam.persistence.ManagedPersistenceContext
org.jboss.seam.persistence.EntityManagerFactory
JPA EntityManagerFactory
を管理します。 EJB 3.0 サポート環境以外で JPA を使用する場合に最適となります。
org.jboss.seam.persistence.ManagedSession
org.jboss.seam.persistence.HibernateSessionFactory
org.jboss.seam.jms.ManagedQueueSender
org.jboss.seam.jms.ManagedTopicPublisher
org.jboss.seam.drools.ManagedWorkingMemory
org.jboss.seam.drools.RuleBase
アプリケーションスコープの Drools RuleBase
の管理コンポーネントです。 新しいルールの動的なインストールには対応しないため、 実稼働での使用は目的としていないので注意してください。
org.jboss.seam.framework.EntityHome
org.jboss.seam.framework.HibernateEntityHome
org.jboss.seam.framework.EntityQuery
org.jboss.seam.framework.HibernateEntityQuery
Seam には Seam での作業に便利な JSF コントロールがいくつか含まれています。これらは組み込み JSF コントロールと他のサードパーティライブラリのコントロールの機能補完を目的としています。 Seam と併用する際は、JBoss RichFaces、Apache MyFaces Trinidad タグライブラリの使用を推奨します。 Tomahawk タグライブラリの使用はお薦めできません。
これらのタグを使用するには、 以下のように使用するページで "s
" 名前空間を定義します (facelets 固有)。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
>
UIサンプル(examples/ui)ではいくつかのタグの使用例を示しています。
対話の伝搬を制御するアクションの起動をサポートするボタンです。 フォームはサブミットしません。
<s:button id="cancel"
value="Cancel"
action="#{hotelBooking.cancel}"/>
<s:link />
には、 view
と action
の両方が指定可能です。 この場合、指定されたビューへのリダイレクトが発生した時点でアクションが呼ばれます。
<s:button />
ではアクションリスナー(デフォルトのJSFアクションリスナーも含む)の使用はサポートされません。
対話の伝搬を制御するアクションの起動をサポートするリンクです。 フォームはサブミットしません。
<s:link />
ではアクションリスナー(デフォルトのJSFアクションリスナーも含む)の使用はサポートされません。
<s:link id="register" view="/register.xhtml"
value="Register New User"/>
<s:link />
には、 view
と action
の両方が指定可能です。 この場合、指定されたビューへのリダイレクトが発生した時点でアクションが呼ばれます。
コマンドリンクやボタン (または同様の JSF コントロール) に対する対話の伝搬をカスタマイズします。 Facelets 固有です。
<h:commandButton value="Apply" action="#{personHome.update}">
<s:conversationPropagation type="join" />
</h:commandButton
>
Seam タイムゾーン内でデータ変換または時刻変換を行います。
<h:outputText value="#{item.orderDate}">
<s:convertDateTime type="both" dateStyle="full"/>
</h:outputText
>
エンティティコンバータを現在のコンポーネントに割り当てます。 ラジオボタンコントロールおよびドロップダウンコントロールに役立ちます。
<s:convertEntity />
は Seam管理トランザクション (Seam managed transaction) ( 項9.2. 「Seam 管理トランザクション」 参照) とともに使う必要があります。
管理された永続コンテキスト (Managed Persistence Context) が entityManager
と呼ばれていないならば、 components.xmlで設定する必要があります:
<components xmlns="http://jboss.com/products/seam/components" xmlns:ui="http://jboss.com/products/seam/ui"> <ui:jpa-entity-loader entity-manager="#{em}" />
管理された Hibernate セッション を使用するならば、components.xmlで設定する必要があります:
<components xmlns="http://jboss.com/products/seam/components" xmlns:ui="http://jboss.com/products/seam/ui"> <ui:hibernate-entity-loader />
管理された Hibernate セッション (Managed Hibernate Session) が session
と呼ばれていないならば、components.xmlで設定する必要があります:
<components xmlns="http://jboss.com/products/seam/components" xmlns:ui="http://jboss.com/products/seam/ui"> <ui:hibernate-entity-loader session="#{hibernateSession}" />
ひとつ以上のエンティティマネージャをエンティティコンバータと使いたい場合、components.xml
でそれぞれのエンティティマネージャに対してエンティティコンバータのコピーを作成することができます。エンティティコンバータがどのようにエンティティローダに永続化処理を委譲するのか注意してください:
<components xmlns="http://jboss.com/products/seam/components" xmlns:ui="http://jboss.com/products/seam/ui"> <ui:entity-converter name="standardEntityConverter" entity-loader="#{standardEntityLoader}" /> <ui:jpa-entity-loader name="standardEntityLoader" entity-manager="#{standardEntityManager}" /> <ui:entity-converter name="restrictedEntityConverter" entity-loader="#{restrictedEntityLoader}" /> <ui:jpa-entity-loader name="restrictedEntityLoader" entity-manager="#{restrictedEntityManager}" />
<h:selectOneMenu value="#{person.continent}"> <s:selectItems value="#{continents.resultList}" var="continent" label="#{continent.name}" /> <f:converter converterId="standardEntityConverter" /> </h:selectOneMenu >
使い方
<h:selectOneMenu value="#{person.continent}" required="true">
<s:selectItems value="#{continents.resultList}" var="continent"
label="#{continent.name}"
noSelectionLabel="Please Select..."/>
<s:convertEntity />
</h:selectOneMenu
>
enum コンバータを現在のコンポーネントに割り当てます。 おもにラジオボタンコントロールおよびドロップダウンコントロールに役立ちます。
<h:selectOneMenu value="#{person.honorific}">
<s:selectItems value="#{honorifics}" var="honorific"
label="#{honorific.label}"
noSelectionLabel="Please select" />
<s:convertEnum />
</h:selectOneMenu
>
java.util.concurrent.atomic.AtomicBoolean
のための javax.faces.convert.Converter
です。
<h:outputText value="#{item.valid}"> <s:convertAtomicBoolean /> </h:outputText >
java.util.concurrent.atomic.AtomicInteger
のための javax.faces.convert.Converter
です。
<h:outputText value="#{item.id}"> <s:convertAtomicInteger /> </h:outputText >
java.util.concurrent.atomic.AtomicLong
のための javax.faces.convert.Converter
です。
<h:outputText value="#{item.id}"> <s:convertAtomicLong /> </h:outputText >
非視覚的なコントロールです。 Hibernate Validator を使用してバウンドプロパティに対して JSF 入力フィールドを確認します。
<h:inputText id="userName" required="true"
value="#{customer.userName}">
<s:validate />
</h:inputText>
<h:message for="userName" styleClass="error" />
非視覚的なコントロールです。 Hibernate Validator を使ってそのバウンドプロパティに対しすべての子 JSF 入力フィールドを確認します。
<s:validateAll>
<div class="entry">
<h:outputLabel for="username"
>Username:</h:outputLabel>
<h:inputText id="username" value="#{user.username}"
required="true"/>
<h:message for="username" styleClass="error" />
</div>
<div class="entry">
<h:outputLabel for="password"
>Password:</h:outputLabel>
<h:inputSecret id="password" value="#{user.password}"
required="true"/>
<h:message for="password" styleClass="error" />
</div>
<div class="entry">
<h:outputLabel for="verify"
>Verify Password:</h:outputLabel>
<h:inputSecret id="verify" value="#{register.verify}"
required="true"/>
<h:message for="verify" styleClass="error" />
</div>
</s:validateAll
>
検証に失敗した場合または required="true"
が設定されている場合、 JSF 入力フィールドを "装飾" します。
<s:decorate template="edit.xhtml">
<ui:define name="label"
>Country:</ui:define>
<h:inputText value="#{location.country}" required="true"/>
</s:decorate
>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib">
<div
>
<s:label styleClass="#{invalid?'error':''}">
<ui:insert name="label"/>
<s:span styleClass="required" rendered="#{required}"
>*</s:span>
</s:label>
<span class="#{invalid?'error':''}">
<s:validateAll>
<ui:insert/>
</s:validateAll>
</span>
<s:message styleClass="error"/>
</div
>
</ui:composition
>
レンダリングされないコンポーネントです。その子要素のレンダリングを有効化/無効化するのに便利です。
<s:fragment rendered="#{auction.highBidder ne null}">
Current bid:
</s:fragment
>
List、 Set、 DataModel または Array から List<SelectItem>
を作成します。
<h:selectOneMenu value="#{person.age}"
converter="ageConverter">
<s:selectItems value="#{ages}" var="age" label="#{age}" />
</h:selectOneMenu
>
ファイルアップロードコントロールをレンダリングします。 このコントロールはエンコーディングタイプ multipart/form-data
を使用してフォーム内で使用する必要があります。
<h:form enctype="multipart/form-data"
>
マルチパート要求の場合、 Seam Multipart サーブレットフィルタも web.xml
内で設定しなければなりません。
<filter>
<filter-name
>Seam Filter</filter-name>
<filter-class
>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name
>Seam Filter</filter-name>
<url-pattern
>/*</url-pattern>
</filter-mapping
>
components.xml では、 次のようなマルチパート要求用の設定オプションが設定できます。
<component class="org.jboss.seam.web.MultipartFilter">
<property name="createTempFiles"
>true</property>
<property name="maxRequestSize"
>1000000</property>
</component
>
<s:fileUpload id="picture" data="#{register.picture}"
accept="image/png"
contentType="#{register.pictureContentType}" />
拡張された <h:graphicImage>
で、Seamコンポーネント内で画像を作成することを許可します。 さらに画像の変換も適用できます。
<h:graphicImage>
のすべての属性がサポートされている他、 以下もサポートされています。
イメージに変換を適用するには、 適用する変換を指定するタグをネストさせます。 Seam は現在、 次のような変換をサポートしています。
<s:graphicImage rendered="#{auction.image ne null}"
value="#{auction.image.data}">
<s:transformImageSize width="200" maintainRatio="true"/>
</s:graphicImage
>
Seam Remoting を使うのに必要な Javascript のスタブを生成します。
使い方
<s:remote include="customerAction,accountAction,com.acme.MyBean"/>
Seamはさらに、SeamコンポーネントをJSFコンバータやバリデータとして使えるようにするアノテーションを提供します:
@Converter
@Name("itemConverter")
@BypassInterceptors
@Converter
public class ItemConverter implements Converter {
@Transactional
public Object getAsObject(FacesContext context, UIComponent cmp, String value) {
EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
entityManager.joinTransaction();
// Do the conversion
}
public String getAsString(FacesContext context, UIComponent cmp, Object value) {
// Do the conversion
}
}
<h:inputText value="#{shop.item}" converter="itemConverter" />
@Validator
@Name("itemValidator")
@BypassInterceptors
@org.jboss.seam.annotations.faces.Validator
public class ItemValidator implements javax.faces.validator.Validator {
public void validate(FacesContext context, UIComponent cmp, Object value)
throws ValidatorException {
ItemController ItemController = (ItemController) Component.getInstance("itemController");
boolean valid = itemController.validate(value);
if (!valid) {
throw ValidatorException("Invalid value " + value);
}
}
}
<h:inputText value="#{shop.item}" validator="itemValidator" />
SeamコンポーネントをJSFバリデータとして登録します。ここで例に挙げたのは、別のSeamコンポーネントをインジェクトし、インジェクトされたコンポーネントが値を検証するようなバリデータです。
Seam は 標準の Unified Expression Language (EL) に拡張を提供する JBoss EL を使用します。 JBoss EL は EL 式のパワーや表現力を増強させるいくつかの機能拡張を提供しています。
JBoss EL ではこの制約が取り除かれます。 以下が例です。
<h:commandButton action="#{hotelBooking.bookHotel(hotel)}" value="Book Hotel"/>
@Name("hotelBooking")
public class HotelBooking {
public String bookHotel(Hotel hotel) {
// Book the hotel
}
}
ちょうど Java から メソッドへのコールと同じように、 パラメータは括弧で囲まれコンマで区切られます。
<h:commandButton action="#{hotelBooking.bookHotel(hotel, user)}" value="Book Hotel"/>
パラメータ hotel
と user
は値式として評価されコンポーネントの bookHotel()
メソッドに渡されます。
パラメータには、以下のように、どのような値式も使う事ができます。
<h:commandButton
action="#{hotelBooking.bookHotel(hotel.id, user.username)}"
value="Book Hotel"/>
<h:commandLink action="#{printer.println('Hello world!')}" value="Hello"/>
JBoss EL はメソッド構文を使った値の検索を許可することで制約を取り除きます。 以下が例です。
<h:outputText value="#{person.name}" rendered="#{person.name.length()
> 5}" />
#{searchResults.size()}
一般的には #{obj.property} 形式の表現は #{obj.getProperty()} 形式とまったく同一となります。
パラメータを使うこともできます。 次の例では文字列リテラルの引数を持つ productsByColorMethod
を呼び出しています。
#{controller.productsByColor('blue')}
JBoss EL を使用する際には次の点に留意してください。
@Factory("items")
public List<Item
> getItems() {
return entityManager.createQuery("select ...").getResultList();
}
<h:dataTable value="#{items}" var="item">
<h:column>
<h:commandLink value="Select #{item.name}" action="#{itemSelector.select(item})" />
</h:column>
</h:dataTable
>
上記のようなケースは非常に稀であり、 Java コードで MethodExpression
を手作業で呼び出す必要が有る場合にのみ適用されます。
JBoss EL は限られたプロジェクション構文に対応します。 プロジェクション式はサブとなる式を複数値 (リスト、 セットなど) の式全体にマッピングします。 以下が例です。
#{company.departments}
#{company.departments.{d|d.name}}
サブとなる式は中括弧で囲みます。 この例では各部署ごとに d.name
式が評価され、 d
を部署のオブジェクトへのエイリアスとして使っています。 この式の結果は文字列値の一覧となります。
式中に有効な式ならいずれの式でも使用できるため次の記述は完全に有効です。 ある会社の全部署の部署名の長さ (サイズ) を知りたい場合には次のように問い合わせることができます。
#{company.departments.{d|d.size()}}
プロジェクションはネストさせることが可能です。 次の式は各部署内のそれぞれの社員のラストネームを返します。
#{company.departments.{d|d.employees.{emp|emp.lastName}}}
ただしプロジェクションのネストは若干の注意が必要です。 次の式は全部署の全社員一覧を返すように見えます。
#{company.departments.{d|d.employees}}
しかし実際には各個別部署ごとの社員一覧を含む一覧を返します。 値を結合させるにはもう少し長い式を使う必要があります。
#{company.departments.{d|d.employees.{e|e}}}
Seamアプリケーションのほとんどは、少なくとも2種類の自動テストが必要です。 個々のSeamコンポーネントを独立してテストするユニットテストと、 アプリケーションのすべてのJava層 (ビューページ以外のすべて) をスクリプトでテストする統合テストです。
どちらのテストもとても簡単に作成できます。
次のような、顧客アカウントのステートメントを作成するSeamコンポーネントを考えてみましょう。
@Stateless
@Scope(EVENT)
@Name("statementOfAccount")
public class StatementOfAccount {
@In(create=true) EntityManager entityManager
private double statementTotal;
@In
private Customer customer;
@Create
public void create() {
List<Invoice
> invoices = entityManager
.createQuery("select invoice from Invoice invoice where invoice.customer = :customer")
.setParameter("customer", customer)
.getResultList();
statementTotal = calculateTotal(invoices);
}
public double calculateTotal(List<Invoice
> invoices) {
double total = 0.0;
for (Invoice invoice: invoices)
{
double += invoice.getTotal();
}
return total;
}
// getter and setter for statementTotal
}
calculateTotalメソッドのユニットテスト(つまりこのコンポーネントのビジネスロジックのテスト)は、以下のように書くことができます。
public class StatementOfAccountTest {
@Test
public testCalculateTotal {
List<Invoice
> invoices = generateTestInvoices(); // A test data generator
double statementTotal = new StatementOfAccount().calculateTotal(invoices);
assert statementTotal = 123.45;
}
}
Seamが採用するアプローチは、コンポーネントのテストを作成し独立したコンテナ環境(SeamとJBoss内蔵のコンテナ:詳細は項29.6.1. 「Embedded JBoss をインストールする」参照)で実行するというものです。
public class RegisterTest extends SeamTest
{
@Test
public void testRegisterComponent() throws Exception
{
new ComponentTest() {
protected void testComponents() throws Exception
{
setValue("#{user.username}", "1ovthafew");
setValue("#{user.name}", "Gavin King");
setValue("#{user.password}", "secret");
assert invokeMethod("#{register.register}").equals("success");
assert getValue("#{user.username}").equals("1ovthafew");
assert getValue("#{user.name}").equals("Gavin King");
assert getValue("#{user.password}").equals("secret");
}
}.run();
}
...
}
@Name("paymentProcessor")
public class PaymentProcessor {
public boolean processPayment(Payment payment) { .... }
}
統合テストをするには、次のようなコンポーネントのモック実装を作成します。
@Name("paymentProcessor")
@Install(precedence=MOCK)
public class MockPaymentProcessor extends PaymentProcessor {
public boolean processPayment(Payment payment) {
return true;
}
}
このアプローチではビューを除くすべてをテストすることができます。
さきほどユニットテストしたコンポーネントのJSFビューを考えてみましょう。
<html>
<head>
<title
>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<table border="0">
<tr>
<td
>Username</td>
<td
><h:inputText value="#{user.username}"/></td>
</tr>
<tr>
<td
>Real Name</td>
<td
><h:inputText value="#{user.name}"/></td>
</tr>
<tr>
<td
>Password</td>
<td
><h:inputSecret value="#{user.password}"/></td>
</tr>
</table>
<h:messages/>
<h:commandButton type="submit" value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html
>
このアプリケーションのユーザー登録機能(Registerボタンをクリックしたときの動作)をテストします。TestNG自動テストで、JSF要求のライフサイクルを再現してみましょう。
public class RegisterTest extends SeamTest
{
@Test
public void testRegister() throws Exception
{
new FacesRequest() {
@Override
protected void processValidations() throws Exception
{
validateValue("#{user.username}", "1ovthafew");
validateValue("#{user.name}", "Gavin King");
validateValue("#{user.password}", "secret");
assert !isValidationFailure();
}
@Override
protected void updateModelValues() throws Exception
{
setValue("#{user.username}", "1ovthafew");
setValue("#{user.name}", "Gavin King");
setValue("#{user.password}", "secret");
}
@Override
protected void invokeApplication()
{
assert invokeMethod("#{register.register}").equals("success");
}
@Override
protected void renderResponse()
{
assert getValue("#{user.username}").equals("1ovthafew");
assert getValue("#{user.name}").equals("Gavin King");
assert getValue("#{user.password}").equals("secret");
}
}.run();
}
...
}
Seamのサンプルアプリケーションには、もっと複雑なケースの統合テストが用意されています。Antを使用してテストを実行する方法と、EclipseのTestNGプラグインを使用する方法があります。
各テストの前にデータベースにデータを挿入したり、消去したりしたい場合はDBUnitと連携します。SeamTestの替わりにDBUnitSeamTestを継承してください。
<dataset>
<ARTIST
id="1"
dtype="Band"
name="Pink Floyd" />
<DISC
id="1"
name="Dark Side of the Moon"
artist_id="1" />
</dataset
>
prepareDBUnitOperations()
をオーバーライドしてSeamに知らせます。
protected void prepareDBUnitOperations() {
beforeTestOperations.add(
new DataSetOperation("my/datasets/BaseData.xml")
);
}
テストメソッドの実行後にさらにデータ消去が必要な場合はafterTestOperations
のリストにオペレーションを追加してください。
TestNGのテストパラメータdatasourceJndiName
にデータソース名を指定して、DBUnitにデータソースを知らせます。
<parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>
DBUnitSeamTestはMySQLとHSQLをサポートします。どちらを使うか、以下のように設定してください。
<parameter name="database" value="HSQL" />
バイナリデータをテストデータセットに挿入することもできます(Windowsでは未検証ですので注意してください)。リソースの場所を以下のように指定してください。
<parameter name="binaryDir" value="images/" />
testng.xml
にこの三つのパラメータを必ず指定してください。
DBUnitSeamTestで別のデータベースを使用するには、いくつかのメソッドを実装しなければいけません。詳細はAbstractDBUnitSeamTest
のjavadocを参照してください。
OC4J (Oracle Containers for Java) 11g (現時点では "Technology Preview" リリース) は、Oracle の JEE5 アプリケーションサーバーです。Seam アプリケーションは、OC4J にデプロイすることが可能ですが、構成の変更と依存関係の追加が必要になります。この章では、まさに何が必要であるかを説明します。Seam に同梱されている JEE5 ホテル予約サンプルアプリケーションのビルドとデプロイを説明するところから始めます。それから、seam-gen
によって生成されるプロジェクトをデプロイします。 まず最初に、RichFaces の ajax コンポーネントと facelets を使用した基本的な seam-gen
アプリケーションを動作させます。次にこのアプリケーションを、Drools を使用した Seam セキュリティ、Hibernate により提供されるJPA、MySQL データベースからのリバースエンジニアリングを使用した CRUD 自動アプリケーションへと拡張してゆきます。
最初にターゲットコンテナ - OC4J のインストールが必要です。 この章では、OC4J 11g Technology Preview (OC4J 10g ではありません) の使用が必要です。OC4J 11g は http://www.oracle.com/technology/tech/java/oc4j/11/ からダウンロードできます。以下は、OC4J 11g のインストール、起動、アクセス、終了の手順です。OC4J のインストールについてのさらなる情報については、OC4J と一緒に配布されている readme.txt
や、OC4J インストールガイド (installation guide) 、リリースノート (release notes)を参照してください。
OC4J をダウンロードして解凍してください。
環境変数として $JAVA_HOME
と $ORACLE_HOME
が設定されていることを確認してください ($ORACLE_HOME
は OC4J を解凍したディレクトリです)。 OC4J のインストールの詳細については、OC4J と一緒に配布された readme.txt
を参考にしてください
$ORACLE_HOME/j2ee/home/applications
ディレクトリにアプリケーション (ear/war) をデプロイしてください。
OC4J は、デフォルトではホットデプロイをサポートしていないことに注意してください。 これは、アプリケーションをデプロイするたびにサーバを再起動しなければならないことを意味します。
OC4J を起動してください。 $ORACLE_HOME/j2ee/home/java -jar -XX:MaxPermSize=256M oc4j.jar
上記のコマンドを使用して、デフォルトの PermGen メモリ設定を上書きしなければなりません。詳細は、 OC4J リリースノート を参照してください。
OC4J の初回起動時には、管理パスワードを設定するように求められます。
デプロイできればすぐに、http://localhost:8888/<your-app-path>
にアクセスしてアプリケーションを確認できます。
サーバを停止するには、サーバを実行させているコンソールで CTRL-C
を押します。
まずホテル予約サンプルの基本的な依存関係を調べます。この知識をもとに OC4J に必要となる追加の依存関係を調べます。
アプリケーションに依存関係を含める方法は、後の 項36.2.3. 「jee5/booking
サンプルのビルド 」 で説明します。
jboss-seam.jar
— EJB3 モジュールとして宣言します (Seam はコンテナ管理のトランザクションと連携する必要があるからです。そのため EJB3 ステートフルセッション Bean として実装されています) jboss-el.jar
jboss-seam-ui.jar
— Seam の JSF コントロールは Apache の commons-beanutils に依存していますjboss-seam-debug.jar
jsf-facelets.jar
richfaces-api.jar
と richfaces-impl.jar
と richfaces-ui.jar
— これらは Apache commons-digester と commons-beanutils を必要とします Hibernate — もちろん JPA プロバイダとして (OC4J に同梱されて出荷される TopLink Essentials ではなく) Hibernate を使用します。
Hibernate を JPA プロバイダとして使用するためには、以下の jar が必要です。
サードパーティの jar — Seam とサンプルを実行させるには、さまざまな jar が必要になります。
hibernate-search.jar
hibernate-common-annotations.jar
— Hibernate search に必要 lucene-core.jar
— Hibernate search に必要 antlr.jar
— Seam テキストに必要 jbpm-jpdl.jar
— Seam JBPM に必要 quartz.jar
dbunit.jar
— テスト用クラスに必要 jboss-embedded-api.jar
— テスト用クラスに必要 Drools — Seam セキュリティのために必要です。Drools による Seam セキュリティは使用しませんが含める必要があります。Drools は 6 つの jar からなります。
web.xml
web.xml
にすべての EJB を宣言する必要があります。 これは多くの JEE5 アプリケーションサーバー - 例えば OC4J と GlassFish - で必要となる思慮のない要求です。
以下の例は、すでに変更を完了したサンプルの web.xml ファイルです。
<ejb-local-ref>
<ejb-ref-name>
jboss-seam-jee5/AuthenticatorAction/local
</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local>
org.jboss.seam.example.booking.Authenticator
</local>
<ejb-link
>AuthenticatorAction</ejb-link>
</ejb-local-ref>
persistence.xml
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.query.factory_class"
value="org.hibernate.hql.classic.ClassicQueryTranslatorFactory"/>
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.OrionTransactionManagerLookup"/>
サンプルの build.xml
ファイルを修正してください。
OC4J 用とラベル付けされたライブラリプロパティのコメントアウトをはずしてください。これにより上記で説明した追加のライブラリが含まれます。
<!-- add libs for oc4j (eager classloading) -->
<property name="jbpm.lib" value="true"/>
<property name="drools.lib" value="true"/>
<property name="quartz.lib" value="true" />
<property name="search.lib" value="true" />
<property name="dbunit.lib" value="true" />
<property name="jboss-embedded-api.lib" value="true" />
examples/jee5/booking
ディレクトリでant
を実行してデモアプリケーションをビルドします。ビルドされるターゲットは dist/jboss-seam-jee5.ear
です。
このミニチュートリアルでは、JEE 5 アプリケーションを OC4J にデプロイするのに必要な (とても退屈な) 手順を説明します。項36.1. 「OC4J のインストールと操作」 の手順に従ってすでに OC4J をダウンロードしインストールを終えているものとして進めます。 組み込み hsqldb データベースを使用して jee5/booking
サンプルをデプロイすることします。別のアプリケーションをデプロイするために、データソースとアプリケーション名を変更する必要があります。
OC4J 共用ライブラリディレクトリへ hsqldb.jar
をコピーしてください。 cp $SEAM_HOME/lib/hsqldb.jar $ORACLE_HOME/j2ee/home/applib/
(OC4J には組み込みデータベースが同梱されていないので HSQLDB を使用することにします)
OC4J データソースファイル $ORACLE_HOME/j2ee/home/config/data-sources.xml
を編集して、<data-sources>
の内側に以下を追加してください
<managed-data-source
connection-pool-name="jee5-connection-pool"
jndi-name="jdbc/__default"
name="jee5-managed-data-source" />
<connection-pool name="jee5-connection-pool">
<connection-factory
factory-class="org.hsqldb.jdbcDriver"
user="sa"
password=""
url="jdbc:hsqldb:." />
</connection-pool>
jndi-name
は persistence.xml
で jta-data-source
として使用されます。
$ORACLE_HOME/j2ee/home/config/server.xml
を編集して、<application-server>
の内側に以下を追加してください
<application name="jboss-seam-jee5"
path="../../home/applications/jboss-seam-jee5.ear"
parent="default"
start="true" />
簡単にするために、プロジェクトに使用したのと同じ名前を使用してください。
$ORACLE_HOME/j2ee/home/config/default-web-site.xml
を編集して、<web-site>
の内側に以下を追加してください
<web-app application="jboss-seam-jee5"
name="jboss-seam-jee5"
load-on-startup="true"
root="/seam-jee5" />
root
は、アプリケーションにアクセスするために Web ブラウザに入力するコンテキストパスです。
アプリケーションを OC4J にコピーしてください。 cp dist/jboss-seam-jee5.ear $ORACLE_HOME/j2ee/home/applications/
上記の 項36.1. 「OC4J のインストールと操作」 の手順に従って、OC4J を起動 / 終了してください。
http://localhost:8888/seam-jee5
をブラウズしてアプリケーションを確認してください。
build.xml
resources/META-INF/persistence-dev.xml
プロパティを追加してください (jee5/booking
サンプルで述べられています)。
<property name="hibernate.query.factory_class"
value="org.hibernate.hql.classic.ClassicQueryTranslatorFactory" />
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.OrionTransactionManagerLookup" />
<property name="hibernate.transaction.flush_before_completion"
value="true"/>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.HashtableCacheProvider"/>
EntityManagerFactory を定義する JBoss AS 固有のメソッドを取り除いてください。
<property
name="jboss.entity.manager.factory.jndi.name"
value="java:/oc4j_exampleEntityManagerFactory">
prod プロファイルを使用して OC4J にデプロイしたければ、persistence-prod.xml
も同様に修正する必要があります。
resources/META-INF/jboss-app.xml
JBoss AS にはデプロイしないのでこのファイルを削除できます (JBoss AS では jboss-app.xml
を使用して、クラスローディングの分離を有効にします)
resources/*-ds.xml
resources/WEB-INF/components.xml
jndi-pattern
を java:comp/env/oc4j_example/#{ejbName}/local
に修正します
<persistence:entity-manager-factory
auto-create="true"
name="oc4jEntityManagerFactory"
persistence-unit-name="oc4j_example" />
<persistence:managed-persistence-context
name="entityManager"
auto-create="true"
entity-manager-factory="#{oc4jEntityManagerFactory}" />
resources/WEB-INF/web.xml
<ejb-local-ref>
<ejb-ref-name>
oc4j_example/EjbSynchronizations/local
</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local>
org.jboss.seam.transaction.LocalEjbSynchronizations
</local>
<ejb-link
>EjbSynchronizations</ejb-link>
</ejb-local-ref>
resources/META-INF/orion-application.xml
<?xml version = '1.0' encoding = 'utf-8'?>
<orion-application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://xmlns.oracle.com/oracleas/schema/
orion-application-10_0.xsd"
schema-major-version="10"
schema-minor-version="0"
component-classification="internal">
<imported-shared-libraries>
<remove-inherited name="oracle.xml"/>
</imported-shared-libraries>
</orion-application>
<copy todir="${ear.dir}/META-INF">
<fileset dir="${basedir}/resources/META-INF">
<include name="application.xml" />
<include name="orion-application.xml"/>
<include name="jboss-app.xml" />
</fileset>
</copy>
このアプリケーションは、jee5/booking
サンプルと同様の変更が必要となります。
<target name="ear" description="Build the EAR">
<copy todir="${ear.dir}">
<fileset dir="${basedir}/resources">
<include name="*jpdl.xml" />
<include name="*hibernate.cfg.xml" />
<include name="jbpm.cfg.xml" />
<include name="*.drl" />
</fileset>
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset>
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
</fileset>
</copy>
<copy todir="${ear.dir}/META-INF">
<fileset dir="${basedir}/resources/META-INF">
<include name="application.xml" />
<include name="jboss-app.xml" />
</fileset>
</copy>
</target>
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-search.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/commons-logging.jar"/>
<include name="lib/commons-collections.jar"/>
<include name="lib/jboss-common-core.jar"/>
<include name="lib/core.jar"/>
サードパーティ jar — これらの多くは OC4J クラスローディングのためのみに必要です。
<include name="lib/javassist.jar"/>
<include name="lib/quartz.jar"/>
<include name="lib/dbunit.jar"/>
<include name="lib/jboss-embedded-api.jar"/>
<include name="lib/dom4j.jar"/>
<include name="lib/lucene-core.jar"/>
<include name="lib/cglib.jar"/>
<include name="lib/asm.jar"/>
<include name="lib/commons-beanutils.jar"/>
<include name="lib/commons-digester.jar"/>
<include name="lib/concurrent.jar"/>
<include name="lib/antlr.jar"/>
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-search.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/commons-logging.jar"/>
<include name="lib/commons-collections.jar"/>
<include name="lib/jboss-common-core.jar"/>
<include name="lib/core.jar"/>
<include name="lib/javassist.jar"/>
<include name="lib/quartz.jar"/>
<include name="lib/dbunit.jar"/>
<include name="lib/jboss-embedded-api.jar"/>
<include name="lib/dom4j.jar"/>
<include name="lib/lucene-core.jar"/>
<include name="lib/cglib.jar"/>
<include name="lib/asm.jar"/>
<include name="lib/commons-beanutils.jar"/>
<include name="lib/commons-digester.jar"/>
<include name="lib/concurrent.jar"/>
<include name="lib/antlr.jar"/>
</fileset>
これらの手順は 項36.3. 「OC4J への Seam アプリケーションのデプロイ」 の手順と非常に類似していますが、oc4j_example
アプリケーションへ正しく置き換えて使用することが必要です。
プロジェクトのベースディレクトリ (例えば /home/jbalunas/workspace/oc4j_example
) で ant
を実行してアプリケーションをビルドしてください。ビルドされるターゲットファイルは dist/oc4j_example.ear
です。
mysql-connector.jar
ファイルを $ORACLE_HOME/j2ee/home/applib
ディレクトリにコピーして、JDBC ドライバが利用できるようにしてください。
$ORACLE_HOME/j2ee/home/config/data-sources.xml
<managed-data-source
connection-pool-name="oc4j-example-connection-pool"
jndi-name="jdbc/__oc4jexample"
name="oc4j-example-managed-data-source" />
<connection-pool
name="oc4j-example-connection-pool">
<connection-factory
factory-class="com.mysql.jdbc.Driver"
user="username"
password="password"
url="jdbc:mysql:///oc4j" />
</connection-pool>
$ORACLE_HOME/j2ee/home/config/server.xml
<application name="oc4j_example"
path="../../home/applications/oc4j_example.ear"
parent="default"
start="true" />
$ORACLE_HOME/j2ee/home/config/default-web-site.xml
<web-app application="oc4j_example"
name="oc4j_example"
load-on-startup="true"
root="/oc4j_example" />
上記の 項36.1. 「OC4J のインストールと操作」 の手順に従って、OC4J を起動 / 終了してください。
http://localhost:8888/oc4j_example
をブラウズしてアプリケーションを確認してください。
ここでは、基本的な seam-gen
アプリケーションを、現存のデータベースをもとにした本格的な CRUD アプリケーションに拡張します。さらに Drools
ベースのセキュリティも追加します。
@Name("authenticator")
@Stateless
public class AuthenticatorAction implements Authenticator {
@Local
public interface Authenticator {
public boolean authenticate();
}
AuthenticatorAction
クラスに以下の行を追加することによって EntityManager をインジェクトするのに@PersistenceContext
を使用してください。
@PersistenceContext private EntityManager entityManager;
public boolean authenticate() {
List <User
> users = entityManager .createQuery("select u from User u where
u.username = #{identity.username} and
u.password = #{identity.password}") .getResultList();
if (users.size() == 1) {
identity.addRole("admin");
return true;
} else {
return false;
}
}
<ejb-local-ref>
<ejb-ref-name>
oc4j_example/AuthenticatorAction/local
</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local>
org.jboss.seam.tutorial.oc4j.action.Authenticator
</local>
<ejb-link
>AuthenticatorAction</ejb-link>
</ejb-local-ref>
前のようにビルドしてデプロイしてください、今は単に実際に入力されたユーザー名とパスワードが受け入れられることに注意してください。
WebLogic 10.3 はBEAが提供している最新版の安定したJ2EEサーバです。SeamアプリケーションはWebLogicサーバ上でデプロイと開発が可能であり、この章ではその方法を示します。WebLogicサーバでは動作上いくつかの既知の問題があり、WebLogicの仕様上、設定の変更が必要となります。
最初にWebLogicをダウンロード、インストールそして実行します。次にSeamのJEE5のサンプルを操作して実行させます。その後JPAのサンプルがサーバにデプロイされます。そして最後に seam-gen
アプリケーションを作成し、独自のアプリケーションを開始するためにその作成したアプリケーションを起動します。
Weblogic 10.0.MP1
— ダウンロード ページ
10.0.MP1は、EJBで使用しているメソッドで問題がいくつか知られています。その問題とは他のものと同じようにこれらのメソッドにあるvarargs
(これらの可変引数をtransient
として混乱させる)を使用することによって生じます。問題と動作のすべての詳細は次を参照してください。項37.2.1. 「Weblogic上のEJB3の問題」
Weblogic 10.3
— ダウンロードページ
これはWebLogicの最新の安定リリースで、そしてその一つは下記においてそのサンプルと共に使用されています。このバージョンは10.0.MP1に含まれていたEJBのいくつかの問題に取り組んできました。しかしながらた、変更のなかの一つはこのリリースではうまくいきませんでした。 詳細は次を参照してください。項37.2.1. 「Weblogic上のEJB3の問題」 しかしながらこの問題のために使用する特別なWebLogic用のjarファイルについては下記で議論します。
jboss-seam.jar
Seam 2.0.2.CR2 を起動すると、特殊なWebLogicの仕様のjarファイルが作成されますが、それにはTimerServiceDispatcher
は含まれていません。これはEJBが可変引数を使用していることがEJBの第2の問題を露呈させています。このBEAの既知の問題をさけるためにjee5/booking
のサンプルでは、このjarファイルを使用することにします。
ここにはWeblogic 10.3をインストールするために簡単な手順があります。詳細を知りたい場合や問題を抱えた場合は、BEAのドキュメントを参照してください。 Weblogic 10.3 ドキュメントセンタ . ここで、グラフィカルインストーラーを用いて RHEL バージョン5 をインストールします。
10.3 用の上のリンクにしたがってあなたの環境に正しいバージョンをインストールしてください。これを使用するためにはオラクルのアカウントを使用してサインアップする必要があります。
実行形式のserver103_XX.bin
ファイルを変更する必要があるかもしれません。
chmod a+x server103_XX.bin(パーミッションの変更)
インストールを実行します。
./server103_XX.bin
グラフィカルインストールをロードしたら、BEAのホームロケーションに設定する必要があります。このロケーションはBEAのアプリケーションがインストールされている場所です。このロケーションはこのドキュメント上では、 $BEA_HOME
として知られています。
/jboss/apps/bea
インストールタイプとして、Complete
を選択してください。完全インストールのすべてのオプションは必要ありません(strutsやbeehiveのライブラリのように)が、入れても害はありません。
次のページにあるコンポーネントのインストールはデフォルトのままにしてください。
これらの使用説明は(以降参照)はWeblogicのJSF 1.2ライブラリをデプロイそして設定するためのものです。そのままの状態ではWeblogicに入っているJSFライブラリはアクティブになりません。詳細は、 Weblogic 10.3 Configuring JSF and JSTL Libraries を参照してください。
管理コンソールでは、左側のメニューを使用して、Deployments
ページに進みます。
デプロイメントテーブルにの一番上にあるInstall
ボタンを選択したら
ディレクトリブラウザを使って$BEA_HOME/wlserver_10.3/common/deployable-libraries
ディレクトリに進みます。 jsf-1.2.war
アーカイブを選択したらNext
ボタンをクリックします。
Install this deployment as a library
が選択されていることを確認してください。Install Application Assistant
ページにある Next
ボタンをクリックしてください。
Optional Settings
ページにあるNext
ボタンをクリックしてください。
Yes, take me to the deployment's configuration screen.
が選択されていることを確認してください。 Review your choices and click Finish
ページにある Finish
ボタンをクリックしてください。
自動的にデプロイされているアプリケーションより優先的にデプロイされるためにSettings for jsf(1.2,1.2.3.1)
ページで Deployment Order
に 99
を設定してください。それから、 Save
ボタンをクリックしてください。
JSFを動かすために必要なもう一つのステップがあります。いくつかの理由のために、そのステップで jsf-api.jar
の中にあるクラスが、アプリケーションのデプロイメントの間に見つからないことさえあります。動かすための唯一の方法は、ドメインの中にある共有ライブラリにあるjsf-1.2.war
から javax.jsf_1.2.0.0.jar
(jsf-api.jar)を置くことです。このことはサーバの再起動を必要とします。
この問題はWeblogicが内部的に生成したスタブクラスが不適切に残したEJBのあるメソッドが原因なのです。この結果、次のエラーメッセージがデプロイ中に表示されます。
<<Error > <EJB > <BEA-012036 > <Compiling generated EJB classes produced the following Java compiler error message: <Compilation Error > TimerServiceDispatcher_qzt5w2_Impl.java: The type TimerServiceDispatcher_qzt5w2_Impl must implement the inherited abstract method TimerServiceDispatcher_qzt5w2_Intf.scheduleTimedEvent(String, Schedule, Object[]) <Compilation Error > TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java: Type mismatch: cannot convert from Object to Timer <Compilation Error > TimerServiceDispatcher_qzt5w2_LocalTimerServiceDispatcherImpl.java: Type mismatch: cannot convert from Object to Timer > <Error > <Deployer > <BEA-149265 > <Failure occurred in the execution of deployment request with ID '1223409267344' for task '0'. Error is: 'weblogic.application.ModuleException: Exception preparing module: EJBModule(jboss-seam.jar)
この節では、 jee5/booking
をサーバにアップし起動する手順について記述します。
このサンプルではメモリ内にある高速データベースを使用します。そして的確にデータソースを設定します。管理コンソールでウィザードを用いてこのページを設定してください。
管理コンソールの誘導にしたがってサーバを起動してください。 項37.1.3. 「ドメインの 起動/停止/アクセス 方法」
左側のツリーにあるseam_examples - Services- JDBC - Data Sources
に進んでください。
データソーステーブルの一番上にあるNew
ボタンを選択してください。
下記に従って入力してください。
Name: seam-jee5-ds
JNDI Name: seam-jee5-ds
Database Type and Driver: other
Next
ボタンを選択してください。
Transaction Options
ページにあるNext
ボタンを選択してください。
Connection Properties
ページに従って入力してください。
Database Name: hsqldb
Host Name: 127.0.0.1
Port: 9001
Username:sa
パスワードは未入力にしてください。
Password: 未入力
Next
ボタンを選択してください。
Connection Properties
ページに従って入力してください。
Driver Class Name: org.hsqldb.jdbcDriver
URL: jdbc:hsqldb:.
Username: sa
Password: 未入力
残りのフィールドはそのままにしておいてください。
Next
ボタンを選択してください。
ただ一つのAdminServer
の中にあるデータソース用に目的のドメインを選択してください。
では、WeblogicサーバにデプロイするためにSeamアプリケーションの調整を始める最後の準備ができました。
resources/META-INF/persistence.xml
jta-data-source
をあなたが入力したものに変更してください。
<jta-data-source
>seam-jee5-ds</jta-data-source
>
weblogicのサポート用にこれらの二つのプロパティを追加します。
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.WeblogicTransactionManagerLookup"/>
resources/META-INF/weblogic-application.xml
resources/META-INF/ejb-jar.xml
assembly-descriptor
要素をこのように修正してください。
<assembly-descriptor>
<interceptor-binding
>
<ejb-name
>AuthenticatorAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>BookingListAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>RegisterAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>ChangePasswordAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>HotelBookingAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>HotelSearchingAction</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
<interceptor-binding
>
<ejb-name
>EjbSynchronizations</ejb-name>
<interceptor-class
>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor
>
resources/WEB-INF/weblogic.xml
ビルドファイルをいくつか修正する必要があります。そうすれば、jboss-seam.jar
をアプリケーションにデプロイすることができます。
build.xml
次に従って、 weblogic-application.xml
に追加する必要があります。
<!-- Resources to go in the ear -->
<fileset id="ear.resources" dir="${resources.dir}">
<include name="META-INF/application.xml" />
<include name="META-INF/weblogic-application.xml" />
<include name="META-INF/*-service.xml" />
<include name="META-INF/*-xmbean.xml" />
<include name="treecache.xml" />
<include name="*.jpdl.xml" />
<exclude name=".gpd.*" />
<include name="*.cfg.xml" />
<include name="*.xsd" />
</fileset
>
$SEAM/lib/interop/jboss-seam-wls-compatible.jar
項37.2.1. 「Weblogic上のEJB3の問題」 で議論された変さらには二つの方法があります。
このjarファイルの名前を変更して、オリジナルの $SEAM/lib/jboss-seam.jar
ファイルと置き換えてください。このアプローチはパッケージのEAR
アーカイブに一切の変更を必要としませんが、オリジナルのjboss-seam.jar
ファイルには変更を必要とします。
もう一つの方法はパッケージのEAR
アーカイブを修正して jboss-seam.jar
の中にあるアーカイブと手動で置き換えることです。これはオリジナルのjarファイルはそのままにしますが、アーカイブがパッケージされている場合必ず手動で置き換えることが必要となります。
jboss-seam-wls-compatible.jar
をハンドリングするために最初の方法を選択した場合、 jee5/booking
サンプルのベースディレクトリでant archive
でアプリケーションをビルドすることができます。
デプロイモードでWeblogicドメインを作成することを選択したから、ドメインにある自動デプロイディレクトリにEARファイルを置くだけで、アプリケーションをデプロイできるのです。
cp ./dist/jboss-seam-jee5.ear $BEA_HOME/user_projects/domains/seam_examples/autodeploy
http://localhost:7001/seam-jee5/
でアプリケーションをチェックアウトしてください。
最初にサンプルをWeblogic 10.x用についてビルドます。デプロイするためにいくつかステップを踏みます。それからWeblogicのバージョンの違いについてJBoss ASと共に説明します。
このサンプルはWeblogicのJSFライブラリが項37.1.4. 「 WeblogicのJSFサポートの設定」で設定されていることを想定していることに注意してください。
最初にデータソースを設定して、次にアプリケーションをビルドして、最後にデプロイしてくさい。
データソースの設定はjee5 項37.2.2.1. 「hsqlのデータソースの設定」にとてもよく似ています。そのセクションの手順に従ってください、ただし必要な箇所では入力してください。
DataSource Name: seam-jpa-ds
JNDI Name: seam-jpa-ds
Building it only requires running the correct ant command:
ant weblogic10.xml
This will create a container specific distribution and exploded archive directories.
項37.1.2. 「Weblogicのドメインを作成する。」に従ってWeblogicをインストールするときは、開発モードにあるドメインを選択します。このことは、アプリケーションをデプロイするのに、自動デプロイディレクトリにただそのアプリケーションをコピーさえすればよいことを示しています。
cp ./dist-weblogic10/jboss-seam-jpa.war $BEA_HOME/user_projects/domains/seam_examples/autodeploy
http://localhost:7001/jboss-seam-jpa/
に従って、アプリケーションをチェックアウトしてください。
Weblogicの10.xと9.2のサンプルの間にはいくつかの違いがあります。
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app
xmlns="http://www.bea.com/ns/weblogic/90"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/90
http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd">
<library-ref>
<library-name
>jsf</library-name>
<specification-version
>1.2</specification-version>
<implementation-version
>1.2</implementation-version>
<exact-match
>false</exact-match>
</library-ref>
<container-descriptor>
<prefer-web-inf-classes
>true</prefer-web-inf-classes>
</container-descriptor>
</weblogic-web-app
>
<property name="hibernate.query.factory_class"
value="org.hibernate.hql.classic.ClassicQueryTranslatorFactory"/>
WEB-INF/components.xml
— Weblogic 10.xバージョンでJPAエントリのトランザクションが次の追加によって使用可能になります。
<transaction:entity-transaction entity-manager="#{em}"/>
WEB-INF/web.xml
— jsf-impl.jar
は WAR
ではないので、このリスナーは設定変更する必要があります。
<listener>
<listener-class
>com.sun.faces.config.ConfigureListener</listener-class>
</listener
>
最初にいくつかの設定ファイルを変更したり削除したりします。それからアプリケーションとともにデプロイされているライブラリを更新します。
build.xml
resources/META-INF/persistence-dev.xml
jta-data-source
をseam-gen-ds
に変更してください(Weblogicの管理コンソールでデータソースを作成したときはこれをjndi-name
として使用してください)。
JPAトランザクションを使えるように、トランザクションの型をRESOURCE_LOCAL
に変更してください。
<persistence-unit name="weblogic_example" transaction-type="RESOURCE_LOCAL"
>
Weblogicをサポートするために下記のプロパティを追加/修正してください。
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.WeblogicTransactionManagerLookup"/>
本番環境用のプロファイルを使用してWeblogicにデプロイしたい場合、persistence-prod.xml
を変更する必要があります。
resource/WEB-INF/weblogic.xml
このファイルを作成して description of WEB-INF/weblogic.xmlに従い変更する必要があるでしょう。
resource/WEB-INF/components.xml
JPAトランザクションを使いたいので、Seamに通知するために以下を追加する必要があります。
<transaction:entity-transaction entity-manager="#{entityManager}"/>
また、トランザクションの名前空間とスキーマの場所をドキュメントの一番上に追加する必要があります。
xmlns:transaction="http://jboss.com/products/seam/transaction"
http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.1.xsd
resource/WEB-INF/web.xml
WEB-INF/web.xml
— jsf-impl.jar
は WAR
ではないので、このリスナーは設定変更する必要があります。
<listener>
<listener-class
>com.sun.faces.config.ConfigureListener</listener-class>
</listener
>
resources/WEB-INF/jboss-web.xml
JBoss ASにデプロイするのでなければ、このファイルは削除することができます(jboss-app.xml
はJBoss ASは独立してクラスロードを可能にするために使用します)。
resources/*-ds.xml
JBoss ASにデプロイしないとき、これらのファイルを削除することができます。これらのファイルはJBoss ASでデータソースを定義するファイルであり、Weblogicでは管理コンソールを使用します。
seam-gen
アプリケーションは、上記のjpa
サンプルと非常によく似たライブラリの依存関係を持っています。項37.3.2. 「Weblogic 10.xでの違い」を参照してください。下記はその依存関係を取得するためにアプリケーション内で必要になる変更です。
build.xml — 今ここでbuild.xml
を調整する必要があります。war
ターゲットを見つけて、その最後に以下を追加してください。
<copy todir="${war.dir}/WEB-INF/lib">
<fileset dir="${lib.dir}">
<!-- Misc 3rd party -->
<include name="commons-logging.jar" />
<include name="dom4j.jar" />
<include name="javassist.jar" />
<include name="cglib.jar" />
<include name="antlr.jar" />
<!-- Hibernate -->
<include name="hibernate.jar" />
<include name="hibernate-commons-annotations.jar" />
<include name="hibernate-annotations.jar" />
<include name="hibernate-entitymanager.jar" />
<include name="hibernate-validator.jar" />
<include name="jboss-common-core.jar" />
<include name="concurrent.jar" />
</fileset>
</copy
>
あと残っているのはアプリケーションのデプロイです。データソースの設定を行い、アプリケーションをビルドしてデプロイしてください。
データソースを設定は、jee5 項37.2.2.1. 「hsqlのデータソースの設定」とほぼ同じです。ここにあるリストを除いて、リンクの指示に従ってください。
DataSource Name: seam-gen-ds
JNDI Name: seam-gen-ds
項37.1.2. 「Weblogicのドメインを作成する。」に従ってWeblogicをインストールするときは、開発モードにあるドメインを選択します。このことは、アプリケーションをデプロイするのに、自動デプロイディレクトリにただそのアプリケーションをコピーさえすればよいことを示しています。
cp ./dist/weblogic_example.war /jboss/apps/bea/user_projects/domains/seam_examples/autodeploy
http://localhost:7001/weblogic_example/
に従ってアプリケーションをチェックアウトしてください。
Websphere 6.1.x is IBM's application server offering. The latest release is 6.1.0.19 which does not have EJB3
or JEE5
support. There is a recently released (Nov 07) EJB3
feature pack which provides some support for EJB3
and JPA
. Currently there is no true JEE5
offering from IBM. This causes some issues with Seam integration with applications that use EJB3.
First we will go over some basic information about the Websphere environment that we used for these examples. After a good deal of research and work we were able to get EJB3 applications to function correctly. We will go over the details of those steps with the jee5 example. We will also deploy the JPA example application.
The EJB3 feature pack that we installed came with the 6.1.0.13 patch version of Websphere. Installing the feature pack does not ensure that your server will have the proper environment for EJB3 applications. Be sure that as part of the installation of the feature pack you follow the instructions to create a new server profile with the EJB3 feature pack enabled, or augment one of your existing ones. This can also be done after the installation by running the profile management tool.
It is highly recommended to patch Websphere by latest fix pack, at the time of this writing it is 6.1.0.19
There are times that restarting the server will be required after deploying or changes the examples in this chapter. Its does not seem like every change requires a restart. If you get errors or exceptions after modifying a property or deploying an application try to restart the server.
There are a couple of Websphere custom properties that are required for Seam integration. These properties are not needed specifically for Seam, but work around some issues with Websphere. These are set following the instructions here : Setting web container custom properties
prependSlashToResource = "true"
— This solves a fairly common issue with Websphere where applications are not using a leading "/" when attempting to access resources. If this is not set then a java.net.MalformedURLException
will be thrown. With this property set you will still see warnings, but the resources will be retrieved as expected.
com.ibm.ws.webcontainer.invokefilterscompatibility = "true"
— This solves an issue with Websphere where it throws a FileNotFoundException
when a web application attempts to access a file resource that does not actually exist on disk. This is a common practice in modern web applications where filters or servlets are used to process resource requests like these. This issue manifests itself as failures to retrieve JavaScript, CSS, images, etc... when requesting a web page.
PK33090; 6.1: A filter that serves a file does not pop-up an alert message
雛形のサンプルに対して必要となる構成ファイルの変更点は以下の通りです。
resources/WEB-INF/components.xml
<core:init jndi-pattern="java:comp/env/jboss-seam-jee5/#{ejbName}" debug="true"/>
resources/WEB-INF/web.xml
<xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- JEE5 EJB3 names -->
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/AuthenticatorAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.example.booking.Authenticator</local
>
</ejb-local-ref
>
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/BookingListAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.example.booking.BookingList</local
>
</ejb-local-ref
>
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/RegisterAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.example.booking.Register</local
>
</ejb-local-ref
>
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/ChangePasswordAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home
>
<local
>org.jboss.seam.example.booking.ChangePassword</local
>
</ejb-local-ref
>
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/HotelBookingAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.example.booking.HotelBooking</local
>
</ejb-local-ref
>
<ejb-local-ref
>
<ejb-ref-name
>jboss-seam-jee5/HotelSearchingAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home
>
<local
>org.jboss.seam.example.booking.HotelSearching</local
>
</ejb-local-ref
>
<ejb-local-ref>
<ejb-ref-name
>jboss-seam-jee5/EjbSynchronizations</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type>
<local-home
></local-home>
<local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
</ejb-local-ref
>
<ejb-local-ref>
<ejb-ref-name
>myapp/EjbSynchronizations</ejb-ref-name>
<ejb-ref-type
>Session</ejb-ref-type>
<local-home
></local-home>
<local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
</ejb-local-ref>
web.xml の中で上記の設定を行わなければ、以下のエラーが発生します。
Name comp/env/myapp/EjbSynchronizations not found in context java:
resources/META-INF/persistence.xml
<jta-data-source
>DefaultDatasource</jta-data-source>
Hibernate プロパティを設定する必要があります。まず最初に GlassFish プロパティをコメントアウトします。次に以下のプロパティを追加修正する必要があります。
<!--<property name="hibernate.transaction.flush_before_completion" value="true"/>-->
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.dialect" value="GlassfishDerbyDialect"/>
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.WebSphereExtendedJTATransactionLookup"/>
resources/GlassfishDerbyDialect.class
cp ../../jpa/resources-websphere61/WEB-INF/classes/GlassfishDerbyDialect.class ./resources
resources/import.sql
cp ../../jpa/resources-websphere61/import.sql ./resources
<fileset id="jar.resources" dir="${resources.dir}">
<include name="import.sql" />
<include name="seam.properties" />
<include name="GlassfishDerbyDialect.class" />
<include name="META-INF/persistence.xml" />
<include name="META-INF/ejb-jar.xml" />
</fileset
>
<!--<include name="lib/log4j.jar" />-->
<include name="lib/el-api.jar" />
<include name="lib/el-ri.jar" />
<include name="lib/jsf-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/jboss-seam.jar" />
</fileset
>
<fileset id="war.lib.extras" dir="${seam.dir}"
>
<include name="lib/richfaces-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/el-ri.jar" />
</fileset
>
最後に残された作業は、 ant archive
タスクを実行することです。アプリケーションは、jee5/booking/dist
ディレクトリにビルドされます。
必要なものはすべて所定の位置に揃いました。残されたことはデプロイすることです - あとわずか数ステップの手順です。
デプロイには、WebSphere の管理コンソールを使用します。従来どおり従われなければならない手順とヒントがあります。
The steps below are for the Websphere version stated above, yours may be slightly different.
Log in to the administration console
https://localhost:9043/ibm/console
Access the Enterprise Application
menu option under the Applications
top menu.
アプリケーションのインストールが完了しましたが、実行の前にいくつかの調整をする必要があります。
Enterprise Applications (エンタープライズアプリケーション)
テーブルで Seam Booking
リンクを選択するところから始めてください。
Change the Class loader order
combo box to Classes loaded with application class loader first
.
ラジオボタンで Classes loaded with application class loader first
を選択してください。
Open the following file in a text editor of your choice:
$WebSphereInstall/$yourServerName/profiles/$yourProfileName/config/cells/ $yourCellName/applications/Seam Booking.ear/deployments/ Seam Booking/deployment.xml
Modify the following line so that PARENT_FIRST
is now PARENT_LAST
:
<classloader xmi:id="Classloader_#######" mode="PARENT_FIRST"/>
You can now access the application at http://localhost:9080/seam-jee5/
.
サンプルには、Websphere も含めた多くのコンテナ用の構成とビルドスクリプトが既に用意されています。
最初に行うことは、サンプルのビルトとデプロイです。そのあとに必要な設定変更を行います。
これは jee5
サンプルの 項38.2.3. 「Websphere へのアプリケーションのデプロイ」 と類似していますが、多くの手順は必要ありません。
Enterprise Applications (エンタープライズアプリケーション)
テーブルから Install (インストール)
ボタンを選択してください。
アプリケーションのインストール準備
Browse to the examples/jpa/dist-websphere61/jboss-seam-jpa.war
file using the file upload widget.
Context root
テキストボックスに jboss-seam-jpa
を入力してください。
Next
ボタンを選択してください。
Next
ボタンを選択して、3 ページ先まで進んでください。そこまで変更は必要ありません。
Summary (要約)
ページ
お望みなら設定を確認して、Finish (完了)
ボタンを選択してアプリケーションのインストールを完了してください。インストールが完了して Save (保存)
リンクを選択すると Enterprise Applications (エンタープライズアプリケーション)
テーブルに戻ります。
As with the jee5
example there are some class loader changes needed before we start the application. Follow the instructions at installation adjustments for jee5 example but exchange jboss-seam-jpa
for Seam Booking
.
最後にアプリケーションを開始するには、Enterprise Applications (エンタープライズアプリケーション)
テーブルでアプリケーションを選択して Start (開始)
ボタンをクリックしてください。
http://localhost:9080/jboss-seam-jpa/index.html
からアプリケーションにアクセスできます。
seam-gen
は、開発者が素早くアプリケーションを準備して動作させるのにとても役に立つツールで、独自の機能を追加するための雛形を用意します。seam-gen
はそのままで JBoss AS で動作するように構成されたアプリケーションを生成します。以下の手順では、Websphere 上で動作させるために必要なステップを示します。項38.2. 「jee5/booking
サンプル 」 で述べたように、EJB3 アプリケーションを動作させるには変更が必要です。このセクションでは、その正確な手順を示します。
resources/META-INF/persistence-dev.xml
jta-data-source
を DefaultDatasource
に修正してください。組み込みの Websphere DB を使用します。
以下のプロパティを追加修正してください。項38.2. 「jee5/booking
サンプル 」 に詳細が説明されています。
<property name="hibernate.dialect" value="GlassfishDerbyDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.WebSphereExtendedJTATransactionLookup"/>
EntityManagerFactory を定義する JBoss AS 固有のメソッドを取り除いてください。
<property
name="jboss.entity.manager.factory.jndi.name"
value="java:/websphere_exampleEntityManagerFactory">
prod プロファイルを使用して Websphere にデプロイしたければ、persistence-prod.xml
も同様に修正する必要があります。
resources/GlassfishDerbyDialect.class
As with other examples we need to include this class for DB support. It can be copied from the jpa
example into the websphere_example/resources
directory.
cp $SEAM/examples/jpa/resources-websphere61/WEB-INF/classes/GlassfishDerbyDialect.class ./resources
resources/META-INF/jboss-app.xml
JBoss AS にはデプロイしないのでこのファイルを削除できます (JBoss AS では jboss-app.xml
を使用して、クラスローディングの分離を有効にします)
resources/*-ds.xml
JBoss AS にはデプロイしないのでこのファイルを削除できます (これらのファイルは、JBoss AS ではデータソースを定義していますが、Websphere ではデフォルトのデータソースを使用しています)
resources/WEB-INF/components.xml
コンテナ管理トランザクション統合を有効にします - <transaction:ejb-transaction />
コンポーネントと、その名前空間宣言 xmlns:transaction="http://jboss.com/products/seam/transaction"
を追記してください
jndi-pattern
を java:comp/env/websphere_example/#{ejbName}
に修正します
このサンプルでは、managed-persistence-context
は必要ではないので、そのエントリは削除します。
<persistence:managed-persistence-context name="entityManager"
auto-create="true"
persistence-unit-jndi-name="java:/websphere_exampleEntityManagerFactory"/>
resources/WEB-INF/web.xml
Websphere does not support Servlet 2.5
, it required Servlet 2.4
. For this change we need to adjust the top of the web.xml
file to look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
As with the jee5/booking
example we need to add EJB references to the web.xml. These references require the empty local-home
to flag them for Websphere to perform the proper binding.
<ejb-local-ref
>
<ejb-ref-name
>websphere_example/AuthenticatorAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.tutorial.websphere.action.Authenticator</local
>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name
>websphere_example/EjbSynchronizations</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type>
<local-home
></local-home>
<local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
</ejb-local-ref
>
このアプリケーションは、jee5/booking
サンプルと同様の変更が必要となります。
デフォルトのターゲットを archive
に変更します (Websphere への自動的なデプロイを行いません)。
<project name="websphere_example" default="archive" basedir=".">
<copy todir="${war.dir}">
<fileset dir="${basedir}/resources" >
<include name="*.drl" />
</fileset>
</copy>
<target name="jar" depends="compile,copyclasses"
description="Build the distribution .jar file">
<copy todir="${jar.dir}">
<fileset dir="${basedir}/resources">
<include name="seam.properties" />
<include name="*.drl" />
<include name="GlassfishDerbyDialect.class" />
</fileset>
</copy>
...
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset
>
So that the whole archive
task looks like:
<target name="archive" depends="jar,war,ear"
description="Package the archives">
<jar jarfile="${dist.dir}/${project.name}.jar" basedir="${jar.dir}"/>
<jar jarfile="${dist.dir}/${project.name}.war" basedir="${war.dir}"/>
<jar jarfile="${dist.dir}/${project.name}.ear">
<fileset dir="${ear.dir}"/>
<fileset dir="${dist.dir}">
<include name="${project.name}.jar"/>
<include name="${project.name}.war"/>
</fileset>
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset>
</jar>
</target
>
<target name="ear" description="Build the EAR">
<copy todir="${ear.dir}">
<fileset dir="${basedir}/resources">
<include name="*jpdl.xml" />
<include name="*hibernate.cfg.xml" />
<include name="jbpm.cfg.xml" />
</fileset>
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset>
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/core.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
</fileset>
</copy>
<copy todir="${ear.dir}/META-INF">
<fileset dir="${basedir}/resources/META-INF">
<include name="application.xml" />
<include name="jboss-app.xml" />
</fileset>
</copy>
</target
>
<!-- Hibernate and deps -->
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/jboss-common-core.jar" />
JSF dependencies. You will need to copy the el-ri.jar
from the $SEAM/examples/jpa/lib
directory.
<!-- jsf libs -->
<include name="lib/jsf-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/el-api.jar" />
<include name="lib/el-ri.jar"/>
<!-- 3rd party and supporting jars -->
<!--<include name="lib/log4j.jar" />-->
<include name="lib/javassist.jar"/>
<include name="lib/dom4j.jar" />
<include name="lib/concurrent.jar" />
<include name="lib/cglib.jar"/>
<include name="lib/asm.jar"/>
<include name="lib/antlr.jar" />
<include name="lib/commons-logging.jar" />
<include name="lib/commons-collections.jar" />
jboss-seam.jar
- this is needed in both the ear
base and /lib
directory.
<!-- seam jar -->
<include name="lib/jboss-seam.jar" />
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/core.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
<!-- Hibernate and deps -->
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/jboss-common-core.jar" />
<!-- jsf libs -->
<include name="lib/jsf-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/el-api.jar" />
<include name="lib/el-ri.jar"/>
<!-- 3rd party and supporting jars -->
<include name="lib/javassist.jar"/>
<include name="lib/dom4j.jar" />
<include name="lib/concurrent.jar" />
<include name="lib/cglib.jar"/>
<include name="lib/asm.jar"/>
<include name="lib/antlr.jar" />
<include name="lib/commons-logging.jar" />
<include name="lib/commons-collections.jar" />
<!-- seam jar -->
<include name="lib/jboss-seam.jar" />
</fileset
>
<copy todir="${war.dir}/WEB-INF/lib">
<fileset dir="${lib.dir}">
<includesfile name="deployed-jars-war.list" />
<include name="jsf-impl.jar" />
<include name="el-ri.jar" />
<exclude name="jboss-seam-gen.jar" />
</fileset>
</copy
>
アプリケーションをデプロイするには、 項38.2.3. 「Websphere へのアプリケーションのデプロイ」 の手順に従ってください 。但し、jboss-seam-jee5
の代わりにこのプロジェクト websphere_example
へ置き換えて使用してください。
http://localhost:9080/websphere_example/index.html
をブラウズしてアプリケーションを確認してください。
GlassFish は、Java EE 5 を完全に実装したオープンソースのアプリケーションサーバーです。最新の安定リリースは v2 UR2 です。
まず最初に GlassFish 環境について述べて、どのように jee5 サンプルをデプロイするのかを詳しく説明します。次に、jpa サンプルアプリケーションをデプロイします。最後に、seam-gen により生成されたアプリケーションをどのようにして GlassFish 上で動作させるのかを示します。
この章のサンプルと情報は、すべて執筆時の GlassFish の最新バージョンに基づいています。
GlassFish をダウンロードして、インストールしてください。
$ java -Xmx256m -jar glassfish-installer-v2ur2-b04-linux.jar
インストールした後に、GlassFish をセットアップしてください。
$ cd glassfish; ant -f setup.xml
作成されたドメインの名前はdomain1
です。
次に、組み込み JavaDB サーバを起動します。
$ bin/asadmin start-database
JavaDB は、HSQLDB が JBoss AS に含まれているのと同様に、GlassFish の組み込みデータベースです。
最後に、GlassFish サーバを起動してください。
$ bin/asadmin start-domain domain1
The web adminstration console is available at http://localhost:4848/
. You can access the web admin console with the default username (admin
) and password (adminadmin
). Alternatively, you could copy EAR/WAR file to glassfish/domains/domain1/autodeploy
to deploy it.
サーバとデータベースを停止するには、以下のようにしてください。
$ bin/asadmin stop-domain domain1; bin/asadmin stop-database
サンプルには、GlassFish も含めた多くのコンテナ用の構成とビルドスクリプトが既に用意されています。
サンプルをビルドするためには、glassfish
ターゲットを使用します。
$ ant glassfish
これによりコンテナに対応した dist-glassfish
ディレクトリと exploded-archives-glassfish
ディレクトリが作成されます。
This is very similar to the jee5
example at 項39.2.1. 「GlassFish へのアプリケーションのデプロイ」.
管理コンソールへログインしてください。
http://localhost:4848
左側のサイドメニュー Applications (アプリケーション)
の下にあるメニューオプションで Web Applications (Web アプリケーション)
にアクセスしてください。
アプリケーションのインストール準備
ブラウザで examples/jpa/dist-glassfish/jboss-seam-jpa.war
を指定してください。
OK
ボタンを選択してください。
http://localhost:8081/jboss-seam-jpa/
からアプリケーションにアクセスできます。
examples/jpa/resources-glassfish/WEB-INF/classes/GlassFishDerbyDialect.class
is a hack to get around a Derby bug in GlassFish server. You must use it as your Hibernate dialect if you use Derby with GlassFish. seam-gen
is a very useful tool for developers to quickly get an application up and running, and provides a foundation to add your own functionality. Out of box seam-gen
will produce applications configured to run on JBoss AS. These instructions will show the steps needed to get it to run on GlassFish. As stated above in 項39.2. 「jee5/booking
サンプル」 it's easy to deploy either an EJB3 or a Seam POJOs application on Glassfish.
resources/META-INF/persistence-dev.xml
jta-data-source
を jdbc/__default
に修正してください。組み込みの GlassFish Derby DB を使用します。
Add or change the properties below. These are described in detail at 項39.2. 「jee5/booking
サンプル」:
<property name="hibernate.dialect" value="GlassFishDerbyDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.SunONETransactionManagerLookup"/>
prod プロファイルを使用して GlassFish にデプロイしたければ、persistence-prod.xml
も同様に修正する必要があります。
resources/GlassFishDerbyDialect.class
他のサンプルと同様に、データベースサポートのためのこのクラスを含める必要があります。jpa
サンプルから seamgen_example/resources
ディレクトリへコピーしてください。
$ cp \ $SEAM_DIST/examples/jpa/resources-glassfish/WEB-INF/classes/GlassFishDerbyDialect.class \ ./resources
resources/META-INF/jboss-app.xml
JBoss AS にはデプロイしないのでこのファイルを削除できます (JBoss AS では jboss-app.xml
を使用して、クラスローディングの分離を有効にします)
resources/*-ds.xml
JBoss AS にはデプロイしないのでこのファイルを削除できます (これらのファイルは、JBoss AS ではデータソースを定義していますが、GlassFish ではデフォルトのデータソースを使用しています)
resources/WEB-INF/components.xml
コンテナ管理トランザクション統合を有効にします - <transaction:ejb-transaction/>
コンポーネントと、その名前空間宣言 xmlns:transaction="http://jboss.com/products/seam/transaction"
を追記してください
Alter the jndi-pattern
to java:comp/env/seamgen_example/#{ejbName}/local
resources/WEB-INF/web.xml
As with the jee5/booking
example we need to add EJB references to the web.xml. These references require the empty local-home
to flag them for GlassFish to perform the proper binding.
<ejb-local-ref
>
<ejb-ref-name
>seamgen_example/AuthenticatorAction</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type
>
<local-home
></local-home>
<local
>org.jboss.seam.tutorial.glassfish.action.Authenticator</local
>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name
>seamgen_example/EjbSynchronizations</ejb-ref-name
>
<ejb-ref-type
>Session</ejb-ref-type>
<local-home
></local-home>
<local
>org.jboss.seam.transaction.LocalEjbSynchronizations</local>
</ejb-local-ref
>
このアプリケーションは、jee5/booking
サンプルと同様の変更が必要となります。
デフォルトのターゲットを archive
に変更します (GlassFish への自動的なデプロイを行いません)。
<project name="seamgen_example" default="archive" basedir=".">
<copy todir="${war.dir}">
<fileset dir="${basedir}/resources">
<include name="*.drl" />
</fileset
>
</copy
>
<target name="jar" depends="compile,copyclasses" description="Build the distribution .jar file">
<copy todir="${jar.dir}">
<fileset dir="${basedir}/resources">
<include name="seam.properties" />
<include name="*.drl" />
<include name="GlassFishDerbyDialect.class" />
</fileset
>
</copy>
...
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" >
</fileset
>
So that the whole archive
task looks like:
<target name="archive" depends="jar,war,ear" description="Package the archives">
<jar jarfile="${dist.dir}/${project.name}.jar" basedir="${jar.dir}"/>
<jar jarfile="${dist.dir}/${project.name}.war" basedir="${war.dir}"/>
<jar jarfile="${dist.dir}/${project.name}.ear">
<fileset dir="${ear.dir}"/>
<fileset dir="${dist.dir}">
<include name="${project.name}.jar"/>
<include name="${project.name}.war"/>
</fileset>
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset>
</jar>
</target
>
<target name="ear" description="Build the EAR">
<copy todir="${ear.dir}">
<fileset dir="${basedir}/resources">
<include name="*jpdl.xml" />
<include name="*hibernate.cfg.xml" />
<include name="jbpm.cfg.xml" />
</fileset>
<fileset dir="${lib.dir}">
<include name="jboss-seam.jar" />
</fileset>
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/core.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
</fileset>
</copy>
<copy todir="${ear.dir}/META-INF">
<fileset dir="${basedir}/resources/META-INF">
<include name="application.xml" />
<include name="jboss-app.xml" />
</fileset>
</copy>
</target
>
<!-- Hibernate and deps -->
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/jboss-common-core.jar"/>
Add JSF dependencies. You will need to copy the el-ri.jar
from the $SEAM_DIST/lib
directory.
<!-- jsf libs -->
<include name="lib/jsf-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/el-api.jar" />
<include name="lib/el-ri.jar"/>
<!-- 3rd party and supporting jars -->
<include name="lib/javassist.jar"/>
<include name="lib/dom4j.jar"/>
<include name="lib/concurrent.jar" />
<include name="lib/cglib.jar"/>
<include name="lib/asm.jar"/>
<include name="lib/antlr.jar" />
<include name="lib/commons-logging.jar" />
<include name="lib/commons-collections.jar" />
<fileset dir="${basedir}">
<include name="lib/jbpm*.jar" />
<include name="lib/jboss-el.jar" />
<include name="lib/drools-*.jar"/>
<include name="lib/core.jar"/>
<include name="lib/janino*.jar"/>
<include name="lib/antlr-*.jar"/>
<include name="lib/mvel*.jar"/>
<include name="lib/richfaces-api*.jar" />
<!-- Hibernate and deps -->
<include name="lib/hibernate.jar"/>
<include name="lib/hibernate-commons-annotations.jar"/>
<include name="lib/hibernate-annotations.jar"/>
<include name="lib/hibernate-entitymanager.jar"/>
<include name="lib/hibernate-validator.jar"/>
<include name="lib/jboss-common-core.jar" />
<!-- jsf libs -->
<include name="lib/jsf-api.jar" />
<include name="lib/jsf-impl.jar" />
<include name="lib/el-api.jar" />
<include name="lib/el-ri.jar"/>
<!-- 3rd party and supporting jars -->
<include name="lib/javassist.jar" />
<include name="lib/dom4j.jar" />
<include name="lib/concurrent.jar" />
<include name="lib/cglib.jar" />
<include name="lib/asm.jar" />
<include name="lib/antlr.jar" />
<include name="lib/commons-logging.jar" />
<include name="lib/commons-collections.jar" />
</fileset
>
アプリケーションをデプロイするには、 項39.2.1. 「GlassFish へのアプリケーションのデプロイ」 の手順に従ってください 。但し、jboss-seam-jee5
の代わりにこのプロジェクト seamgen-example
へ置き換えて使用してください。
http://localhost:8081/seamgen_example/
をブラウズしてアプリケーションを確認してください。
ここでは Maven の使い方については触れませんが利用できる基本的な POM をいくつか見てみることにします。
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam</artifactId>
</dependency
>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam-ui</artifactId>
</dependency
>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam-pdf</artifactId>
</dependency
>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam-remoting</artifactId>
</dependency
>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam-ioc</artifactId>
</dependency
>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam-ioc</artifactId>
</dependency
>
このサンプルの POM は Seam、 JPA (Hibernate により提供される)、 Hibernate Validator を提供しています。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion
>4.0.0</modelVersion>
<groupId
>org.jboss.seam.example/groupId>
<artifactId
>my-project</artifactId>
<version
>1.0</version>
<name
>My Seam Project</name>
<packaging
>jar</packaging>
<repositories>
<repository>
<id
>repository.jboss.org</id>
<name
>JBoss Repository</name>
<url
>http://repository.jboss.org/maven2</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId
>org.hibernate</groupId>
<artifactId
>hibernate-validator</artifactId>
<version
>3.0.0.GA</version>
</dependency>
<dependency>
<groupId
>org.hibernate</groupId>
<artifactId
>hibernate-annotations</artifactId>
<version
>3.3.0.ga</version>
</dependency>
<dependency>
<groupId
>org.hibernate</groupId>
<artifactId
>hibernate-entitymanager</artifactId>
<version
>3.3.1.ga</version>
</dependency>
<dependency>
<groupId
>org.jboss.seam</groupId>
<artifactId
>jboss-seam</artifactId>
<version
>2.0.0.GA</version>
</dependency>
</dependencies>
</project
>