カンバセーション(conversation)はJBoss Seamのカンバセーションから着想を得た機能で、sessionよりも小さい単位で一連の処理を扱うためのものです。
Ymirのカンバセーションには以下の特徴があります。
- カンバセーションは識別子としての名前を持ちます。
- カンバセーションにはオブジェクトをバインドすることができます。バインドされたオブジェクトは、カンバセーションが存在する間有効です。このスコープをカンバセーションスコープと呼びます。
- カンバセーションには複数のPageクラスを関連付けることができます。関連付けられたPageクラス毎にフェーズ(phase)という文字列を割り当てることができます。
- カンバセーションに関連付けられたPageクラスには、どのフェーズから遷移可能かという制約を付与することができます。許可されたフェーズ以外から遷移した場合は不正遷移を表す例外(IllegalTransitionRuntimeException)がスローされます。また、フェーズに関する制約があったとしても、今のフェーズから同じフェーズに遷移することは許されています(例:自画面に帰ってくるような遷移)。
- カンバセーションは開始(begin)指示によって開始されます。カンバセーションに参加していない状態で開始指示なしにカンバセーションに参加しようとした場合や、あるカンバセーションから開始指示なしに別のカンバセーションに遷移した場合は、IllegalTransitionRuntimeExceptionがスローされます。
- カンバセーションは終了(end)指示によって終了されます。終了するとカンバセーションは削除され、カンバセーションにバインドされたオブジェクトも自動的にアンバインドされます。ただし、終了せずに別のカンバセーションを開始することも許されています。この場合でも自動的にそれまでのカンバセーションは削除されます。
- カンバセーションは基本的に同一sessionにつき同時に1つしか存在できませんが、あるカンバセーションから別のカンバセーション(これをサブカンバセーション(sub conversation)と言います)を開始して終了後に元のカンバセーションに戻ってくるようにすることは可能です。
- カンバセーションに参加している状態で、カンバセーションに関連付けられていないPageに遷移することは可能です。この場合、その遷移自体行なわれないのと同じことになります。従って、元のカンバセーションに遷移を戻すこともできます。
以下、コメントの入力→確認→登録完了という一連の処理についてカンバセーションを定義する例を通してカンバセーション機能の使い方を説明します。
Pageにカンバセーションを関連付ける
Pageにカンバセーションを関連付けるには、次の例のようにPageクラスに@Conversationアノテーションを付与します。
なお、投稿完了画面を表示するためのCompletedPageクラスには@Conversationアノテーションを付与していませんが、これはこのページへはConfirmPageにてカンバセーションを終了してから遷移するような作りにしているためです。
@Conversation(name = "comment", phase = "input") public class InputPage extends InputPageBase { ... @Begin public void _get() { ... } ... } ... @Conversation(name = "comment", phase = "confirm", followAfter = "input") public class ConfirmPage extends ConfirmPageBase { ... @End public String _post_ok() { // コメントの投稿処理 .... return "redirect:comment_completed.html"; } ... } ... public class CompletedPage extends CompletedPageBase { ... }
nameプロパティにはカンバセーションの名前を指定します。上の例ではコメント投稿処理なので「comment」という名前にしています。
phaseプロパティにはこのPageがカンバセーション中のどういうフェーズを表すのかを文字列で指定します。phaseプロパティは省略可能ですが、極力指定しておくことをお勧めします。
カンバセーション中でページ遷移を決まった順序で行なわせたい場合は、followAfterプロパティで直前のフェーズを1つまたは複数指定します。followAfterプロパティが指定されると、指定されたフェーズからの遷移しか許可しないようになります。上の例では、confirmフェーズはinputフェーズからしか遷移できないように指定しています。
カンバセーションの開始と終了
カンバセーションは開始指示がないと開始できないため、開始指示を追加します。開始指示は@Beginアノテーションで行ないます。
public class InputPage extends InputPageBase { ... @Begin public void _get() { ... } ... }
@Beginアノテーションはアクションメソッドに付与します。_getメソッドに@Beginアノテーションを付与していますので、普通にInputPageのURLに遷移することでcomment カンバセーションが開始されます。
@Beginアノテーションが付与されているアクションが呼び出されてカンバセーションが開始した後に再度@Beginアノテーションが付与されているアノテーションが呼び出されると、例え同一のカンバセーションに関する@Beginであっても改めてカンバセーションが開始されます。この時カンバセーションの内容はクリアされます。これを避けるには、@Beginアノテーションのwhereプロパティを指定して下さい。whereプロパティにBeginCondition.EXCEPT_FOR_SAME_CONVERSATION_AND_SAME_PHASEを指定すると、同一のカンバセーションでかつ同一のフェーズからの遷移の場合はカンバセーションを開始しないようになります。BeginCondition.EXCEPT_FOR_SAME_CONVERSATIONを指定すると、同一のカンバセーションからの遷移の場合はカンバセーションを開始しないようになります。
カンバセーションを終了するには終了指示として@Endアノテーションを付与します。
public class ConfirmPage extends ConfirmPageBase { ... @End public String _post_ok() { // コメントの投稿処理 .... return "redirect:comment_completed.html"; } ... }
この例では、投稿確認ページで「OK」ボタンが押された場合に_post_okアクションが呼び出され、コメントの投稿処理が終わった後にカンバセーションが終了するようにしています。
ここで注意してほしいのは、カンバセーションの終了処理は@Endアノテーションが付与されているアクションの実行が完了した時点で行なわれるということです。このようにしているのは、@Endアノテーションが付与されているアクションの処理中にもカンバセーションスコープのオブジェクトを利用できるようにするためです。
なお、アクションの実行中に実行時例外等がスローされて処理が中断された場合でもカンバセーションは終了します。このため、アクションの中で何らかの判定を行なってその結果でカンバセーションを終了させるかどうかを決定したい場合は工夫が必要です。
一番簡単なのは、アクションの中でカンバセーションを終了させるかどうか判定し、終了させる場合は終了用のアクションにリダイレクトするようにしておき、そのアクションに@Endアノテーションを付与する方法です。
このような画面構成が取れない場合は、アクションの呼び出し前にYmirのconstraintの仕組みで判定を行なう方法を使うことができます。具体的には、「カンバセーションの終了条件を満たしていること」という制約を表す専用のConstraintクラスとアノテーションを作成し、そのConstraintクラスの中で判定を行なうようにします。または、Pageクラスにページ固有のバリデーションとしてカンバセーションの終了条件を満たしていることを確認するためのメソッドを作成し、該当アクション呼び出し時にだけこのバリデーションが呼ばれるようにしておくという方法もあります。
この他にも、Seasar2のインターセプタとして判定処理を実装し、該当アクションにそのインターセプタを掛けることでも条件分岐を実現することができます。
不正遷移の検出
不正遷移が検出されてIllegalTransitionRuntimeExceptionがスローされた場合でも、カンバセーションは終了しません。また、カンバセーションスコープ(後述)の内容もクリアされません。
カンバセーションスコープ
カンバセーションスコープにオブジェクトをバインドしたい場合や、カンバセーションスコープからオブジェクトを取り出したい場合は@Inアノテーションと@Outアノテーションを使用します。
@In(ConversationScope.class) public void setComment(CommentDto comment) { comment_ = comment; } ... @Out(ConversationScope.class) public CommentDto getComment() { return comment_; }
@Inアノテーションや@Outアノテーションの詳細については「オブジェクトスコープ」を参照して下さい。
サブカンバセーション(sub conversation)
例えばコメント投稿の画面遷移の途中で、認証が済んでいなければユーザ認証画面を経由させてから投稿画面に戻す、というような遷移を実現したいとします。このように、あるカンバセーション中から一時的に別のカンバセーションに遷移させ、そのカンバセーションが終了してから元のカンバセーションに復帰させたい場合はサブカンバセーション(sub conversation)を使います。
サブカンバセーションに遷移させるには@BeginSubConversationアノテーションを使用します。
@Conversation(name = "comment", phase = "start") public class StartPage extends StartPageBase { ... @BeginSubConversation(reenter = "redirect:comment_input.html") public String _post_logininput() { return "redirect:login_input.html"; } ... }
上の例では、コメント投稿開始ページ(StartPage)にて「ログイン」ボタンが押され、_post_logininputアクションが呼び出されて処理が完了したタイミングでログイン処理用のカンバセーションを開始するようになります。なお、サブカンバセーションの開始処理もカンバセーションの終了処理と同様、アクションの処理が完了した時点で行なわれますが、アクションの処理中に例外がスローされるとサブカンバセーションは開始されません。
@BeginSubConversationのreenterプロパティには、サブカンバセーションが終了した時に元々のカンバセーションに帰ってくるための遷移先をYmirのアクションの文字列の返り値と同じ形式で指定して下さい。上の例では、ログイン処理が終わった後にコメントの入力画面(comment_input.html)に遷移するように指定しています。
サブカンバセーションを終了するには特別な処理は必要ありません。普通のカンバセーションを終了するのと同じように@Endアノテーションで終了させるだけです。
@Conversation(name = "login", phase = "input") public class InputPage extends InputPageBase { ... @End public String _post_login() { // ログインのために必要な処理 ... return "redirect:login_completed.html"; } ... }
上の例では、ログインフォーム画面(InputPage)にて_post_loginアクションが呼び出された場合にloginカンバセーションが終了します。終了後の遷移先は通常はアクションの返り値に従ってlogin_completed.htmlになりますが、現在のカンバセーションがサブカンバセーションである場合はアクションの返り値は無視され、代わりにサブカンバセーション開始時に@BeginSubConversationアノテーションで指定されたreenterプロパティの値が使用されます。従って、上の例ではcomment_input.htmlに遷移することになります。
なお終了後の遷移先を差し替える都合上、サブカンバセーションを終了させる可能性のある@Endアノテーションは返り値型がStringまたはObjectまたはorg.seasar.ymir.Response型(Response型はymir-core-1.0.2から対応)であるアクションメソッドに付与する必要があります。それ以外の返り値型であるアクションメソッドに@Endを付与してそのアクションメソッド経由でサブカンバセーションを終了させようとした場合は実行時例外がスローされます。