SeamFramework.orgCommunity Documentation
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あるいはSeam ナビゲーション規則の利用 - ステートレスなナビゲーションモデル
jPDL の利用 - ステートフルなナビゲーションモデル
簡単なアプリケーションではステートレスなナビゲーションモデルで十分です。 とても複雑なアプリケーションは場所に応じて両方を使用します。 それぞれのモデルはそれぞれの強みも弱みもあります。
ステートレスなモデルは 一組の名前の付いた論理的なイベントの結果 (outcome) から 直接、結果として生じるビューのマッピングを定義します。 ナビゲーション規則は、どのページがイベントのソースであったかということ以外、 アプリケーションによって保持されたどのような状態も全く気にしません。 これは、アクションリスナーメソッドがページフローを決めなければならないことがあることを意味しています。 なぜなら、アクションリスナーメソッドだけがアプリケーションの現在の状態にアクセスできるからです。
これは JSF ナビゲーション規則を使用したページフローの例です。
<navigation-rule>
<from-view-id
>/numberGuess.jsp</from-view-id>
<navigation-case>
<from-outcome
>guess</from-outcome>
<to-view-id
>/numberGuess.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>win</from-outcome>
<to-view-id
>/win.jsp</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome
>lose</from-outcome>
<to-view-id
>/lose.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule
>
これは Seam ナビゲーション規則を使用したページフローの例です。
<page view-id="/numberGuess.jsp">
<navigation>
<rule if-outcome="guess">
<redirect view-id="/numberGuess.jsp"/>
</rule>
<rule if-outcome="win">
<redirect view-id="/win.jsp"/>
</rule>
<rule if-outcome="lose">
<redirect view-id="/lose.jsp"/>
</rule>
</navigation>
</page
>
ナビゲーション規則が冗長過ぎると考えるならば、 アクションリスナーメソッドから直接、ビューIDを返すことが可能です。
public String guess() {
if (guess==randomNumber) return "/win.jsp";
if (++guessCount==maxGuesses) return "/lose.jsp";
return null;
}
これは、リダイレクトの結果であることに留意ください。 リダイレクト中に使用するパラメータを指定することも可能です。
public String search() {
return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}
ステートフルなモデルは名前の付いた論理的なアプリケーションの状態間で起こる遷移の組み合わせを定義します。 このモデルではjPDL ページフロー定義中に、どのようなユーザーインタラクションのフロー表現も可能であり、 インタラクションのフローを全く知らないアクションリスナーメソッドを書くことも可能です。
これは jPDL を使用したページフロー定義の例です。
<pageflow-definition name="numberGuess">
<start-page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</start-page>
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
<decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
<transition name="true" to="lose"/>
<transition name="false" to="displayGuess"/>
</decision>
<page name="win" view-id="/win.jsp">
<redirect/>
<end-conversation />
</page>
<page name="lose" view-id="/lose.jsp">
<redirect/>
<end-conversation />
</page>
</pageflow-definition
>
ここですぐに気づく 二つのことがあります。
JSF/Seam ナビゲーション規則は、より 簡単です。 (しかし、これは根底となる Java コードがより複雑化であるという事実をあいまいにしています。)
jPDL はJSP や Java コードを見る必要がなく、 即座にユーザーインタラクションの理解ができます。
それに加えてステートフルモデルはもっと 制約的 です。 各論理的な状態 (ページフローの各ステップ) に対して他の状態に遷移可能な制約された組み合わせがあります。 ステートレスモデルはアドホックな モデルです。 それはアプリケーションではなく、 比較的制約のない、ユーザーが次に行きたいところを決めるフリーフォームナビゲーションに適しています。
ステートフル / ステートレスナビゲーションの違いは、 典型的なモーダル / モーダレスインタラクションの考え方ととてもよく似ています。 さて、アプリケーションをモーダルな振る舞いから回避することは、 対話を持つ 1 つの主な理由ですが、 Seam アプリケーションは、 通常、単純な意味でのモーダルではありません。 しかし、Seam アプリケーションは、 特定な対話レベルで、モーダル可能であり、しばしばそうです。 モーダルな振る舞いは、 可能な限り回避すべきものとして知られています。 ユーザーがしたいことの順番を予測することは、とても困難です。 しかし、ステートフルモデルの存在意義があるのは疑う余地はありません。
二つのモデルの最大の違いは、 戻るボタンの振る舞いです。
JSF あるいは Seam ナビゲーション規則が使用される場合、 Seam は、ユーザーに戻る、進む、更新ボタンの自由なナビゲーションを可能にします。 これが発生したとき、 内部的な対話状態の一貫性を保持することは、 アプリケーションの責任です。 Struts や WebWork のような対話モデルをサポートしない WEB アプリケーションフレームワーク、 そして、EJB ステートレスセッションBean や Spring framework のようなステートレスコンポーネントモデルの組み合わせの経験は、 多くの開発者にこれをすることは、ほとんど不可能であることを教えていました。 しかし、Seam のコンテキストでの経験から、 ステートフルセッション Bean に裏付けられた明確な対話モデルがあるところでは、 それは実際とても簡単です。 通常、それは、アクションリスナーメソッドの最初に、 no-conversation-view-id アノテーションと null チェックの使用を組合わせるのと同じ位に簡単です。 私たちは、フリーフォームナビゲーションのサポートは、 ほぼいつも要求されるものと考えています。
この場合、no-conversation-view-id
の宣言は pages.xml
で行います。 対話中のレンダリングされたページからの要求の場合、 それは Seam にリダイレクトを指示し、対話はもはや存在しないことを知らせます。
<page view-id="/checkout.xhtml"
no-conversation-view-id="/main.xhtml"/>
On the other hand, in the stateful model, using the back button is interpreted as an undefined transition back to a previous state. Since the stateful model enforces a defined set of transitions from the current state, the back button is not permitted by default in the stateful model! Seam transparently detects the use of the back button, and blocks any attempt to perform an action from a previous, "stale" page, and simply redirects the user to the "current" page (and displays a faces message). Whether you consider this a feature or a limitation of the stateful model depends upon your point of view: as an application developer, it is a feature; as a user, it might be frustrating! You can enable backbutton navigation from a particular page node by setting back="enabled"
.
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
This allows navigation via the back button from the checkout
state to any previous state!
もちろん、ページフロー時のレンダリングされたページからの要求の場合に何をするのか、そして、その際にページフローでの対話は存在していないのか、といったことを 定義しなければなりません。この場合、no-conversation-view-id
の宣言は、ページフロー定義で行います:
<page name="checkout"
view-id="/checkout.xhtml"
back="enabled"
no-conversation-view-id="/main.xhtml">
<redirect/>
<transition to="checkout"/>
<transition name="complete" to="complete"/>
</page
>
実際には両ナビゲーションモデルとも使い道がありますが、どんなときにどちらのモデルの方が適切かを理解するために、これから簡単に学んでいきます。
Seam の jBPM 関連のコンポーネントをインストールし、Seam アーカイブ ( seam.properties
ファイルを含むアーカイブ ) の中にページフロー定義の場所を ( 標準の jpdl.xml
拡張を使用して ) 指示する必要があります。 この components.xml に Seam 設定を指定することができます。
<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
>
If we are beginning the pageflow during the RENDER_RESPONSE
phase — during a @Factory
or @Create
method, for example — we consider ourselves to be already at the page being rendered, and use a <start-page>
node as the first node in the pageflow, as in the example above.
しかし、ページフローがアクションリスナー呼び出しの結果として開始される場合、 アクションリスナーの結果 (outcome) は、レンダリングされる最初のページを決定します。 この場合、ページフローの最初のノードとして <start-state>
を使用し、 それぞれの可能な結果 (outcome) のために遷移を宣言します。
<pageflow-definition name="viewEditDocument">
<start-state name="start">
<transition name="documentFound" to="displayDocument"/>
<transition name="documentNotFound" to="notFound"/>
</start-state>
<page name="displayDocument" view-id="/document.jsp">
<transition name="edit" to="editDocument"/>
<transition name="done" to="main"/>
</page>
...
<page name="notFound" view-id="/404.jsp">
<end-conversation/>
</page>
</pageflow-definition
>
各 <page>
ノードは、システムがユーザー入力を待っている状態を表します。
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
view-id
はJSFのビューIDです。 <redirect/>
要素はJSFナビゲーションルールにある<redirect/>
と同じで、ブラウザの再表示ボタンの問題に対応するためのpost-then-redirectといった振る舞いと同じ効果をもたらします。(Seamはこれらブラウザのリダイレクトをこえて対話コンテキストを渡していきますのでRuby on Rails の "flash"のようなものはSeamでは必要はありません)
遷移名は、numberGuess.jsp
において、 ボタン あるいは、リンクをクリックすることによって起動された JSF 結果 (outcome) の名前です。
<h:commandButton type="submit" value="Guess" action="guess"/>
遷移が、このボタンをクリックすることによって起動されるときに、 numberGuess
コンポーネントの guess ()
メソッドと呼び出すことによって、 jBPM は、遷移のアクションを起動します。 jPDL においてアクションを指定するために使わるシンタックスは、 JSF EL 式とよく似ていること、 そして、遷移のアクションハンドラは、 ちょうど現在の Seam コンテキストにおける Seam コンポーネントのメソッドであることに注意してください。 従って、JSF イベントのために既に持っているものと、ちょうど同じ jBPM イベントのモデルを持ちます。 (一貫した原則 (The One Kind of Stuff principle))
nullでのoutcome の場合 (例えば、action
が定義されていないコマンドボタン)、 もし、名前のない遷移があるならば、Seam は遷移するためのシグナルを送ります。 あるいは、もし、すべての遷移が名前を持つならば、単純にページを再表示します。 従って、サンプルのページフローとボタンは少し単純化できます。
<h:commandButton type="submit" value="Guess"/>
これは以下の名前のない遷移でのアクションを実行します。
<page name="displayGuess" view-id="/numberGuess.jsp">
<redirect/>
<transition to="evaluateGuess">
<action expression="#{numberGuess.guess}" />
</transition>
</page
>
ボタンにアクションメソッドを呼ばせることも可能です。 この場合アクション結果 (outcome) が遷移を決定します。
<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>
<page name="displayGuess" view-id="/numberGuess.jsp">
<transition name="correctGuess" to="win"/>
<transition name="incorrectGuess" to="evaluateGuess"/>
</page
>
しかし、これは質の悪いスタイルだと考えます。 なぜならフロー制御の責任をページフロー定義の外側の他のコンポーネントに移動しているからです。 ページフローに関連することを局所化することは、より良いことです。
一般的にページフローを定義するときにjPDLのより強力な機能は必要としていません。しかし<decision>
ノードは必要です。
<decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision
>
decision ( 意志決定 ) は Seam コンテキスト中では JSF EL 式によって評価されます。
<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
>
ページフローを含むことができ、他のページフローが実行中の際にとめることも可能です。<process-state>
ノードは外部のページフローをとめて指定されたページフローを開始します。
<process-state name="cheat">
<sub-process name="cheat"/>
<transition to="displayGuess"/>
</process-state
>
<start-state>
ノードで内側のフローは開始します。 <end-state>
ノードの到着すると、内側のフローは終了し、外側のフローの<process-state>
要素で定義された遷移から再開されます。
ビジネスプロセスは誰が、いつ実行されるべきかが明確に定義されたルールの上で、ユーザーやソフトウェアによって実行される明確に定義されたタスクの集合です。 Seam の jBPM インテグレーションはタスク一覧をユーザーに表示することやタスク管理を容易にします。また Seam はアプリケーションにビジネスプロセスに関する状態をビジネスプロセスコンテキストに保管することを可能にします。
<page>
の代わりに<task-node>
ノードを持つ以外、 簡単なビジネスプロセス定義はページフロー定義とほぼ同じであるように見えます。 (一貫した原則 (The One Kind of Stuff principle)) 長期間のビジネスプロセスにおいて、 待ち状態はシステムが、ユーザーがログインしタスクを実行するのを待っているところです。
<process-definition name="todo">
<start-state name="start">
<transition to="todo"/>
</start-state>
<task-node name="todo">
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task>
<transition to="done"/>
</task-node>
<end-state name="done"/>
</process-definition
>
同じプロジェクトの中にjPDL ビジネスプロセス定義と jPDL ページフロー定義を持つことは可能です。 そうであれば2 つの関係は ビジネスプロセス中の <task>
は ページフロー <process-definition>
全体と一致します。
jBPM を設定し、そのjBPMにビジネスプロセス定義の場所を指示する必要があります。
<bpm:jbpm>
<bpm:process-definitions>
<value
>todo.jpdl.xml</value>
</bpm:process-definitions>
</bpm:jbpm
>
jBPM プロセスのリスタートをまたいで永続化され、本番環境で Seam を使用する場合、アプリケーションのリスタートのたびにプロセス定義をインストールをしたくはありません。つまり本番環境ではSeamアプリケーションとは別に、jBPMに対してプロセス定義をデプロイする必要があります。そしてアプリケーションを開発するときには、components.xml
からプロセス定義だけをインストールします。
いつでも現在ログインしているユーザーを知っている必要があります。 jBPM はactor id と group actor idによってユーザーを識別します。 actor
と呼ばれる組み込み Seam コンポーネントを使用することにより現在の actor id を指定します。
@In Actor actor;
public String login() {
...
actor.setId( user.getUserName() );
actor.getGroupActorIds().addAll( user.getGroupNames() );
...
}
ビジネスプロセスインスタンスを初期化するためには @CreateProcess
アノテーションを使用します。
@CreateProcess(definition="todo")
public void createTodo() { ... }
また、 pages.xmlを使用してビジネスプロセスの初期化も行えます:
<page>
<create-process definition="todo" />
</page
>
プロセスがタスクノードに到着したときタスクインスタンスは作成されます。そのタスクインスタンスにはユーザーもしくはユーザーグループが割り当てられていなければなりません。アクターIDをハードコードもできますしSeamコンポーネントに委譲することもできます。
<task name="todo" description="#{todoList.description}">
<assignment actor-id="#{actor.id}"/>
</task
>
この場合、 単純に現在のユーザーにタスクを割り当てます。 タスクをプールに割り当てることもできます。
<task name="todo" description="#{todoList.description}">
<assignment pooled-actors="employees"/>
</task
>
いくつかの組み込み Seam コンポーネントによりタスクリストの表示が容易になっています。 pooledTaskInstanceList
は ユーザーが自分自身に割り当てることができるプールされたタスクのリストです。
<h:dataTable value="#{pooledTaskInstanceList}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
<s:link>
の代わりに普通の JSF <h:commandLink>
を使用することもできます。
<h:commandLink action="#{pooledTask.assignToCurrentActor}"
>
<f:param name="taskId" value="#{task.id}"/>
</h:commandLink
>
pooledTask
コンポーネントは単純にタスクを現在のユーザーに割り当てる組み込みコンポーネントです。
taskInstanceListForType
コンポーネントは、 現在のユーザーに割り当てられた特定タイプのタスクを含んでいます。
<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
<h:column>
<f:facet name="header"
>Description</f:facet>
<h:outputText value="#{task.description}"/>
</h:column>
<h:column>
<s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
</h:column
>
</h:dataTable
>
タスクの作業を開始させるために、リスナーメソッドに @StartTask
あるいは @BeginTask
を使用します。
@StartTask
public String start() { ... }
また、 タスクの実行を pages.xml を使用して始めることもできます:
<page>
<start-task />
</page
>
これらのアノテーションはビジネスプロセス全体に関して意味を持つ特殊な種類の対話を開始します。 この対話による処理はビジネスプロセスコンテキストの中で保持する状態にアクセスできます。
@EndTask
を使用して対話を終了する場合にSeam はタスクの完了サインを送信します。
@EndTask(transition="completed")
public String completed() { ... }
また、 pages.xmlも使用できます:
<page>
<end-task transition="completed" />
</page
>
pages.xmlに遷移をELを使って指定もできます。
この時点でjBPM はビジネスプロセス定義を引継ぎ、実行を続行します。 (さらに複雑なプロセスではプロセスの実行が可能となる前にタスクが完了される必要があります。)
複雑なビジネスプロセスの管理を実現する各種の高度な機能の全体的な概要については jBPM ドキュメントを参照してください。