2004年7月
概要この資料は、『 Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の資料に続く、より詳細なイベント処理フローとそれに関連する技術的内容を説明したものです。本書の前に『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』を読んで、基本的なデータ・バインディングの仕組みと利点を理解されることをお勧めします。
目次
ADF DataAction によるイベント処理の理解
サンプル・アプリケーションの概要
Webアプリケーションにおけるイベントとは?
Struts DispatchActionを利用したイベント処理
DispatchAction によるケース・スタディ
同機能の DataAction による実装
国際化対応への配慮
ADFバインディングと宣言的なイベントの利用
宣言的なイベント・ベースのページ・ナビゲーション
メソッド結果に対するデータ・バインディング
サービス・メソッドの戻り値へのバインディング
コレクション値プロパティに対するイテレータ
String 引数のあるメソッドへのバインディング
String および int 引数のあるメソッドへのバインディング
Beanデータ・コントロールの引数としての利用
カスタム・メソッド起動のために独立したデータアクションを使用する理由
最後に
より洗練された動的なWebアプリケーションを構築できるように、ADF DataAction によって、簡単で、イベント駆動のメカニズムが提供されています。これを利用すれば、ページ内のリンクやボタンをクリックしたときに処理すべき操作を容易に制御できるようになります。ここでは、理解を深めるために用意されたサンプル・アプリケーションを使って、その技術的背景を説明します。サンプル・アプリケーションはここからダウンロードできます:
DataActionEventsExample.zip.
| 注意: |
このサンプル・アプリケーションを実行するためには、接続ナビゲータの"データベース" の下にscott という名前の接続を作成する必要があります。 それは、ユーザーがアクセスできるデータベース上のSCOTT/TIGER スキーマを指していなければなりません。 |
図1 は、サンプル・アプリケーションの全体的なページフローを示しています。前述のリンクから zip ファイルをダウンロードして展開し、その中の
DataActionEventsExample.jws ワークスペースを開いて、
ViewController プロジェクト内のページ・フロー図を見ると、これを確認することができます。
ViewController プロジェクトを実行すると、次のようなページ(index.jsp)が表示されます(
図2)。
Struts DispatchAction Example(DispatchExample) 、
ADF DataPage Example(DataPageExample)、
Example2(DataPageExample2)、
ADF Declarative Example(DataPageDeclarative)はすべて、同じ機能をもつページで、整数入力値の増加、減少、値の更新を行います。ユーザーによる操作の結果、整数値が "10" になると、"You Win! " というメッセージが表示されるというものです。
このサンプルで提供されるさまざまなアプローチを考察することで、Oracle ADFとStrutsとの間のイベント処理に関する理解を深めることができるはずです。
Events, Actions, and Forwards Example(
UpdateEmp1および
UpdateEmp2)は、シンプルな従業員データの編集フォームを2つの方法で実現したもので、ADFのイベント処理メカニズムに関する興味深い事実を明らかにするでしょう。
Java開発者で、JavaBeansを作成したり、SwingベースのGUIアプリケーションを開発したことのある方であれば、Javaコンポーネントがイベント・セットを公開していることはご存知でしょう。例えば、Swingの
JButton オブジェクトは、
actionPerformed という名前のイベントを持っていて、ユーザーがボタンを押した時にそのイベントが起動されます。イベント処理を行うために、開発者は、イベントが起こると起動されるメソッド内に適切なコードを記述します。例えば、
(Update) ボタンが押されたときのために次のようにコードを記述します:
/** * Event handler for the actionPerformed event on JButton instance "updateButton". */ private void updateButton_actionPerformed(ActionEvent e) { // Do something here when the [Update] button is pressed }
JavaServer Faces が登場する以前までは、J2EEプラットフォームで、サーバー上でのWebベースのアプリケーションに対して、Swing で提供されているような機能(イベントなど)は用意されていませんでした。そのため、J2EE Web開発者は、それぞれの手法で、ユーザーがブラウザから実行したアクションを識別し、アプリケーションの中間層でアクションを実施させるようにしています。
よく使用される手法としては、
event というような名前のパラメータを用意して、その値で起動するイベントを指定します。たとえば、HTMLページに2つのボタンとリンクがあるとします:
このような場合、それぞれに対して
event という名前のパラメータを設定します:
<%-- Excerpt of code in SomePage.jsp --%> : <form action="SomePage.jsp" method="post"> : <input type="submit" name="event" value="Update"/> <input type="submit" name="event" value="Increment"/> <a href="SomePage.jsp?event=Decrement">Remove One</a> </form>
その後、JSPページ内で、または、Beanコードの中で、
event パラメータの値によって適切なイベント処理コードを実施させることができます:
<%-- Excerpt of code in SomePage.jsp --%> <% String event = request.getParameter("event"); if ("Update".equals(event)) { // code that handles the 'Update' event goes here } else if ("Increment".equals(event)) { // code that handles the 'Increment' event goes here } else if ("Decrement".equals(event)) { // code that handles the 'Decrement' event goes here } %> <form action="SomePage.jsp" method="post"> : <input type="submit" name="event" value="Update"/> <input type="submit" name="event" value="Increment"/> <a href="SomePage.jsp?event=Decrement">Remove One</a> </form>
このようなテクニックはSwingのような手法ほど美しくはありませんが、今日のJSPアプリケーションでは一般に用いられる標準的な手法です。
Struts開発者で、特に、コントローラ層のロジックとビュー層のJSPとの厳密な分離を好む人の中には、Strutsの DispatchActionを拡張してイベント処理をさせる手法を使う人もいます。
DispatchAction を使用すると、
eventパラメータの値に応じて、適切なイベント処理メソッドへディスパッチする機能を用意できます。
たとえば、"/SomePage"というパスで、
SomePage.jsp ページ上でユーザーがボタンやリンクをクリックした際に起動されるイベントを処理させる場合、
struts-config.xml で次のように構成します:
<action-mappings> : <action path="/SomePage" type="somepackage.SomePageAction" parameter="event" name="SomeFormBean"> <forward name="success" path="SomePage.jsp"/> </action> :
<action> タグの
parameter 属性が "
event" という値であることに注目してください。これが、イベントを通知するために使用されるHTTPリクエスト・パラメータ名を表します。
somepackage.SomePageAction クラスは、
DispatchAction の拡張クラスとして、次にあげるように、実行時に
eventパラメータに対して設定されるイベント名に一致する名前のメソッドが用意されている必要があります:
package somepackage; public class SomePageAction extends DispatchAction { /** * When the "event" parameter has the value "DoSomething", * the DispatchAction will delegate to this method to handle it. */ public ActionForward DoSomething(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { /* Do Something Here */ return mapping.findForward("success"); } /** * When the "event" parameter has the value "DoSomethingElse", * the DispatchAction will delegate to this method to handle it. */ public ActionForward DoSomethingElse(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { /* Do Something Else Here */ return mapping.findForward("success"); } /** * We have to implement this method to handle the case when * no "event" parameter is supplied as part of the request. * By default, the DispatchAction would throw an Exception. */ public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { /* Handle the no-event-happening case here */ return mapping.findForward("success"); } }
SomePage.jsp ページは、たとえば、次のようなコードになるでしょう:
<%-- Excerpt of code in SomePage.jsp using /SomePage.do based on DispatchAction --%> <form action="SomePage.do" method="post"> : <input type="submit" name="event" value="Update"/> <input type="submit" name="event" value="Increment"/> <a href="SomePage.do?event=Decrement">Remove One</a> </form>
このページでは「ポストバック」パターンを実装して、ボタンやリンクの処理先として
"SomePage.do" パスに再度戻ってくるようにしています。このようなアクション・マッピングとJSPの組み合わせの利用によって、コントローラ層とビュー層の機能を分離できます。
サンプルにあるDataActionEventsExampleワークスペースの DispatchExample アクションと
DispatchExample.jsp ページは、実行すると、
図3のようになります。この例では、Struts
DispatchActionを使用して、「ポストバック」パターンを実装しています。
この例では、シンプルな
ExampleFormBean というFormBean(
int型の
value という名前のプロパティをもつ)を使用しています:
package controller; import org.apache.struts.action.ActionForm; public class ExampleFormBean extends ActionForm { private int _value = 0; public ExampleFormBean() {} public void setValue(int value) { _value = value; } public int getValue() { return _value; } }
JDeveloper の Strutsページフロー図とプロパティ・インスペクタを使用して、
DispatchExample アクションでこの
ExampleFormBean を使用するよう構成しています:
<action-mappings> : <action path="/DispatchExample" type="controller.DispatchExampleAction" parameter="event" name="ExampleFormBean"> <forward name="success" path="/DispatchExample.jsp"/> <forward name="winner" path="/YouWin.jsp"/> </action> :
DispatchExample.jsp ページは、次のように、Struts HTML Formタグ・ライブラリを使用したHTMLフォームを含みます:
: <html:form action="DispatchExample.do"> <DIV align="center"> <table cellspacing="0" cellpadding="3" border="1"> <tr> <td>Value</td> <td><html:text property="value"/></td> <tr> <tr valign="middle"> <td colspan="2"> <input type="submit" value="Update"/> <input type="submit" name="event" value="Increment"/> <a href="DispatchExample.do?event=Decrement">Remove One</a> </td> </tr> </table> </DIV> </html:form> :
ここでは、ボタンおよびリンクの形式で、
event パラメータを "Increment" および "Decrement" というイベント名で使用しています。
Struts層では、自動的に、"value" という名で送信された値を、
ExampleFormBean の value プロパティにセットします。この例では、
(Update) ボタンに対してカスタム・イベント・コードは必要としていません。それゆえ、
(Update) ボタンには
name プロパティを指定していません。後は、アプリケーションが機能するように、コントローラ層のロジックを用意する必要があります。これは次のようなコードです:
"Increment" イベント発生時に、FormBeanの value プロパティの値を増加させます。このために、
DispatchExampleAction クラスに次のコードを追加します:
/**
* When the "event" parameter has the value "Increment",
* the DispatchAction will delegate to this method to handle it.
*/
public ActionForward Increment(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ExampleFormBean formBean = (ExampleFormBean) form;
formBean.setValue(formBean.getValue() + 1);
return mapping.findForward("success");
}
|
"Decrement" イベント発生時に、FormBeanの value プロパティの値を減少させます。
このために、次のコードを追加します:
/**
* When the "event" parameter has the value "Decrement",
* the DispatchAction will delegate to this method to handle it.
*/
public ActionForward Decrement(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ExampleFormBean formBean = (ExampleFormBean) form;
formBean.setValue(formBean.getValue() - 1);
return mapping.findForward("success");
}
|
value プロパティの値が 10 になったら、0 に値をリセットして、"winner" に転送(フォワード)します(これは
YouWin.jsp ページにマップされています)。
このために、Strutsの
Action の
execute メソッドをオーバーライドして、
super.execute() の実行後、条件分岐するようにコードを追加します:
/**
* If the "value" property equals 10, then reset the value to 10
* and forward to the "winner" forward
*/
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ActionForward fwd = super.execute(mapping, form, request, response);
ExampleFormBean formBean = (ExampleFormBean) form;
if (formBean.getValue() == 10) {
formBean.setValue(0);
return mapping.findForward("winner");
}
else {
return fwd;
}
}
|
前述のように、Struts
DispatchAction では、アクション・クラス内のイベント処理メソッドの
名前と
event パラメータの
値が正確に一致している必要があります。そのため、
Increment() と
Decrement() のメソッドが
Increment と
Decrement というイベントそれぞれのために必要になります。
もう一つの追加手順として、
DispatchExampleAction の
unspecified() メソッドをオーバーライドして、
event パラメータが指定されていない場合や、用意されているメソッド名に一致しない値が渡された場合に、例外(Exception)を出すというデフォルトの動作を変更しています。この例では、単に "success" へ転送するようにしています。
/** * We have to override this method to handle the case when * no "event" parameter is supplied as part of the request. * By default, the DispatchAction would throw an Exception. */ public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return mapping.findForward("success"); }
ViewController プロジェクトを実行して、
Struts DispatchAction Example のリンクをクリックすると、 ここまでに説明した基本機能を実装したアプリケーションを確認できます。
ここまでで、Struts
DispatchActionを使用したシンプルなイベント処理例の機能について理解できたことと思います。それでは、同じ機能をOracle ADF DataActionで実現する場合を考えてみましょう。おそらく、この方法が、前の例と、概念的にも機能的にもほぼ同じであることに気付くでしょう。前の例と同様に、「ポストバック」パターンに従うために、ADF DataPageを使用します。これは、『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「
ページフロー・ダイアグラムを簡略化するDataPage」で学んだように、DataAction とページ・フォワードが合わさったものです。
ページ・タイトルとURL以外は、
DataPageExample.jsp と、前の例で紹介した DispatchExample.jsp とは同じです。ここで注目すべき点は、コントローラ層の機能を、Struts
DispatchActionではなくADF
DataActionを使用した場合にどのように処理されるか、というところでしょう。"
DataPageExample" パスは、
struts-config.xml では次のように構成されています:
<action-mappings> : <action path="/DataPageExample" className="oracle.adf.controller.struts.actions.DataActionMapping" type="controller.DataPageExampleAction" name="ExampleFormBean" parameter="/DataPageExample.jsp"> <set-property property="modelReference" value="none"/> <forward name="winner" path="/YouWin.jsp"/> </action> :
ここでは、同じ
ExampleFormBean を再利用しています。先の
DispatchExample と異なる部分は次のようになります:
ADF DataAction ベースのアクション・クラスでは、拡張されたStrutsアクション・マッピング・クラスが必要です。
DataActionMapping クラス(
oracle.adf.controller.struts.actions パッケージ)は、Struts
ActionMapping の拡張で、ページ・リクエスト・ライフサイクルとデータ・バインディングの自動化を計るために、ADFバインディング層とDataActionにとって必要ないくつかの追加パラメータを取得します。このような場合、アクション・マッピングの
className 属性の値で、カスタムのアクション・マッピング・クラス名を指定できます。
parameter 属性の値は、前の例のように "event" を設定するのではなく、DataAction がデフォルトで転送するJSPページ名を設定します。
ADF DataActionは、Struts DispatchAction とStruts
ForwardAction に機能を組み合わせた一つの便利なクラスで、ADFバインディング層との調整作業も実装しています。Struts
ForwardAction では
parameter 属性で転送先のページ・パスを指定します。
parameter 属性では、2つ以上の異なる値を指定できませんが、ADF DataAction では、イベント用のパラメータの名前を "
event" と規定することで、
ForwardAction のように
parameter 属性を転送先パスのために使用できるようにしています。
| 注意: |
必要なら、プログラミングによってカスタムのDataActionクラスを作成して、イベント用パラメータの名前を変更することもできます。これは、オーバーライドした
|
サンプルの
controller.DataPageExampleAction は、Struts
DispatchAction でなく、ADF
DataForwardAction クラスを拡張して構成しています。
controller.DataPageExampleAction を作成してカスタムのコントローラ・コードを記述するには、Strutsページフロー図上で
DataPageExample のアイコンをクリックして、右クリック・メニューから
「コードに移動」を選択します。これにより、
図5のようなダイアログが表示され、クラス/パッケージ名を指定できます。
OK をクリックすると、
DataPageExampleAction クラスのスケルトン・コードが作成されます。
controller.DataPageExampleAction クラスに追加するコードは、
DispatchAction の例で紹介した実装と同じ処理です。
"
Increment" イベントに対応して値を増加させるメソッドを用意します:
/**
* When the "event" parameter has the value "Increment" (or "increment"),
* the DataAction will delegate to this method to handle it.
*/
public void onIncrement(DataActionContext ctx) {
ExampleFormBean formBean = (ExampleFormBean) ctx.getActionForm();
formBean.setValue(formBean.getValue() + 1);
}
|
"
Decrement" イベントに対応して値を減少させるメソッドを用意します:
/** |
イベント処理完了後、FormBeanの
value プロパティが 10 に一致するかどうか判定し、一致する場合に、値を 0 にリセットして、"winner" に転送(フォワード)します(これは
YouWin.jsp ページにマップされています)。
/**
* If the "value" property equals 10, then reset the value to 10
* and forward to the "winner" forward
*/
protected void findForward(DataActionContext ctx) throws Exception {
super.findForward(ctx);
ExampleFormBean formBean = (ExampleFormBean) ctx.getActionForm();
if (formBean.getValue() == 10) {
formBean.setValue(0);
ctx.setActionForward("winner");
}
}
|
DispatchAction ベースでの実装と、今回の
DataAction ベースの実装との主要な違いは次のとおりです:
イベント処理メソッドの名前が
Eventnameではなく、
onEventnameという形式になっています。
DispatchAction ベースのイベント処理メソッドでは、4つの引数を持ち、
ActionForward を返すものでしたが、この例では、
DataActionContext だけを引数とし、返り値のない(void)メソッドです。
| 注意: |
|
通常、カスタム・アクションではStruts Actionの
execute() をオーバーライドしますが、ADF DataAction では、ページ処理ライフサイクル内で転送先の検知("Find Forward")処理を調整できるよう、 findForward メソッドをオーバーライドしています。
したがって、メソッドの結果として
ActionForward を返す手法ではなく、
DataActionContextの
setActionForward() メソッドを使って、メソッドの結果となる
ActionForward をセットしています。
DispatchAction の例のような、
unspecified メソッドは必要ありません。
デフォルトで、ADF
DataAction は
event パラメータの指定がない場合でも例外を出さないように作られています。
ViewController プロジェクトを実行して
ADF DataPage Example リンクをクリックすると、Struts
DispatchAction の例と同じ機能を持つ ADF DataAction ベースの例を確認できます(
図6)
ADF DataPage Example2 は、トップ・ページの Example2 リンクからアクセスできます。これは前述のDataPageによる例とほとんど同じですが、起動されるイベント名に関して、少し違ったアプローチをとっています。
DispatchExample と ADF DataPage の例では、"Increment" イベントを起動するボタンは次のようにして作成されていました:
<input type="submit" name="event" value="Increment"/>
つまり、"event" がボタンの名前で、"Increment" がボタンの値(ラベルの値であり、送信される値でもある)を表しています。このような方法では、ボタンのラベル文字とイベント名を一致させないといけないという制限を生じます。
HTMLボタンの
value 属性はボタンのラベルであると同時に送信される値を表します。HTMLのユーザー・インターフェースを多言語対応にする(
Increment ボタンのラベルを翻訳する)ためには、
value 属性の値を "Increment" から別の値に変える必要があります。しかし、この代償として、イベント名も変更となり、これまでのコントローラ層のロジックも期待通りに機能しなくなります。
一般に、Struts ベースのアプリケーションで多言語をサポートする場合、ユーザー・インターフェース上の文字を切り出して
ApplicationResources.properties ファイル内に格納します:
# From ApplicationResources.properties button.add.one=Add One button.update=Update link.remove.one=Remove One
このように切り出された文字は、Struts Bean タグ・ライブラリを使用して、文字列に対する "キー" で参照できます。これで、アクセスするユーザーのブラウザで指定された言語に応じて、適切なメッセージが実行時に使用されます。これまでのコード
<input type="submit" value="Update"/> <input type="submit" name="event" value="Increment"/> <a href="DataPageExample.do?event=Decrement">Remove One</a>
は、次のように国際化対応のコードに書き直すことができます。
<input type="submit" value="<bean:message key='button.update'/>"/> <input type="submit" name="event" value="<bean:message key='button.add.one'/>"/> <a href="DataPageExample2.do?event=Decrement"><bean:message key='link.remove.one'/></a>
| 注意: |
この方法により、開発者は、多数のユーザー・インターフェース文字列を用意して、Struts が実行時に適切にそれらを使用するように構成できます。たとえば、イタリア語用の
|
イベント処理のメソッド名をイベント名に対応する必要がありますが、ここでもう一つの課題があります。適切な表示ラベルを持つアプリケーションを目指す場合、たとえば、ユーザーに対して (Increment) ではなく (Add One)というようなラベル表示でよりわかりやすくしたい場合には、問題に直面します。このようにスペース(空白)を含む名前の場合、これに対応するメソッド名を用意できません。
Struts では、
LookupDispatchAction というクラスを提供して、このような翻訳に絡む問題に取り組んでいますが、ADF DataAction では、よりシンプルなアプローチをしています。それは、イベントを表すHTMLコントロールの
value 属性を使用するのではなく、ボタンの名前自体でイベントを表現します; つまり、"event_" という接頭辞付きの名前を使用します。たとえば、
<input type="submit" name="event" value="<bean:message key='button.add.one'/>"/>
というコードでは、ボタンの値(ラベル値)でイベント名を表現していましたが、この代わりに、
<input type="submit" name="event_Increment" value="<bean:message key='button.add.one'/>"/>
というように、
name="event" を
name="event_Increment" に変更します。このようにすると、ボタンの値にスペースが含まれていても、国際化の問題に対応しながら、確実に "Increment" イベントを起動できるようになります。
DataPageExample2.jsp のソースコードを見ると、国際化対応のために
<bean:message> タグが使用されていますが、逆にいうと、前に紹介した
DataPageExample.jsp との違いはその程度です。
DataPageExample2 データアクションは、
DataPageExample データアクションとまったく同じ DataPageExampleAction クラスおよび
ExampleFormBean を使用するよう構成されており、実際に実行してみて
Example2 リンクをクリックすることで、前述の例と同じ機能であることを確認できます。
ADFバインディングとイベント処理のメカニズムを更に究明するために、次の例である ADF Declarative Exampleに進みましょう。ここでも、アプリケーションの機能は同じですが、その実装方法として、Strutsと密接したADFフレームワークおよびDataActionの機能による利点をより多く使用しています。
ここまでの例では、アプリケーションは、真の意味において、適切な「ビジネス・サービス」/「モデル」の層をもっていなかったといわざるをえません。変更される数値は、単にStrutsの
ExampleFormBean にプロパティとして格納されるだけでした。このFormBeanは、単に送信されたデータ値をビュー層から運ぶだけの Transfer Object の役割を果たすだけでした。別の言い方をすると、前述の例は、モデル層を無視してごまかした実装であるといえます。
ADF の宣言的利用例では、モデル層を適切に、シンプルなJavaBean(
Modelプロジェクト内の
model.ExampleBean)として実装しました:
package model; public class ExampleBean { private int _value; public ExampleBean() {} public void setValue(int value) { _value = value; } public int getValue() { return _value; } public void increment() { setValue(getValue() + 1); } public void decrement() { setValue(getValue() - 1); } }
カプセル化された
increment() 、
decrement() メソッドが追加されていること以外は、
ExampleBean は、これまでに使用した
ExampleFormBean と同じに見えます。しかし、重要な違いは、このBeanが、Struts依存クラスの拡張では
ない(
ExampleFormBean は、Struts
ActionForm の拡張クラスでした)ということです。つまり、このモデル層のクラスは、純粋にシンプルなJavaBeanであり、このケースでは、
int型の
value という名前のプロパティをもっています。
JDeveloperのアプリケーション・ナビゲータ上で
ExampleBean.java をクリックして、右クリック・メニューから
データ・コントロールの作成を選択すると
ExampleBeanDataControlが作成され、これでADFデータ・バインディングの準備が完了します。
図7は、データ・コントロール・パレットに、このプロパティとオペレーションが検知されたことを表しています。
DataPageDeclarative.jsp ページは、
DataPageDeclarative データページのビュー層を実装します。このページに対するデータ・バインドを行うために、ADFデータ・バインディング・コンテナが必要になります。この例では、次の3つのタイプのバインディングを使用します:
1つの
イテレータ・バインディング
ルートの
ExampleBean インスタンスを保持するシングルトン・コレクションへの参照
1つの
コントロール値バインディング
HTMLフォームのフィールドにデータをバインドさせて、
ExampleBean ビジネス・サービスの
value プロパティの現行値を表示
2つの
コントロール・アクション・バインディング
ビジネス・サービスの
increment() と
decrement() メソッドに対応。これらを使うことで、メソッドを起動する処理を宣言的に設定可能になる。
| 注意: |
これらの2つの
|
『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「
バインディング・コンテナ定義およびバインディング定義の作成」では、設計時に明示的または暗黙的にバインディングを作成する方法について説明しました。これらの両方のアプローチを組み合わせて、DataPageDeclarative.jspを構成する手順を説明します:
まず、データ・コントロール・パレットで、
ExampleBeanDataControl の
value プロパティをクリックします。このパレットの下にある
ドラッグ・アンド・ドロップの形式 リストから "Input Field" を選択します。そして、この
value プロパティを空のページ上にドロップします。このとき、
図8のようなダイアログで、ドラッグ&ドロップされた入力フォームを含むHTMLフォームを同時に作成するかどうかたずねられますので、「はい」を選択します。
この作業によって、暗黙的に以下の物が作成されます:
DataPageDeclarativeUIModel
という名前のバインディング・コンテナExampleBean に対する ExampleBeanDataControl_rootIter という名前のイテレータ・バインディング
ExampleBean の value プロパティに対応する
value という名前のコントロール値バインディング
次に、
increment メソッドをクリックして、"Button" としてHTMLフォーム内(
<html:form> タグを表す点線内)にドラッグ&ドロップします。
この作業によって、
increment という名前のコントロール・アクション・バインディングが新しく追加されます。
最後に、明示的アプローチによって、
decrement メソッドに対するアクション・バインディングを作成してみます。手順は次のとおりです:
DataPageDeclarativeUIModel ノードをクリックして、右クリック・メニューから
バインディングの作成 →
アクション →
Action を選択します。ExampleBeanDataControlをクリックして、
操作の選択 リストから
decrement()メソッドを選択し、OKを押します。
この明示的な方法によって、
decrement が新しく追加されます。
プロジェクト内で
DataPageDeclarative.jsp ページを選択して、構造ペインから "UIモデル" タブをクリックすると、
図10のように、バインディングの作成結果を見ることができます:
バインディングが作成されたので、後は、前述の例と同じ見た目になるようにデザインを調整します。この結果、
DataPageDeclarative.jsp の
<html:form> セクションは、国際化された
DataPageExample2.jsp のユーザー・インターフェースと似通ったものになります:
<html:form action="DataPageDeclarative.do"> <input type="hidden" name="<c:out value='${bindings.statetokenid}'/>" value="<c:out value='${bindings.statetoken}'/>"/> <DIV align="center"> <table cellspacing="0" cellpadding="3" border="1"> <tr> <td>Value</td> <td> <html:text property="value" /> </td> </tr> <tr valign="middle"> <td colspan="2"> <input type="submit" value="<bean:message key='button.update'/>"/> <input type="submit" name="event_increment" value="<bean:message key='button.add.one'/>"/> <a href="DataPageDeclarative.do?event=decrement"> <bean:message key='link.remove.one'/></a> </td> </tr> </table> </DIV> </html:form>
今度は、コントローラ層の構成と実装の話に移りましょう。
DataPageDeclarative アクションは、
struts-config.xml 内で次のように構成されます:
<action-mappings> : <action path="/DataPageDeclarative" className="oracle.adf.controller.struts.actions.DataActionMapping" type="controller.DataPageDeclarativeAction" name="DataForm" parameter="/DataPageDeclarative.jsp"> <set-property property="modelReference" value="DataPageDeclarativeUIModel"/> <forward name="winner" path="/YouWin.jsp"/> </action> :
前述の DataPageExample と同じように、ADF
DataActionMapping クラスを使ってアクション・マッピングを表現しています。これによって、ネストされたタグ
<set-property> で指定された
modelReference プロパティなどにアクセスできるようになります。その結果、自動的にバインディング・コンテナを設定し、
DataActionContext 経由でアクセス可能にします。一つ、興味深い違いは、ここまで使用してきた
ExampleFormBean ではなく、今回は
DataForm という名前のFormBeanを使用していることです。
struts-config.xml の <form-beans> セクションを見ると、
DataForm が
BindingContainerActionForm クラスで実装されていることがわかります。これは、ADFで提供される、汎用利用可能なFormBeanで、
DynaActionForm として、バインディング・コンテナに定義されたバインディング定義をプロパティに持ちます。
: <form-beans> <form-bean name="ExampleFormBean" type="controller.ExampleFormBean"/> <form-bean name="DataForm" type="oracle.adf.controller.struts.forms.BindingContainerActionForm"/> </form-beans> :
このような汎用利用可能なFormBeanによって、Webページごとに異なるFormBeanを設計する必要はなくなるため、開発者の作業は格段に楽になります。これは、Struts
DynaActionForm を更に賢くした実装だといえます。つまり、追加の作業なしで、現行ページのバインディング定義をそのままプロパティとしてStruts層に反映させることができるのです。Struts がこのFormBeanにプロパティをセットしようとすると、自動的に、適切なバインディングに対する値設定の作業が行われます。
| 注意: |
|
ViewController プロジェクトを実行して
ADF Declarative Example リンクをクリックすると、
図11 のようなページが表示され、前述の例と同じ機能を確認できます:
では、この例がどのように動いていて、どんなコードが必要なのか確認しましょう。
DataPageDeclarativeAction のソースコードを見ると、唯一のオーバーライドされたメソッド findForward を見ることができます。
/** * If the "value" property equals 10, then reset the value to 10 * and forward to the "winner" forward */ protected void findForward(DataActionContext ctx) throws Exception { super.findForward(ctx); DCBindingContainer bc = ctx.getBindingContainer(); JUCtrlValueBinding binding = (JUCtrlValueBinding) bc.findCtrlBinding("value"); int value = ((Integer) binding.getInputValue()).intValue(); if (value == 10) { binding.setInputValue(new Integer(0)); ctx.setActionForward("winner"); } }
上記コードでは、バインディング・コンテナの
findCtrlBinding() メソッドが使用されています。また、コントロール・バインディングの
getInputValue() と
setInputValue() メソッドによって、("
value" バインディングを経由して)モデル層から現行の数値を読み取り、条件に応じて 0 に戻す作業をしています。
ここに、ページ上の
(Add One) ボタンや
Remove One リンクによる
increment/decrement イベントを処理する
onIncrement() や
onDecrement() メソッドがないことに注目してください。これらはどうしたのでしょう
?
答えは簡単です。ADF DataActionの数多くの宣言的機能の一つに、この自動的なアクション・バインディング起動があります。現行イベントと一致する名前のアクション・バインディングをバインディング・コンテナから見つけ出し、自動でその対応するメソッドを起動します。もう一度、
図10 を見てください。"increment" イベントが ADF
DataAction で処理される場合、(内部的には次のようなコードによって、)一致する
increment アクション・バインディングの存在を見つけ出し、対応するバインディング・コードを起動します。
/* Note: ctx is the current DataActionContext */ if (ctx.getEventActionBinding() != null) { PageLifecycle p = (PageLifecycle)getPageLifecycle(ctx); /* Invoke the action binding corresponding to the name of the event */ p.invokeActionBinding(ctx,ctx.getEventActionBinding().getName()); }
"increment" アクション・バインディングは、
ExampleBeanDataControlの
increment() メソッドと連結されており、
doIt()(上記コードでは
invokeActionBinding() の内部でコールされる
)をコールすることで、ADFバインディング層の
ExampleBean の
increment()を起動し、ビーンのプロパティ値を 1 だけ増加させます。まったく同じメカニズムで、
ExampleBean の
decrement() メソッドが、ページ上の
Remove One リンクをクリックしたときに起動されます。
このシンプルなアプリケーション例では機能をコーディングする必要はありませんでしたが、必要に応じて、この宣言的なアクション・バインディングの手法と、
onEventnameによるイベント処理の手法を組み合わせることも可能です。たとえば、次のような
SomeEventNameイベントに対する処理コードを記述することができます:
public void onSomeEventName(DataActionContext ctx) { // Code before executing the default action if (ctx.getEventActionBinding() != null) { PageLifecycle p = (PageLifecycle)getPageLifecycle(ctx); /* Invoke the action binding corresponding to the name of the event */ p.invokeActionBinding(ctx,ctx.getEventActionBinding().getName()); } // Code after executing the default action }
このイベント・ハンドラはデフォルトのアクション・バインディング(メソッド名に対応するアクション・バインディング)を起動するコードを含んでいます。もし
onSomeEventName メソッドに、そういったコードを入れなければ、一致するアクション・バインディングの起動を迂回することもできます。
コードのあちこちで同様の処理が必要なら、この処理を一つのヘルパー・メソッドとして
invokeEventAction() などの名前で用意しておけば、イベント名と一致するデフォルトのアクション・バインディングを起動したい場合に簡単に使用できます。
protected void invokeEventAction(DataActionContext ctx) { if (ctx.getEventActionBinding() != null) { PageLifecycle p = (PageLifecycle)getPageLifecycle(ctx); p.invokeActionBinding(ctx,ctx.getEventActionBinding().getName()); } }
このようにすると、先に出したサンプル・コードは次のように省略化されます:
public void onSomeEventName(DataActionContext ctx) { // Code before executing the default action invokeEventAction(ctx); // Code after executing the default action }
これでお分かりのように、必要に応じて、カスタムのコントローラ・コードは、アクションを起動する前後に好きなように記述できます。
| 注意: |
注意深い読者は、
|
ADF DataAction のイベント処理メカニズムのもう一つの主要な機能を理解するために、次のサンプルである Events, Actions, and Forwards Example に移ります。これは、 図1のフロー図では、上部に位置する2つのページで構成されているものです。
UpdateEmp1 と
UpdateEmp2 データページは、(Oracleデータベースの
SCOTT ユーザーが所有する)
EMP表からの従業員情報を表示するものです。
図12 のように、
UpdateEmp1.jsp ページは、従業員の名前と役職を表示し、
UpdateEmp2.jsp ページは給与情報を表示します。
ページの上部にある2つのボタンはタブ・ページの切替のように機能し、2つのページ間を移動する目的で使用します。下部にある4つのボタンは、すべての変更の保存、取消と、編集する従業員情報を次に進む、または一つ戻るために使用できます。ユーザーは、Deptno の情報に関しては、部門番号のような数値ではなく、それと関連する実際の部門名で構成されるリストから選択できるようになっています。
| 注意: |
ここで紹介したデータ連携されたリストボックスの宣言的な作成方法に興味のある方は、
|
EmpServiceDataControl(
図13)は、表示および編集対象となる
Employees データソースと、"Deptno" リストボックスの中身になるデータを保持する
DepartmentLOV データソースを提供します。このデータ・コントロールの実体は、
Model プロジェクト内の
model.EmpService (ADFアプリケーション・モジュール・コンポーネント)です。それには、
Employees および
DepartmentLOV という名前のインスタンスが含まれており、それぞれ、
model.views.Employees および
model.views.DepartmentLOV というビュー・オブジェクト定義に対応しています。
2つの従業員情報編集ページはいくつかの機能をサポートします:
ADF DataAction のイベント処理メカニズムの機能を活用すると、これらの機能はすべて、カスタムのコントローラ・コードなしで実装できます。以降では、その機能や設定方法を説明することで、ADF DataActionの宣言的アプローチについて究明していきます。
Events, Actions, and Forwards Example アプリケーションにはカスタム・コードが必要ないため、
struts-config.xml でも、
UpdateEmp1 と
UpdateEmp2 のアクションに対するクラスは
DataForwardAction ベース・クラスをそのまま使用します(前述のDataActionベースの例のようにカスタムのサブクラスは必要ありません)。
<action-mappings> : <action path="/UpdateEmp1" className="oracle.adf.controller.struts.actions.DataActionMapping" type="oracle.adf.controller.struts.actions.DataForwardAction" name="DataForm" parameter="/UpdateEmp1.jsp"> <set-property property="modelReference" value="UpdateEmp1UIModel"/> <forward name="ShowCompensationInfo" path="/UpdateEmp2.do"/> </action> <action path="/UpdateEmp2" className="oracle.adf.controller.struts.actions.DataActionMapping" type="oracle.adf.controller.struts.actions.DataForwardAction" name="DataForm" parameter="/UpdateEmp2.jsp"> <set-property property="modelReference" value="UpdateEmp2UIModel"/> <forward name="ShowNameInfo" path="/UpdateEmp1.do"/> <forward name="SaveAll" path="/UpdateEmp1.do"/> </action> :
DataPageDeclarative の例と同様、"DataForm" FormBean と
DataActionMapping が使用されています。
UpdateEmp1.jsp ページには、
event_ という接頭辞のついた名前のボタンがあり、次に示すイベントを起動します:
Commit,
Rollback,
Next,
Previous,
ShowCompensationInfo
構造ペインで
UpdateEmp1.jsp の "UIモデル" タブを選択する(
図14)と、このページに対する5つのイベントのうちの4つに一致するアクション・バインディング(
Commit,
Rollback,
Next,
Previous)を確認できます。
CommitとRollbackのアクション・バインディングは、データ・コントロールの組み込みメソッド(
Commitおよび
Rollback)と結合しています。このため、これらに関して、追加のカスタム・コードは必要ありません 。
Next と
Previous のアクション・バインディングは、イテレータに対する組み込みメソッド(
Next および
Previous)と結合しています。このため、同じ理由で、カスタム・コードは不要です。
これらのバインディングは、データ・コントロール・パレットから組み込みの操作(Operation)をJSPビジュアル・エディタ上にドラッグ&ドロップすれば、暗黙的に作成されます。一点注意すべきなのは、
図9 で説明したように、ドラッグ&ドロップする際に、HTMLフォームの内部に追加するように気をつけることくらいです。もう一つの残っているイベント
ShowCompensationInfo については後ほど説明します。
同様にして、
UpdateEmp2.jsp ページにも
SaveAll,
CancelAll,
Next,
Previous,
ShowNameInfo イベントに対応するボタンがあります。
図15 では、このページに対するUIモデルで、対応する
SaveAll,
CancelAll,
Next,
Previous のアクション・バインディングがあることが確認できます。
UpdateEmp1.jsp と同じ方法でこれらのボタンに対するバインディングを設定できます。ここには、ADF設計時サポート機能によって作成されたデフォルトのイベント名ではない名前を使用したい場合のために、その例を用意してあります。デフォルトでは、
Commitおよび
Rollbackの操作をページに追加すると、そのまま
(Commit) および
(Rollback) ボタンが次のように作成されます:
name="event_Commit" 、
value="Commit"
name="event_Rollback" 、
value="Rollback"
同時に、暗黙的に、
Commitおよび
Rollbackというアクション・バインディングも作成されます。この、
Commit および
Rollback というイベントを起動する
(Commit) /
(Rollback) ボタンを、
SaveAll および
CancelAllというイベントを起動する
(Save All Changes) /
(Cancel Changes) ボタンに変更するには、各ボタンを次のように変更します
(Commit) ボタンに対して:
name プロパティを "
event_SaveAll" に、
value プロパティを "
Save All Changes" に変更
対応するアクション・バインディングの
ID プロパティを "SaveAll" に変更
(Rollback) ボタンに対して:
name プロパティを "
event_CancelAll" に、
value プロパティを "
Cancel Changes" に変更
対応するアクション・バインディングの
ID プロパティを
"
CancelAll" に変更
UpdateEmp1 と
UpdateEmp2 のページの下部にあるボタンに関する実行時の処理については理解できたことと思います。ボタンはイベントを起動しますが、ADF DataActionによって、そのイベントに名前が一致するアクション・バインディングを実行する処理を自動的に実施できるように、名前づけします。これらのアクション・バインディングは、ADFのデータ・コントロールおよびイテレータに対して組み込みで提供される操作であり、前述の例で取り上げた、カスタム・メソッドを起動する
increment/
decrement アクション・バインディングとは違うように思えるかもしれません。しかし、メカニズムは、組み込みメソッドでもカスタム・メソッドでも同じで、処理されるイベントの名前と一致するアクション・バインディングによって起動されます。
最後に残った理解すべき 2つのイベントが、
UpdateEmp1.jsp ページの
ShowCompensationInfo イベント、および
UpdateEmp2.jsp ページの
ShowNameInfo イベントです。これらのイベントは、
図16 でわかるように、
UpdateEmp1 と
UpdateEmp2 のページ間のフォワードの名前でもあります。
ShowCompensationInfo という、
UpdateEmp1 から
UpdateEmp2 へのフォワードがあります。デフォルトでは、ページ処理ライフサイクルの findForward() のフェーズで、ADF DataAction は、DataActionContextの
getActionForward() をコールして、開発者が、事前にActionForwardの名前を設定しているかどうか確認します。このチェックで
nullが返ってくる場合、これは開発者が、使用するActionForwardをプログラム的に設定していないことを意味し、便利な代替の振る舞いとして、イベントと同じ名前のフォワードを探そうとします。名前が一致するフォワードが見つかると、デフォルトの findForward() 実装で、そのActionForwardが設定されます。
このような理由から、カスタムのコントローラ・コードを記述していなくても、ユーザーが
(Show Compensation Info) ボタンをクリックすると、
UpdateEmp2.jsp ページが表示されます。 つまり、
ShowCompensationInfo イベントが発生すると、ADF DataAction によって、宣言的ナビゲーションの実施のために、名前が一致する
ShowCompensationInfo フォワードが使用されます。
例にはあげていませんが、この宣言的ページ・ナビゲーションと、ここまでに学んだイベント処理とを組み合わせて利用することも、もちろん可能です。カスタムのデータ・アクション・クラス内で
onShowCompensationInfo() メソッドを用意しているような場合、イベント処理コードは、ライフサイクルの
findForward() フェーズより前にコールされます。イベント処理コードで、プログラム的に
DataActionContextの
setActionForward() がコールされると、宣言的ページ・ナビゲーションの動作は迂回されます。つまり、プログラム的に設定されたActionForwardが優先されます。
同じ宣言的ページ・ナビゲーション・メカニズムによって、
UpdatePage2.jsp に対する
ShowNameInfo イベントの処理も説明できます。つまり、名前が一致する
ShowNameInfo フォワード経由で、
UpdatePage1.jsp に宣言的ナビゲーションによって戻ります。ViewController プロジェクトを実行して
Events, Actions, and Forwards Example リンクをクリックすると、この例を実際にやってみることができます。いくつもの従業員データを移動したり、編集したり、"Name and Role Info" と "Compensation Info" の間を行き来してみてください。
もう一つ、フォワードの動作があります。実行時に従業員情報の編集を行った後、
Compensation Info ページ(
図17)で
(Save All Changes) ボタンを押してみてください。繰り返しになりますが、このボタンによって
SaveAll イベントが起動し、DataActionによって、名前が一致する
SaveAll アクション・バインディングを起動します。 そして、最終的には、
SaveAllという名前の、
UpdateEmp2.jsp から
UpdateEmp1.jsp へ戻るフォワードがあるため、ボタンをクリックすることで、アクションの起動と、"Name Info Page"へのページ・ナビゲーションの両方を実施します。
これでわかることは、宣言的なアクション・バインディング起動と、宣言的なページ・ナビゲーションは同時に利用可能だということです。最後に、ここまでのいくつかの例を通して見てきた、アプリケーションに有用で、どんな組み合わせでも利用可能な、ADF DataActionの3つの重要な機能についてまとめておきます。 YourEvent という名のイベントが起動された場合...
public void onYourEvent(DataActionContext ctx) というメソッドがある場合、そのカスタム・コードがイベントを処理するために起動されます。
YourEventという名前のアクション・バインディングがある場合、それが起動されます。
1 のメソッドと組み合わせて使用する場合、イベント処理コードでは、明示的に次のようなコードで現行イベントに対するアクションを起動する必要があります:
if (ctx.getEventActionBinding() != null) ctx.getEventActionBinding().doIt();
YourEventという名前のフォワードがある場合、それが次の制御先のページを決定するために使用されます。
1 のメソッドと組み合わせて使用する場合、イベント処理コードで
ctx.setActionForward() をコールすると、それによって設定された転送先のほうが優先されます。
『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「
実際のアプリケーション例によるケーススタディ 」で紹介した
ADFBindingIntro サンプル・アプリケーションでは、コレクション値のデータ・コントロール・プロパティへのデータ・バインディングを紹介しました。具体的には、そのサンプルでは、次のようなことが行われていました:
CommissionService では、
availableCommissionPlans というコレクション値プロパティを
getAvailableCommissionPlans() メソッド経由で公開EmpServiceDataControl は ADFアプリケーション・モジュール実装で、自動的に、ビュー・オブジェクト・インスタンスを、それぞれ同じ名前のコレクション値プロパティとして公開
JavaBeans の標準に従い、ADFデータ・コントロールはプロパティとメソッドの両方を公開します。ADF設計機能サポートでは、次のような名前のメソッドがある場合に、
Somethingという名前のデータ・コントロール・プロパティの存在を認識します:
getSomething()
setSomething(Type value)
その他の名前のメソッドはサービス・メソッドとして取り扱われます。ADFバインディング層では、コレクション値のような公開プロパティへのバインディングと、サービス・メソッドの戻り値へのバインディングの両方をサポートしています。このセクションでは、メソッド結果に対するデータ・バインディングを行うケースについての詳細に注目します。
データ・コントロールがサービス・メソッドを公開している場合、その戻り値である結果に対してデータ・バインディングを実施できます。カスタム・メソッドの結果に対するデータ・バインディングを含むいくつかの状況を紹介したサンプル・アプリケーション(
MethodBindingExample.zip)を用意しました。この例は、次のような
MyService クラスで実装されるJavaBeanベースのデータ・コントロールによるものです:
package test.model; public class MyService { public MyService() {} /** * Test exposing a collection of strings. */ public String[] getArrayOfStrings() { return new String[] { "some", "strings", (new Date()).toString() }; // Return current date as a string } /** * Test exposing a collection of beans. */ public MyType[] getArrayOfMyType() { return new MyType[] { new MyType("Tom", 5), new MyType("Sue", 6) }; } /** * Test method returning an array of strings from method. */ public String[] findArrayOfYourStrings(String x, String y) { return new String[] { x, y }; } /** * Test method returning an array of MyType beans, accepting scalar args. */ public MyType[] findArrayOfMyType(String name, int age, String name2, int age2) { return new MyType[] { new MyType(name, age), new MyType(name2, age2) }; } /** * Test method returning an array of MyType beans, accepting a MyType argument. */ public MyType[] findArrayOfMyType(MyType person1) { return new MyType[] { person1, new MyType(person1.getName() + ", Sr.", person1.getAge() + 20) }; } }
図18 は、このクラスに対して、アプリケーション・ナビゲータで右クリック・メニューから
データ・コントロールの作成 を選択した後の、データ・コントロール・パレットです。これを見ると、
arrayOfMyType と
arrayOfStrings という名前のコレクション値プロパティに加えて、3つのカスタム・メソッドが
Operations フォルダの下に小さな "
f()" アイコンと共に表示されていることに気付きます。メソッドのノードを開くと
return ノードを確認できます。これはメソッドの戻り値を表したものです。
メソッドが JavaBean や、JavaBean のコレクションを返す場合は、さらに
return ノードを開いてメソッド結果の構造を見ることができます。メソッドの戻り値がシンプルなスカラー値や、スカラー値の配列の場合は、
return ノードの下に付随する構造はなく、シンプルな戻り値として表現されます。
シンプルなデモを作成するために、
図19のようなページフロー図を構成しています。
index.jsp ページと、4つの異なるデータ・バインディング・シナリオへのリンクがあります。
HTMLテーブル内に結果を表示するデータページのコンテンツは、次のような基本的な手順を繰り返して作成します:
return ノードを "Read-Only Table" としてデータ・コントロール・パレットからページ上にドラッグ&ドロップするMethodBindingExample ワークスペースの ViewController プロジェクトを実行すると、
図20 のようなページが現れます。
Collections of Strings and Beans リンクは
CollectionsWithNoArgs データページにリンクしています。このページでは、2つのコレクション値プロパティのデータ(
MyServiceDataControlの
arrayOfStrings と
arrayOfMyType )とバインドしています。先にあげた MyService クラスの
getArrayOfString() メソッドでは、現在の時間情報を配列で返していました。
public String[] getArrayOfStrings() { return new String[] { "some", "strings", (new Date()).toString() }; // Return current date as a string }
getArrayOfString() メソッドが起動されるたびに、配列の3番目の値に現在の時間が記録されます。
Collections of Strings and Beans の
CollectionsWithNoArgs データページを実行した後で、ブラウザで再読み込みしてみてください。現行時間が元のまま同じで、更新されていないことに気付きます。ADFバインディング層では、ページのリフレッシュ時には
getArrayOfString() メソッドを再実行していません。これはなぜでしょう? これを理解することは、アプリケーション上のコントロールの振る舞いを理解するためにとても重要です。
ADF DataAction ページ処理ライフサイクル内の
prepareModel() フェーズで、
refreshControl() メソッドがコールされます。これによって、バインディング・コンテナ内の "最上位レベルの" イテレータはすべて、未実行の場合は実行されます。なお、ここでいう "最上位レベルの" イテレータとは、データ行の "最上位レベルの" コレクション上のイテレータ・バインディングのことで、それらに付随する子のプロパティにバインドしているものは含まれません。わかりやすい例をあげると、データベースのマスター/ディテール関係のデータの場合、マスターのデータ処理が "最上位レベル" に該当し、ディテールのデータが "最上位レベルに付随するプロパティ" に該当します。
したがって、最初に
CollectionsWithNoArgs ページが表示されたときに、コレクション値である
arrayOfString プロパティ上のイテレータが実行されます。これは、そのベースである
getArrayOfStrings() メソッドが呼び出されることになります。その後のページ・リフレッシュでは、イテレータは既に実行済であるため、デフォルトでは再実行されません。
イテレータの再実行を強制するためには、単にイテレータに対する
executeQuery() メソッドをコールします。このサンプルのページ内に用意された
Refresh Results リンクでは、データのリフレッシュを実施するために
Refresh という名前のイベントを起動させます:
<a href="CollectionsWithNoArgs.do?event=Refresh">Refresh Results</a>
このイベント処理はプログラミングによって、または宣言的な設定によって構成できます。プログラムによる構成は次のように行います:
カスタムの
CollectionsWithNoArgsAction DataActionサブクラスを作成します。
これは、ページフロー図内で、データページのアイコンを右クリックして、メニューから
コードに移動 を選択します。
onRefresh() メソッドを用意します。イベント処理コードとして、一行だけ記述します。このコードでは、名前でイテレータ・バインディングを探し出して、その
executeQuery() をコールします。
コードは、次のようになります:
public void onRefresh(DataActionContext ctx) {
ctx.getBindingContainer().findIteratorBinding("arrayOfStringsIterator").executeQuery();
}
|
しかし、これまでにADF DataAction のイベント処理メカニズムについて理解してきたように、宣言的アプローチのほうがより簡単です。実際、サンプルの MethodBindingExample では、その方法を使用しています。
CollectionsWithNoArgs.jsp ページがアクティブな状態で、構造ペインの "UIモデル" タブを選択すると、
Refresh アクション・バインディングがあることを確認できます。これによって、
Refresh イベントが発生した場合に、宣言的に対応するメソッドが起動されます。この
Refresh アクション・バインディングを右クリックして、
編集... を選択すると、
アクション・バインド・エディタが表示されます。これを見ると、アクション・バインディングが
arrayOfStringsIterator の組み込みアクションである
Execute にバインドされていることがわかります。
これだけで、追加のカスタム・コーディングなしでも、
Refresh イベントが発生するとイテレータに対する再実行が行われます。実際に
CollectionsWithNoArgs.jsp ページ上で
Refresh Results リンクをクリックするたびに現行時間が更新されることが確認できます。
ここまでに説明したものが、プロパティ・ベースのバインディングであるのに対して、同サンプル内の残りの3つのページフローはすべて、引数をもつカスタム・メソッドの戻り値へのバインドです。
最初の例では、以下のように構成されています。
AcceptStringsParams.jsp
String[] findArrayOfYourStrings(String,String)
StringResults
ページフローの基本構成を設定するためには、ページフロー図で以下のコンポーネントを作成します:
AcceptStringsParams.jsp ページ(Page)
CallFindStrings データアクション(Data Action)
StringResults データページ(Data Page)ページフロー図上で、
CallFindStrings データアクションと
StringResults データページとを Struts フォワードでつなげる 前に、データ・コントロール・パレットから
findArrayOfYourStrings オペレーションを
CallFindStrings アクション上にドロップします。これによって、暗黙的に
CallFindStringsUIModel バインディング・コンテナが作成され、オペレーションに対する定義情報からメソッド名とパラメータ情報を読み取って、メソッドを起動するアクション・バインディングと(引数用の)カスタムのStruts アクション・プロパティの定義が記述されます (図21)。
| 注意: |
JDeveloper 10g の設計時サポート機能によって暗黙的に
バインディング・コンテナ が作成されると、その名前は、
バインディング・コンテナが構成されていないDataAction への自動設定が行われるもう一つのタイミングが、そのDataActionから別のアクションやページへ、フォワードによってつなげる場合です。この場合、フォワード元のDataAction にバインディング・コンテナが構成されていなければ、フォワード先のターゲットと同じバインディング・コンテナを使用するように設定されます。
|
StringResultsページでメソッド戻り値を表示するために、Strutsページフロー図から対象のアイコンをダブルクリックして、作成するJSPページ名を確認します。その後、データ・コントロール・パレットから、
findArrayOfYourStrings オペレーションの下の
return ノードを選択し、"Dynamic Read-Only Table"として、ページの設計画面上にドラッグ&ドロップします。
このような手順の結果
StringResultsUIModel(図 22)のようなバインディング定義が構成されます。
次に、
CallFindStrings データアクションと
StringResults データページとをフォワードでつなげます。
AcceptStringsParams.jsp ページを開くと、シンプルに
one と
two という2つのフォーム値を取得して
CallFindStrings.do アクションへ送信するページであることを確認できます。
<!-- Form from AcceptStringsParams.jsp --> <form action="CallFindStrings.do"> <table cellspacing="0" cellpadding="2" border="1"> <tr> <td>String One:</td> <td><input type="text" name="one"/></td> </tr> <tr> <td>String Two:</td> <td><input type="text" name="two"/></td> </tr> </table> <input type="submit" value="Submit"/> </form>
このような、ページから
CallFindStrings への送信の事実をページフロー図に反映させたい場合は、
AcceptStringsParams.jsp ページの右クリック・メニューから
ページからダイアグラムをリフレッシュを選択します。こうすると、ダイアグラムがリフレッシュされて、
AcceptStringsParams.jsp と
CallFindStrings の間に点線が表示され、要素間の依存性を認識できるようになります。
最後に、
findArrayOfYourStrings オペレーションに対して引き渡す2つのパラメータを宣言的に設定する方法について説明します。『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「
DataAction 機能をフル活用した宣言的手法 」セクションでは、メソッド起動に関する宣言的定義情報は、Strutsアクション・マッピングのカスタム・プロパティとして記述されることを学びました。データ・コントロール・パレットからオペレーションをドラッグ&ドロップすると、それらの定義情報が自動的に構成されます。
struts-config.xmlに対する構成ペイン(図23)を見ると、
methodName,
resultLocation,
numParams,
paramNames[0]および
paramNames[1]といった追加のプロパティを確認できます。
paramNames[0]と
paramNames[1]に対して、渡すべきパラメータのEL表現で設定を行います。最初の引数として
oneという名のリクエスト・パラメータ値を渡せるように、プロパティ・インスペクタから、
paramNames[0]に対する設定値を、EL表現
${param.one} として設定します。同様に、
paramNames[1] の値を
${param.two} にして、
two
という名前のパラメータ値が渡されるようにします。
| 注意: |
|
ViewController プロジェクトを実行して
Results of Method Taking String Arguments
リンクをクリックすると、ここまでの説明内容を実際に確認できます。
注意深い読者は、
findArrayOfYourStrings アクション・バインディングが
CallFindStrings データアクションと
StringResults
データページの両方のバインディング定義内にあることに気付いたかもしれません。どうして、"メソッドは2度実行" されないのでしょうか? ここでは、それについて説明します。
CallFindStringsUIModel の
findArrayOfYourString アクション・バインディングを選択すると、プロパティ・インスペクタに
結果位置 というプロパティを確認できます。 結果位置 プロパティの値は、実行時にフォワード先アクションがメソッド結果を参照できるように、EL表現によって格納する場所を指し示します。
findArrayOfStrings アクション・バインディングの場合は、デフォルトで次のように設定されます:
MyServiceDataControl.methodResults.MyServiceDataControl_dataProvider_findArrayOfYourStrings_result
これによって、ADFバインディング層は、メソッド実行結果を、
MyServiceDataControl の
methodResults HashMap オブジェクトに、
MyServiceDataControl_dataProvider_findArrayOfYourStrings_result というキー名で格納します。
StringResultsUIModel の
findArrayOfYourString アクション・バインディング定義をプロパティ・インスペクタで見てみると、
結果位置 プロパティに同じ値が設定されていることがわかります。ADFバインディング層では、一つのリクエストの過程で、メソッドに対するアクション・バインディングが実行される場合に、一旦、その
結果位置 プロパティにあたるオブジェクトが null であるかどうかを評価し、null ではない場合(これはつまり、一連のページフロー内の事前に実施されたアクションで既にメソッドが実行され、結果が取得されていることを意味します)、それをメソッド結果として利用します。このようにして、ベースのサービス・メソッドを何度も実行してしまうコストを避けるよう工夫されています。
メソッド結果へのバインディングに関する基礎知識を理解したところで、2つ目の例を使って、コンセプトにより深く迫りましょう。このセクションで触れる例は、次のように構成されています:
AcceptMyTypeArgs.jsp
MyType[] findArrayOfMyType(String,int,String,int)
MyTypeResults
サービス・メソッドは、4つの引数(
String および
int型)を受け取って、
MyTypeの配列を返します。
MyType は、シンプルなJavaBeanで、
name と
age というプロパティを持ちます。さらに、
AcceptMyTypeArgs.jsp 、
CallFindMyType データアクション、
MyTypeResults データページを前の例と同様の手法で作成しています。
前の例と比べて、今回の例で興味深い2つのポイントがあります:
AcceptMyTypeArgs.jsp を見ると、4つのテキスト入力フィールドがあるHTMLフォームで、そのうちの2つが"
name" という名前で、もう2つが "
age" という名前であることを確認できます(
図24)。一般に、HTMLフォーム内で繰り返しのフィールド名で送信を行うと、それは自動的に
String[] 型の値として取得されます。したがって、このフォームでは、
name と
age という名前の2つの配列値パラメータを取得します。EL表現内の
paramValues キーワードは配列値HTTPパラメータに対しても利用可能です。
Struts ページフロー図がアクティブな状態で、構造ペインから
CallFindMyType アクション・マッピングを選択すると、メソッド起動に関連するカスタム・パラメータを参照できます。 4つの
paramNames[n] プロパティに対して、次のような値が設定されていることが、プロパティ・インスペクタから確認できます:
| パラメータ名 | パラメータ値 |
|---|---|
paramNames[0]
|
${paramValues.name[0]}
|
paramNames[1]
|
${paramValues.age[0]}
|
paramNames[2]
|
${paramValues.name[1]}
|
paramNames[3]
|
${paramValues.age[1]}
|
最初の
name と
age の値が 1つ目および2つ目のメソッド引数で、次の
name と
age が3つ目および4つ目のメソッド引数として渡されます。
データ・コントロール・パレットから
findArrayOfMyType(String,int,String,int) オペレーションの下の
return ノードを "Read-Only Table" として、
MyTypeResultsUIModel.jsp にドラッグ&ドロップすると、暗黙的に次のものが作成されます:
findArrayOfMyType アクション・バインディング:メソッドへのバインド情報を保持findArrayOfMyTypeIterator イテレータ・バインディング:メソッド結果へのイテレータfindArrayOfMyType1 レンジ・バインディング:(イテレータと関連して)メソッド結果の属性のテーブル表示を処理既に、
MyTypeResultsUIModelの
findArrayOfMyType1 のようなレンジ・バインディングについては、本ドキュメント内の多くの例で見てきました。ここで唯一見ておきたい点は、
MyTypeResultsUIModel.jsp 内で、メソッド結果のコレクション値である
MyType ビーンのプロパティ属性を参照するEL表現についてです。
以下の
MyTypeResults.jsp 内のコードは、
<c:forEach> ループによって、
findArrayOfMyType1という名前のレンジ・バインディングの範囲内のデータに対して繰り返し処理している部分です。メソッドは
MyType[]を返すため、結果の配列内の各データは、
MyType インスタンスの
name と
age のプロパティを持ちます。ループの各回で
Row 変数を使って、現行の
MyType インスタンスのビーン・プロパティにアクセスできます。
<!-- Snippet from MyTypeResults.jsp --> <c:forEach var="Row" items="${bindings.findArrayOfMyType1.rangeSet}"> <tr> <td><c:out value="${Row['age']}"/> </td> <td><c:out value="${Row['name']}"/> </td> </tr> </c:forEach>
これまでに、数値によるインデックス値の使用については見てきました(
${paramValues.name[0]}など)。ここでは、
Row['age'] や
Row['name'] という EL表現が使われています。インデックスとして文字列を使用すると、その値に一致する名前のプロパティへのアクセスが可能になります。つまり、
Row['age'] は、これまでに見てきた値指定による構文、
Row.age へのアクセスと同義になります。
文字列値によるインデックス指定の表記は、ドット表記よりも柔軟性があります。たとえば、
myvar という変数に対して、その値が "
age" であった場合、EL表現
${Row[myvar]} によって、
age プロパティの値を取得できます。既にお分かりでしょうが、 ${Row.myvar} のようなドット表記では、
myvar という(存在しない)名前のプロパティにアクセスしようとてしまい、
nullとして評価されます。配列のインデックスは、一つの文字列変数名である必要はなく、いくつかの変数による組み合わせ表現であってもかまいません。たとえば、変数
foo が "
a" という値を持ち、変数
bar が "
ge" という値を持つような場合は、
${Row[foo+bar]} によって
age プロパティの値を取得できます。
最後の例では、2つの興味深いポイントがあります。
MyType)を渡す MyType自体をデータ・コントロールにして、パラメータ・コレクションの過程でそれをデータ・バインディングに含めるこの例は次のように構成されています:
AcceptMyTypeArgs2 データページ
MyType[] findArrayOfMyType(MyType)
MyTypeResults2
MyTypeResults2.jsp ページは、前述の例の
MyTypeResults.jsp とほぼ同じで、時間をかけて見るほどの違いはありません。ただし、このシナリオでは、パラメータ・ページである
AcceptMyTypeArgs2 をデータページとして実装しています。このベースとなる
AcceptMyTypeArgs2.jsp ページは、
AcceptMyTypeArgs.jsp と似ていますが、以下の点に違いがあります:
name と
age のフィールドのペアを2つ用意するのではなく、一対の
name /
age フィールドを用意する
アプリケーション・ナビゲータ上で
MyType.java に対して、右クリック・メニューから
データ・コントロールの作成 を選択して
MyTypeDataControlを作成します。これにより、
MyType ビーン・インスタンスへのバインディングが可能になります。
MyTypeDataControl の
name と
age プロパティを "Input Field" として
AcceptMyTypeArgs2.jsp ページにドラッグ&ドロップすると、
図25のようなUIモデルが構成されます。ここには、
MyType ビーンのシングルトン・インスタンスに対するイテレータ・バインディング
MyTypeDataControl_rootIterator と、
name /
age プロパティに対するコントロール値バインディングが含まれます。
AcceptMyTypeArgs2 データページ自身に「ポストバック」されると、ADFバインディング層では、(以前に学んだ
BindingContainerActionForm を経由して)HTMLフォームから送信された対応する値を使って、
MyType ビーンの MyTypeDataControl のインスタンスの
name と
age
のプロパティを更新します。
(Submit) ボタンの名前は
event_Callになっています。したがって、このボタンを押すと、"Call" イベントが発生します。
AcceptMyTypeArgs2 データページと
CallFindMyType2 データアクションの間のフォワードの名前が "
Call" であることから、処理されるイベントの名前と一致するため、自動的にフォワード処理されます。これはつまり、
(Submit) ボタンが押されると、モデルの値を更新したうえで、
CallFindMyType2 アクションによってメソッドが起動され、結果を表示する
MyTypeResults2 データページへと転送されます。
図26 は
CallFindMyType2 データアクションのUIモデルです。データ・コントロール・パレットからデータアクション上にオペレーションをドラッグ&ドロップして
findArrayOfMyType アクション・バインディングを追加し、さらに、手動で(UIモデルタブ上の右クリック・メニューによって)MyTypeDataControl に対するルートのイテレータ・バインディングも追加しています。そのイテレータ・バインディングの名前は好きにつけられますが、これまでのほかの例と同じ命名パターンにならって、
MyTypeDataControl_rootIterにしています。
このイテレータ・バインディングの存在によって、
CallFindMyType2 アクションの
paramNames[0] プロパティの値として、次のようなEL表記を使用できます:
${bindings.MyTypeDataControl_rootIter.currentRow.dataProvider}
この宣言的な設定の結果として、呼び出される findArrayOfMyType メソッドの第一引数に、
MyTypeDataControl で表現される
MyType のインスタンスが引き渡されることになります。
ViewController プロジェクトを実行して
Results of Method Passing Bean Data Control as Bean Argument リンクをクリックすると、この機能を確認できます。たとえば、
name="Steve"、
age=36のように入力して送信すると、"Steve" に対応する
MyType に加えて、
{ name="Steve, Sr.", age=56 } の 2つの
MyType のインスタンスを含む配列を取得することになります。この最後の処理については、
MyService.java クラスの
find の実際のコードを見て確認してください。
ArrayOfMyType
図19にあるページフロー図を見ると、メソッド結果へのバインディングを行った3つのページフローに共通な事実として、カスタム・メソッド起動のために独立したデータページを使用していることに気づきます。そのデータアクションは、その後、適切な "結果の" データページへ、メソッド結果の表示のために転送します。これら3つの例を見ると、次のような疑問をもつかもしれません:
"このようなデータアクションをはさまずに、結果を表示するデータページ上に、直接、起動するメソッドをドラッグ&ドロップすることはできないか? メソッド起動のために独立したデータアクションがなぜ必要なのか?"
これはするどい質問で、その答えの背景にある詳細な事実は知っておく価値が十分にあるものです。
独立したデータアクションでカスタム・メソッドを実行することの必要性を理解するには、データアクションのライフサイクルの中で、メソッドがどういう
順番で起きているかを深く理解することが重要です。
表1 は、ライフサイクルを構成する主要なメソッドを浮き彫りにしたものです。
prepareModel() フェーズにおいて、バインディング・コンテナ内のすべての最上位レベルのイテレータ・バインディングが(必要に応じて)実行されるという事実を思い出してください。
invokeCustomMethod() フェーズでは、データアクションに関連付けられたカスタム・メソッドが実行されます。一般に、そのようなカスタム・メソッドは、データ・コントロール・パレットからデータアクションやデータページ上にドラッグ&ドロップした結果、関連付けられます。
| 手順 | メソッド | 説明 |
|---|---|---|
| 1 |
buildEventList()
|
このリクエスト間に処理される必要のあるイベントのリストを作成します。 |
| 2 |
prepareModel()
|
モデル情報の更新の準備をします。この作業の中で、バインディング・コンテナに対して
refreshControl()をコールして、まだ実行されていない "最上位レベル" のイテレータ・バインディングを実行します。
注意: これによりアクション・バインディングの起動が行われる場合は、 initializeMethodParameters() メソッドはアクション・バインディングの
doIt() メソッドより先にコール
されない ことになります。
|
| 3 |
shouldAllowModelUpdate()
|
現行リクエストで送信されてきたパラメータ値でモデル情報を更新してよい場合、true を返します。
|
| 4 |
processUpdateModel()
|
モデル情報の更新が許可されている場合、リクエストによって(バインディング経由で)送信されてきたパラメータの値でモデル値を更新します。 |
| 5 |
validateModelUpdates()
|
モデル情報の更新が許可されている場合、モデル層に対して更新内容の検証を実施します。 |
| 6 |
processComponentEvents()
|
ここまでにエラーが出なければ、イベント・リスト内のイベントを処理します。これによりアクション・バインディングの起動が行われる場合は、
initializeMethodParameters() メソッドはアクション・バインディングの
doIt() メソッドより先にコールされます。
|
| 7 |
invokeCustomMethod()
|
ここまでにエラーが出なければ、(必要に応じて、
initializeMethodParameters() をコールしてパラメータの設定をした後で、)データアクションに関連付けられたメソッドを起動します。データアクションやデータページに対してデータ・コントロール・パレットからドラッグ&ドロップで設定されたメソッドはこのフェーズで実行されます。
|
| 8 |
refreshModel()
|
ここまでにエラーが出なければ、モデル層に変更が完了したことを通知します。 |
| 9 |
reportErrors()
|
リクエスト中に発生するすべてのエラー処理を行います。 |
| 10 |
findForward()
|
フロー内の「次の処理先」へ転送するために使用されるページ・フォワードを選択します。これは、通常のデータアクションでは、デフォルトは "success" です。データページでは、デフォルトは現行アクションの処理先のページになります。 |
ADFにおいて、イテレータ・バインディングは、モデル層のデータ・コレクションと連携するための重要な要素であることを思い出してください。この考え方はカスタム・メソッドの結果に対する場合でも違いはありません。ADFでは、メソッド結果の戻り値に対しての繰り返し処理を行う、特別な "メソッド結果イテレータ" を提供してくれます。「
コレクション値プロパティに対するイテレータ」セクションで、データアクション・ライフサイクルの
prepareModel() フェーズで、初回に、必要に応じて、バインディング・コンテナ内のイテレータが実行されることについて触れました。また、イテレータ・バインディングに対して
executeQuery() メソッドを使用して、必要なときにイテレータ内のデータをリフレッシュすることが可能なことについても触れました。
たとえば、データベース検索結果に対するイテレータ・バインディング上で
executeQuery() メソッドがコールされると、そのベースとなっている問合せが再実行され、最新の検索結果が取得されます。同様に、"
メソッド結果イテレータ" 上で
executeQuery() がコールされると、ベースとなっているアクション・バインディングの
doIt() メソッドが再実行され、最新の結果が取得されます。これは、メソッド・アクション・バインディングの場合、パラメータを引き渡した後、実際のサービス・メソッドが起動され、結果を
result プロパティに格納します。また、ADFバインディング定義で指定された
ResultLocation(または結果位置)プロパティにその参照を格納します。
ライフサイクル内の
prepareModel() フェーズは、
processModelUpdates() と
invokeCustomMethod() のフェーズの前にあります。つまり、メソッド結果に関連したイテレータは
prepareModel() フェーズ内で実行されますが、それは
invokeCustomMethod() フェーズで実行される以前のタイミングです。
invokeCustomMethod() フェーズでは、コントローラ層が明示的にアクション・バインディングを実行し、
initializeMethodParameters() メソッドに、宣言的EL表現のパラメータ定義を評価して得られたメソッド・パラメータをはめ込みます。しかしながら、
prepareModel() フェーズでは、アクション・バインディングが暗黙的にモデル層によって実行されるため、カスタム・メソッドにパラメータをはめ込む手段がありません。
したがって、問題は、次の組み合わせを同一のDataActionで処理する場合に発生します:
このような場合、prepareModel() フェーズで、バインディング層によってサービス・メソッドが実行されますが、それは、コントローラ層によるメソッド引数の初期化処理の手順を通過する前です。このため、サービス・メソッドが引数なしで実行可能でない限り、必然的に
IllegalArgumentException や
NullPointerException のような例外が発生します。
| 注意: |
ADF アクション・バインディングのパラメータ定義では、任意にEL表現によってパラメータのデフォルト値を指定することを
可能にしています。これは、UIモデルから、対象となるアクション・バインディングのパラメータ名を選択して、プロパティ・インスペクタから値を設定します。この際、表記は
|
この問題の解決のためには、メソッドに渡すべきパラメータの設定を行う前にメソッド・イテレータが実行されるのを避けるようにしなければなりません。カスタム・メソッドの起動を独立したデータアクションに分けて実行し、その後で、その結果に対するメソッド・イテレータを使用する "結果" のページへフォワードするようにすると、宣言的手法のまま対応できます。
この分割型手法では、DataActionのライフサイクルは2度実施されることになります:一度目はデータアクションのメソッド起動時、二度目は結果を表示するデータページの処理過程です。ライフサイクルの最初の過程では、メソッド起動のためのバインディング・コンテナには、アクション・バインディングだけがあり、メソッド・イテレータ・バインディングは含まれません。このため、
prepareModel() フェーズでは、ライフサイクルを通して、暗黙的なメソッド実行が行われません。
invokeCustomMethod() フェーズが起こると、
initializeMethodParameters() をコールしてパラメータ値が設定され、サービス・メソッドの起動が期待通りに実施されます。
ライフサイクル・フェーズは、結果を表示するデータページの処理過程でも繰り返されます。
prepareModel() によって、メソッド・イテレータ・バインディングが実行されます。ベースのメソッド実行によって、
ResultLocation(結果位置) プロパティで指定されたEL表現で識別されるオブジェクト位置に結果が配置され、現行リクエストで結果が取り出されたことが通知されます。これにより、同じリクエスト内でのメソッド起動のコストを回避し、既存結果を使ってメソッド・イテレータを処理できます。
問題点について十分に理解し、データアクションのライフサイクルについても理解を深めたところで、読者の中には、カスタム・メソッド起動に関する問題に対して、独立したデータアクションを使用せずに、プログラミングによって対応策を実装する手法がないか、興味を持った方もいることと思います。
同じバインディング・コンテナ内で、宣言的にメソッドを起動し、さらにメソッド・イテレータを使用することによる問題の根本は、
prepareModel() フェーズで起こっていました。したがって、データアクション・クラスを拡張したカスタム・クラスを用意し、そこで
prepareModel() を適切にオーバーライドし、
super.prepareModel() の実行前に、パラメータの設定を行うようコーディングすればよいことになります。たとえば、前述の
findArrayOfMyType(MyType) メソッドでは、コードを次のようにします:
/* Overrides DataAction.prepareModel() */ protected void prepareModel(DataActionContext ctx) throws Exception { MyType emma = new MyType("Emma",8); ArrayList params = new ArrayList(); params.add(emma); DCBindingContainer bc = ctx.getBindingContainer(); JUCtrlActionBinding ab = (JUCtrlActionBinding)bc.findCtrlBinding("findArrayOfMyType"); ab.setParams(params); super.prepareModel(ctx); }
findArrayOfMyType(String,int,String,int) の場合は、次のようにします:
/* Overrides DataAction.prepareModel() */ protected void prepareModel(DataActionContext ctx) throws Exception { ArrayList params = new ArrayList(); params.add("Emma"); params.add(new Integer(8)); params.add("Amina"); params.add(new Integer(6)); DCBindingContainer bc = ctx.getBindingContainer(); JUCtrlActionBinding ab = (JUCtrlActionBinding)bc.findCtrlBinding("findArrayOfMyType"); ab.setParams(params); super.prepareModel(ctx); }
DataAction に、EL表現によるパラメータ値の設定(Strutsアクション・マッピング定義内の
paramNames[n]の値 )を行わせたい場合は、単純に、適切なアクション・バインディングを引数として、initializeMethodParameters() をコールするように
prepareModel() をオーバーライドします。
/* Overrides DataAction.prepareModel() */ protected void prepareModel(DataActionContext ctx) throws Exception { DCBindingContainer bc = ctx.getBindingContainer(); JUCtrlActionBinding ab = (JUCtrlActionBinding)bc.findCtrlBinding("findArrayOfMyType"); initializeMethodParameters(ctx,ab); super.prepareModel(ctx); }
EL表現による宣言的パラメータ設定と、プログラミングによるパラメータ取得の両方を組み合わせるには、次のようなコードを、
initializeMethodParameters() の後で、
super.prepareModel() の前に追加します:
/* Get ordered list of parameter objects */ ArrayList params = ab.getParams();もしくは
/* * Get parameter objects in a map, keyed by their parameter name as defined * in the action binding parameter properties. */ Map paramMap = ab.getParamsMap();
イテレータの暗黙的な実行(メソッド結果イテレータを含む)は、バインディング・コンテナがリフレッシュされた初回のみ実施されます。したがって、要求に応じて、各リクエストで再実行が必須の場合などでは、
prepareModel() メソッドをオーバーライドして、メソッド・イテレータ・バインディングに対して明示的に
executeQuery() をコールするなどの作業が必要になるかもしれません。
逆に、「独立したデータアクションでのカスタム・メソッドの起動」アプローチでは、カスタム・メソッドは、このアクションが使用されるたびに必ず実行されます。これは、メソッド起動が明示的であり、暗黙的ではないことが保証されているからです。
これで、ADFバインディング層に関する重要機能の技術解説は、一通り完了です。特に Struts と ADFデータアクションとの連動によってもたらされる機能についての技術詳細への理解も深まったことでしょう。