Faces Flowについての解説
- JSF 2.2 Use Faces Flow 手っ取り早く見るのにはベストかも。
- Java EE 7 Tutorial
- JSF 2.2 Faces Flow サンプルコードとビデオがある draft段階?のようで、最終仕様と記述方法が異なる。
Faces Flow
画面と画面遷移をセットにして部品化する仕組み。
フローを定義する。
フローは、おおまかに言えば、画面と画面遷移のセット。フローは有向グラフ(ノードとエッジの集合)として表現される。
Faces Flowにおけるノードは以下の5種類がある。(JavadocのFlowHandler)
- View
- 通常の、FaceletやJSPで作成したビュー。
- Return
- フローの呼び出し元に戻る出力を表す。
- Faces Flow Call
- フローから別のフローの呼び出しを表す。
- Switch
- EL式のリストで、最初にtrueを返した式に応じて次のノードが決定される。
- Method Call
- 任意のアプリケーション路実行の呼び出し。呼び出し後、その出力によってナビゲーションルールに従った遷移をさせることが可能。
フローの入口となるノードを一つ定める必要がある。これを開始ノードと呼ぶ。必ずしもViewノードでなくてもよい。
フローはネストすることが可能。フローから別のフローを呼び出したら、呼び出されたフローがReturnノードに到達したときに元のフローに返ってくる。
フローは次のどちらの方法でも定義できる。
- XMLで定義する
- FlowBilderクラスを使って、プログラムで定義する。
フローをXMLで定義
ファイル名とファイルの配置場所については後述。
記述例(JavaEE 7 Tutorial より引用)
<faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<!-- ★フロー1個を定義。idで名前を定義 --> <flow-definition id="joinFlow"> <!-- ★ Return ノードの定義 必須。 --> <flow-return id="returnFromJoinFlow"> <from-outcome>#{joinFlowBean.returnValue}</from-outcome> </flow-return>
<!-- ★ このフローの入力パラメータの定義 受け取った値は、EL式でバインドされたプロパティに代入される。 --> <inbound-parameter> <name>param1FromCheckoutFlow</name> <value>#{flowScope.param1Value}</value> </inbound-parameter> <inbound-parameter> <name>param2FromCheckoutFlow</name> <value>#{flowScope.param2Value}</value> </inbound-parameter>
<!-- ★ Faces Flow Callノードの定義。他のフローを呼び出す場合に使う。--> <flow-call id="callcheckoutFlow"> <flow-reference> <!-- どのフローを呼び出すか --> <flow-id>checkoutFlow</flow-id> </flow-reference> <!-- 呼び出し時の引数。名前と値をそれぞれ定義。 名前は、呼び出し先のinbound-parameterで定義した名前と一致させる。--> <outbound-parameter> <name>param1FromJoinFlow</name> <!-- 値は、この例では固定的な文字列になってしまっているが、 通常はEL式を記述して、Beanのプロパティの値を、引数の値として使う。--> <value>param1 joinFlow value</value> </outbound-parameter> <outbound-parameter> <name>param2FromJoinFlow</name> <value>param2 joinFlow value</value> </outbound-parameter> </flow-call> </flow-definition> </faces-config>
このほかにも、
- faces-config.xml同様、navigation-ruleが定義できる。
- フローに入る時と出るときの処理をinitializer, finalizerとして定義できるようだ。
- 開始ノードは start-nodeで指定できる。
- 省略時はフローの名前と同じ名前のビュー。この例では、joinFlow.xhtml。(JSF2.2仕様 11.4.3.3 Packaging Flows in Directories)
記述に使うタグは、JSF Schemaの当該部分の説明を参照。
この定義ファイルを作成し、中身を完全に空にした場合は、デフォルトの定義が適用される(JSF2.2仕様 11.4.3.3 Packaging Flows in Directories)。ただ、現実には実用にならないと思われる。デフォルトの定義では、flow-returnのfrom-outcomeが固定的に/<flowName>-return になる。これは、/<flowName>-return.xhtmlというビューを用意しなければならないし、フロー完了時にその画面に遷移するという動作になる。
フローには、「フローを定義したドキュメントのID(definingDocumentId)」という属性がある。フローはアプリケーションから切り離してライブラリ的に作成できるようで、フロー名の衝突を避けるために名前空間的に利用されると思われる。フローを呼び出すときにフローの名前を指定するが、厳密には、フローのIDとdefiningDocumentIdのペアを指定することになっている。ただし、デフォルトでは「フローを定義したドキュメントのID」は""(空文字列)であり、フロー呼び出し時もdefiningDocumentIdを省略できる。<faces-config>配下に<name>を記述した場合は、それが「フローを定義したドキュメントのID」として扱われる。(JSF2.2仕様 11.4.3.1 Defining Flows)
フローをプログラムで定義
あまりメリットは無いように思われる。XMLの記述のほうがわかりやすい。また、動的にフローを構築する必要はあまり無いのではないか。
フロー(の.xhtmlと.xml)を置く場所
XMLファイルは以下のどちらかのパス・ファイル名として作成する。
- /<flowName>/<flowName>-flow.xml
- /WEB-INF/<flowName>/<flowName>-flow.xml
例
- /foo/foo-flow.xml
- /WEB-INF/foo/foo-flow.xml
ここで、<flowName>部分は1階層。つまり、/foo/bar/foobar-flow.xml というのはNG。(JSF2.2仕様 11.4.3.3 Packaging Flows in Directories)
ビュー(XHTML)は、上記のディレクトリに格納しておくと、自動的にViewノードと認識されるため、<viewName>-flow.xmlへの記述が省略できる。上記のディレクトリ以外に格納することも可(らしい)。
フローを単独のJARファイルとしてパッケージ化することもできる。(JSF2.2仕様 11.4.3.2 Packaging Faces Flows in JAR Files)
Flowスコープ
フローが終わるまでインスタンスが維持される。
正確な定義がどこにあるか不明。
FlowScoped:view-sourceからたどれる説明をみても、漠然としている。
import javax.faces.flow.FlowScoped; import javax.inject.Named;
@Named @FlowScoped(value="joinFlow") public class JoinController implements Serializable {
@FlowScopedにはvalue="..."でフローのidの指定が必須。(名前が必須なのは、フローの呼び出しがネストしても、どのフローに対応付くかわかるようにするためか?)
フローの呼び出し
まだどのフローにも入っていない場合
ビューに遷移するのにビューIDを指定していたのと同じように、フローのIDを指定する。フローIDを指定して遷移すると、フローの開始ノードに遷移する。
<h:link outcome="joinFlow" value="joinFlowに入る"/>
呼び出し先のフローに入力パラメータが定義されていても、渡す手段がないと思われる。
いずれかのフロー内の場合
まず、.xmlなどでFaces Flow Callノード(<flow-call>)を定義し、id="..." で名前を付け、呼び出し先のフロー、渡す引数を定義する。 (※)
そして、ビュー遷移時にビューIDを指定するのと同様に、flow-callのidを指定する。
(前掲の記述と別の例となってしまうが、記述例を挙げる)
呼び出し元フロー内のある画面のXHTML
<h:commandButton value="編集" action="#{memberController.doEnterEditor(item)}"/>
memberController(Java)
public String doEnterEditor(Member member) { this.member = member; return "callMemberEditor"; }
呼び出し元フローのxxx-flow.xml
<flow-call id="callMemberEditor"> <flow-reference> <flow-id>member-editor</flow-id> </flow-reference> <outbound-parameter> <name>member</name> <value>#{memberController.member}</value> </outbound-parameter> </flow-call>
呼び出し先フローのxxx-flow.xml
<flow-definition id="member-editor"> <inbound-parameter> <name>member</name> <value>#{memberEditorController.member}</value> </inbound-parameter>
※ これは、呼び出し先のフローが同じでも、渡す引数が異なるのであれば、異なるFaces Flow Callノードを定義しなければならない、ということを表している。また、引数を受け渡す手段として、Javaプログラムからは引数の値を一旦どこかのプロパティに置き、XMLでそのプロパティの場所を指し示す、というまわりくどい手段をとっている(とらざるを得ない)ことも表している。JSFはビュー間の値の受け渡しが困難な仕組みであることがよく表れている。
フローからの戻り
遷移先のビューIDを指定するのと同様に、Returnノードのid (<flow-return>のid属性で定義した名前)を指定する。
戻るとどのビューに遷移するのか、については、
- flow-returnのfrom-outcomeで指定した値を、outcome値として扱い、
- 戻り先のnavigation-ruleに基づいて遷移先を決定する、
のではないかと思われる。JSF2.2仕様 7.4.2 Default NavigationHandler Algorithm に説明があるのではないかと思うが、複雑で読み切れない。
フローからフローを呼び出す場合には、呼び出し元のフローのXMLにnavigation-ruleを書いておくことで、戻ってきたあとに呼び出し元フローのどの画面に遷移するかをコントロールできる。
記述例
/member/member-flow.xml
<navigation-rule> <from-view-id>/member-editor/*</from-view-id> <!-- callMemberEditor, member-editor ではヒットしない。 --> <navigation-case> <from-outcome>return</from-outcome> <to-view-id>/member/member.xhtml</to-view-id> <!-- member.xhtml というように相対パスで書くと、/member.xhtmlと解釈されてエラーになる --> </navigation-case> </navigation-rule>
<flow-call id="callMemberEditor"> <flow-reference> <flow-id>member-editor</flow-id> </flow-reference> <outbound-parameter> <name>member</name> <value>#{memberController.member}</value> </outbound-parameter> </flow-call>
この例では、呼び出し先のフロー member-editorは、/member-editor/member-editor-flow.xmlに定義されている。member-editor-flow.xmlではflow-returnを次のように定義している。
<flow-return id="return"> <from-outcome>return</from-outcome> </flow-return>
member-flow.xmlには、navigation-ruleを定義し、outcomeとして"return"を受け取った場合の遷移先を定義する。不可解なことに、to-view-idは相対パス(member.xhtml)では正常に遷移できない(GlassFish 4.0で確認)。また、from-view-idは、呼び出し先フロー内のビューIDを指定する必要があるようだ。フローIDやflow-callのidでは意図通りに動作しない。
フロー呼び出し時には値を引き渡せたが、フローからの戻りでは引き渡せない。
留意点、不可解な点
(GlassFish 4.0での動作に基づく)
flow-returnのfrom-outcome
- *-flow.xmlのflow-returnのfrom-outcomeでViewIDを指定するのが前提となっているように思われる。Java EE 7 Tutorialの例はそうなっている。ViewIDを指定するには、戻り先のビュー構成について知っている必要があり、再利用性が低い。
- from-outcomeでoutcome値を返し、呼び出し元の*-flow.xmlでnavigation-ruleを書くことで一見動作するように見えるが、h:link outcome="xxx"で xxxにflow-returnのidを指定した場合に、そのlinkで遷移した(フローから戻った)先の画面で異常が出ることがある。バグっぽい動作。本来navigation-ruleどおりに動作すべきなのか、navigation-ruleが無視されるべきなのかわからない。
フロー内からフロー外の画面への遷移
- h:linkのoutcomeに、フロー外部のViewIDを指定することはできない。outcomeにはflow-returnのidを指定し、そのfrom-outcomeにViewIDを指定すれば、フロー外の画面へ遷移できる。
- しかし、フロー外部の画面に脱出したいケースでは記述が増えて手間である。 また、画面共通にナビゲーションバーを付けようとした場合には、その全リンク先についてflow-returnの定義が必要になる。
フローからの戻り
- flowから出るときに、値を呼び出し元に返す機構はない。呼び出し時に器を渡して、それに入れさせればよいのか?
Java EE 7 Tutorialの例にならうのであれば、
- flow呼び出し時には、戻り先のViewIDを渡す。呼び出されたflowは、flow-returnのfrom-outcomeに# {....}を使ってそのViewIDをoutcome値とする。この方法により、前述のとおり、戻り先のビューのIDを事前に知っておく必要はなくなる。しかし、flowをスタックしている恩恵にあずかれない・・・。
- flow外からflowの呼び出し時には引数を渡せないので、・・・どうするんだろう。@PostConstructとRequestスコープのBeanを併用して渡す?トップレベルからフローに組み込む?
その他
- Flowを使うと、URLには特殊なパラメータがいくつか追加される。自動的にClientWindowの機能がONになるためと思われる(コンテキストパラメタ javax.faces.CLIENT_WINDOW_MODE に "url"が指定されたかのように動作する)。(JSF2.2仕様 11.4.3 Faces Flows)
JavaServer Faces Specification Version 2.2
JSF仕様の該当部分を翻訳してみたが、記述例がないので、これだけでは理解できない。(泣)
7.5 FlowHandler
どんなJSFアプリも有効グラフとしてモデル化できる。その有効グラフでは、ノードがビューで、線はビュー間の遷移を表す。
Faces Flowは、関連するビューと線を一緒にカプセル化する機能を提供することによって、
この有効グラフにいくつかの別の種類のノードを導入する。
アプリケーションは、機能のモジュールの組み合わせとして作成されるが、
各モジュールは well-definedな入口/出口条件を持ち、
各モジュール内のノード間で状態を共有することができる。
この機能は
Oracle Fusion Middleware の ADF Task Flowの設計に、
また、Spring Web FlowとApache MyFaces CODIに、非常に強く影響を受けている。
この機能の正規の仕様は、Javadocのjavax.faces.flow.FlowHandler、および、Section 7.4
“NavigationHandler”の関連する要求事項を参照されたし。
この節では、使い方の例、機能の手引きを説明することにより、本仕様の他の部分のうち、本機能と関わりのある部分が理解できるようにする。
7.5.1 Non-normative example
機能の概要を説明するための単純な例を説明する。
本機能のすべての特徴を利用しているわけではない。
この例は、2つのflowを持ち、それぞれ互いを引数付きで呼び出す。
flowの外のビューはどちらのflowにもリンクできる。これらのflowの名前をそれぞれflow-a, flow-bとする。
(図)
この図は以下のルールにしたがって書かれている。
- ビューノードは矩形
- faces flow return ノードは円
- faces flow call ノードは角の欠けた矩形
- @FlowScoped Beanは角丸矩形
- 開始ノードは"start"と印付け
- 入力/出力引数は名前の一覧で(?) (are listed by name)
- 矢印は、ノード間の有効な遷移
これらのflowは、構成要素の名前を除いて同一であり、それぞれ以下のプロパティを持つ。
- 3つのビューノード。そのうちひとつは、明示的に開始ノードとされたもの。
- 1つのfaces flow returnノード。それぞれ、出力"return1"を返す。
- 1つのflow call ノード。それは他のフローを呼び出す。2つの出力引数付きで。その引数の名前は、他方のflowと対応(一致? match up with)する。
- 2つの入力引数。名前は、他方のflowと対応(一致?)する。
上記の説明に出てきた各種のノードの種類については、javax.faces.flow.FlowHandlerのJavadocに定義されている。
上記のflowを含んだbasic_faces_flow_call.warという簡単なWebアプリを考えてみる。
ファイルの配置は次のとおり。ここではMaven war packagingを使っている。
(ディレクトリツリーの図)
この例を完成させるために、
先のフローの実行を細かく見てみよう。
これらのフローを含んだアプリがデプロイされると、実行環境はflowの定義を発見し、それらを内部フローのデータ構造(internal flow data structure)に追加する。
flowのひとつは、flow-b-flow.xmlに定義されている。
これはXMLファイルで、Section 11.4 “Application Configuration Resources”で説明されているApplication Configuration Resources syntaxに合うようになっている。
もうひとつのflowは、FlowA.javaで定義されており、
@FlowDefinition アノテーションの付いたメソッドを持つクラスとなっている。
flowの発見が完了すると、アプリケーションスコープの、スレッドセーフなデータ構造がjavax.faces.flow.FlowHandlerシングルトンオブジェクトから利用可能になり、このデータ構造にflowの定義が含まれている。このデータ構造には、javax.faces.flow.Flow API経由でアクセス可能である(?)(This data structure is navigable by the runtime via the javax.faces.flow.Flow API.)。
ユーザーエージェントが http://localhost:8080/basic_faces_flow_call/faces/index.xhtml にアクセスすると、2つのボタンが配置されたページが表示される。2つのボタンのactionはそれぞれflow-aとflow-bである。どちらかのボタンをクリックすると、対応するflowに入る。ここでは、flow-a ボタンをクリックしたとしよう。@FlowScopedのFlow_a_Beanがコンテナによってインスタンス化され、ただちに、その開始ノード(flow-a.xhtml)に遷移する。ユーザーは続いてnext_a.xhtmlに進むボタンをクリックし、次いでnext_b.xhtmlへのボタンをクリックする。そのページにはボタンがあり、そのアクションはcallBとなっている。このボタンをクリックすると対応する名前のfaces flow call nodeが活性化される。そのfaces flow call nodeは指定された出力パラメータを用意し、Flow_a_Beanを非活性化させ、flow-bを呼ぶ。
flow-bに入るとき、@FlowScopedのFlow_b_Beanがコンテナによってインスタンス化され、flow-aからの出力引数がflow-bの対応する入力引数に対応付けられ(are matched up with)、開始ノード、この場合はflow-b.xhtmlに遷移する。ユーザーは続いてnext_a.xhtmlに進むボタンをクリックし、次いでnext_b.xhtmlに進むボタンをクリックする。そのページにはボタンがあり、そのアクションはtaskFlowReturn1である。このボタンをクリックすると、Flow_b_Beanは非活性化され、return1という名前のビューへの遷移が実行される。