Oracle ADFによるデータ・バインディング: Part 2
イベント処理とメソッドの戻り値の処理

2004年7月

概要

この資料は、『 Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の資料に続く、より詳細なイベント処理フローとそれに関連する技術的内容を説明したものです。本書の前に『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』を読んで、基本的なデータ・バインディングの仕組みと利点を理解されることをお勧めします。

目次

         ADF DataAction によるイベント処理の理解
                 サンプル・アプリケーションの概要
                 Webアプリケーションにおけるイベントとは?
                 Struts DispatchActionを利用したイベント処理
                 DispatchAction によるケース・スタディ
                 同機能の DataAction による実装
                 国際化対応への配慮
                 ADFバインディングと宣言的なイベントの利用
                 宣言的なイベント・ベースのページ・ナビゲーション
         メソッド結果に対するデータ・バインディング
                 サービス・メソッドの戻り値へのバインディング
                 コレクション値プロパティに対するイテレータ
                 String 引数のあるメソッドへのバインディング
                 String および int 引数のあるメソッドへのバインディング
                 Beanデータ・コントロールの引数としての利用
                 カスタム・メソッド起動のために独立したデータアクションを使用する理由
         最後に

ADF DataAction によるイベント処理の理解

より洗練された動的なWebアプリケーションを構築できるように、ADF DataAction によって、簡単で、イベント駆動のメカニズムが提供されています。これを利用すれば、ページ内のリンクやボタンをクリックしたときに処理すべき操作を容易に制御できるようになります。ここでは、理解を深めるために用意されたサンプル・アプリケーションを使って、その技術的背景を説明します。サンプル・アプリケーションはここからダウンロードできます:

DataActionEventsExample.zip.
注意:

このサンプル・アプリケーションを実行するためには、接続ナビゲータの"データベース" の下にscott という名前の接続を作成する必要があります。 それは、ユーザーがアクセスできるデータベース上のSCOTT/TIGER スキーマを指していなければなりません。


サンプル・アプリケーションの概要

図1 は、サンプル・アプリケーションの全体的なページフローを示しています。前述のリンクから zip ファイルをダウンロードして展開し、その中の DataActionEventsExample.jws ワークスペースを開いて、 ViewController プロジェクト内のページ・フロー図を見ると、これを確認することができます。

Page Flow for DataAction Events Example
図1: サンプル・アプリケーションのページ・フロー図

ViewController プロジェクトを実行すると、次のようなページ(index.jsp)が表示されます( 図2)。

Data Action Event Examples Home Page
図2: サンプル・アプリケーションのトップ・ページ

Struts DispatchAction ExampleDispatchExample) 、 ADF DataPage ExampleDataPageExample)、 Example2DataPageExample2)、 ADF Declarative ExampleDataPageDeclarative)はすべて、同じ機能をもつページで、整数入力値の増加、減少、値の更新を行います。ユーザーによる操作の結果、整数値が "10" になると、"You Win! " というメッセージが表示されるというものです。 このサンプルで提供されるさまざまなアプローチを考察することで、Oracle ADFとStrutsとの間のイベント処理に関する理解を深めることができるはずです。

Events, Actions, and Forwards ExampleUpdateEmp1および UpdateEmp2)は、シンプルな従業員データの編集フォームを2つの方法で実現したもので、ADFのイベント処理メカニズムに関する興味深い事実を明らかにするでしょう。


Webアプリケーションにおけるイベントとは?

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 DispatchActionを利用したイベント処理

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の組み合わせの利用によって、コントローラ層とビュー層の機能を分離できます。

DispatchAction によるケース・スタディ

サンプルにあるDataActionEventsExampleワークスペースの DispatchExample アクションと DispatchExample.jsp ページは、実行すると、 図3のようになります。この例では、Struts DispatchActionを使用して、「ポストバック」パターンを実装しています。

The Struts DispatchAction Example
図 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" という名で送信された値を、 ExampleFormBeanvalue プロパティにセットします。この例では、 (Update) ボタンに対してカスタム・イベント・コードは必要としていません。それゆえ、 (Update) ボタンには name プロパティを指定していません。後は、アプリケーションが機能するように、コントローラ層のロジックを用意する必要があります。これは次のようなコードです:

  1. "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");
    }
    
  2. "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");
    }
    
  3. 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() のメソッドが IncrementDecrement というイベントそれぞれのために必要になります。

もう一つの追加手順として、 DispatchExampleActionunspecified() メソッドをオーバーライドして、 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 のリンクをクリックすると、 ここまでに説明した基本機能を実装したアプリケーションを確認できます。

You Winページ
図 4: You Winページ

同機能の DataAction による実装

ここまでで、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 と異なる部分は次のようになります:

  1. ADF DataAction ベースのアクション・クラスでは、拡張されたStrutsアクション・マッピング・クラスが必要です。

    DataActionMapping クラス( oracle.adf.controller.struts.actions パッケージ)は、Struts ActionMapping の拡張で、ページ・リクエスト・ライフサイクルとデータ・バインディングの自動化を計るために、ADFバインディング層とDataActionにとって必要ないくつかの追加パラメータを取得します。このような場合、アクション・マッピングの className 属性の値で、カスタムのアクション・マッピング・クラス名を指定できます。

  2. parameter 属性の値は、前の例のように "event" を設定するのではなく、DataAction がデフォルトで転送するJSPページ名を設定します。

    ADF DataActionは、Struts DispatchAction とStruts ForwardAction に機能を組み合わせた一つの便利なクラスで、ADFバインディング層との調整作業も実装しています。Struts ForwardAction では parameter 属性で転送先のページ・パスを指定します。 parameter 属性では、2つ以上の異なる値を指定できませんが、ADF DataAction では、イベント用のパラメータの名前を " event" と規定することで、 ForwardAction のように parameter 属性を転送先パスのために使用できるようにしています。

    注意:

    必要なら、プログラミングによってカスタムのDataActionクラスを作成して、イベント用パラメータの名前を変更することもできます。これは、オーバーライドした handleLifecycle() 内で、 super.handleLifecycle()をコールする前に、 DataActionContextsetEventPrefix()メソッドを使って指定します。


  3. サンプルの controller.DataPageExampleAction は、Struts DispatchAction でなく、ADF DataForwardAction クラスを拡張して構成しています。

controller.DataPageExampleAction を作成してカスタムのコントローラ・コードを記述するには、Strutsページフロー図上で DataPageExample のアイコンをクリックして、右クリック・メニューから 「コードに移動」を選択します。これにより、 図5のようなダイアログが表示され、クラス/パッケージ名を指定できます。 OK をクリックすると、 DataPageExampleAction クラスのスケルトン・コードが作成されます。

Struts Data Actionの作成
図 31: Struts Data Action の作成

controller.DataPageExampleAction クラスに追加するコードは、 DispatchAction の例で紹介した実装と同じ処理です。

  1. " 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);
      }
    
  2. " Decrement" イベントに対応して値を減少させるメソッドを用意します:

     
     /**
    
    * When the "event" parameter has the value "Decrement" (or "decrement"),
    * the DataAction will delegate to this method to handle it.
    */ public void onDecrement(DataActionContext ctx) { ExampleFormBean formBean = (ExampleFormBean) ctx.getActionForm(); formBean.setValue(formBean.getValue() - 1); }
  3. イベント処理完了後、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 ベースの実装との主要な違いは次のとおりです:

  1. イベント処理メソッドの名前が Eventnameではなく、 onEventnameという形式になっています。

  2. DispatchAction ベースのイベント処理メソッドでは、4つの引数を持ち、 ActionForward を返すものでしたが、この例では、 DataActionContext だけを引数とし、返り値のない(void)メソッドです。

    注意:

    DataActionを利用すると、(引数として渡された) DataActionContextの適切なgetter メソッドから、データ・アクション・マッピング、FormBean、HTTPリクエスト/レスポンスなどの主要オブジェクトへアクセスできます。


  3. 通常、カスタム・アクションではStruts Actionの execute() をオーバーライドしますが、ADF DataAction では、ページ処理ライフサイクル内で転送先の検知("Find Forward")処理を調整できるよう、 findForward メソッドをオーバーライドしています。
    したがって、メソッドの結果として ActionForward を返す手法ではなく、 DataActionContextsetActionForward() メソッドを使って、メソッドの結果となる ActionForward をセットしています。

  4. DispatchAction の例のような、 unspecified メソッドは必要ありません。

    デフォルトで、ADF DataActionevent パラメータの指定がない場合でも例外を出さないように作られています。

ViewController プロジェクトを実行して ADF DataPage Example リンクをクリックすると、Struts DispatchAction の例と同じ機能を持つ ADF DataAction ベースの例を確認できます( 図6

ADF DataPage による例
図 6: ADF DataPage による例

国際化対応への配慮

ADF DataPage Example2 は、トップ・ページの Example2 リンクからアクセスできます。これは前述のDataPageによる例とほとんど同じですが、起動されるイベント名に関して、少し違ったアプローチをとっています。

DispatchExample と ADF DataPage の例では、"Increment" イベントを起動するボタンは次のようにして作成されていました:

 
<input type="submit" name="event" value="Increment"/>

つまり、"event" がボタンの名前で、"Increment" がボタンの値(ラベルの値であり、送信される値でもある)を表しています。このような方法では、ボタンのラベル文字とイベント名を一致させないといけないという制限を生じます。

国際化対応のJSPページの作成

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 が実行時に適切にそれらを使用するように構成できます。たとえば、イタリア語用の ApplicationResources.properties ファイルは、 ApplicationResources_it.propertiesという名前で、次のような内容で構成します:
     # From ApplicationResources_it.properties
     button.add.one=Aggiungere Uno
     button.update=Aggiornare
     link.remove.one=Togliere Uno
ご存知のように、このファイルに日本語を書き込む場合は、Unicodeエスケープ(native2ascii)する必要があります。


ボタンのラベル値とイベント名との分離

イベント処理のメソッド名をイベント名に対応する必要がありますが、ここでもう一つの課題があります。適切な表示ラベルを持つアプリケーションを目指す場合、たとえば、ユーザーに対して (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バインディングとイベント処理のメカニズムを更に究明するために、次の例である 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は、データ・コントロール・パレットに、このプロパティとオペレーションが検知されたことを表しています。

データ・コントロール・パレット上のExampleBeanDataControl
図 7: データ・コントロール・パレット上のExampleBeanDataControl

DataPageDeclarative.jsp ページは、 DataPageDeclarative データページのビュー層を実装します。このページに対するデータ・バインドを行うために、ADFデータ・バインディング・コンテナが必要になります。この例では、次の3つのタイプのバインディングを使用します:

注意:

これらの2つの void メソッドは何も値を返しません。この資料の後半に用意した「 メソッド結果に対するデータ・バインディング」セクションでは、値が返ってくる場合のバインドについて説明します。


『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「 バインディング・コンテナ定義およびバインディング定義の作成」では、設計時に明示的または暗黙的にバインディングを作成する方法について説明しました。これらの両方のアプローチを組み合わせて、DataPageDeclarative.jspを構成する手順を説明します:

  1. まず、データ・コントロール・パレットで、 ExampleBeanDataControlvalue プロパティをクリックします。このパレットの下にある ドラッグ・アンド・ドロップの形式 リストから "Input Field" を選択します。そして、この value プロパティを空のページ上にドロップします。このとき、 図8のようなダイアログで、ドラッグ&ドロップされた入力フォームを含むHTMLフォームを同時に作成するかどうかたずねられますので、「はい」を選択します。

    フォーム要素の追加の確認
    図 8: フォーム要素の追加の確認

    この作業によって、暗黙的に以下の物が作成されます:

    • DataPageDeclarativeUIModel という名前のバインディング・コンテナ
    • ExampleBean に対する ExampleBeanDataControl_rootIter という名前のイテレータ・バインディング
    • ExampleBean の value プロパティに対応する value という名前のコントロール値バインディング
  2. 次に、 increment メソッドをクリックして、"Button" としてHTMLフォーム内( <html:form> タグを表す点線内)にドラッグ&ドロップします。

    "increment" メソッドに対するボタンを追加する
    図 9: "increment" メソッドに対するボタンを追加する

    この作業によって、 increment という名前のコントロール・アクション・バインディングが新しく追加されます。

  3. 最後に、明示的アプローチによって、 decrement メソッドに対するアクション・バインディングを作成してみます。手順は次のとおりです:

    • 構造ペインの "UI モデル" タブをクリックします。
    • ルートの DataPageDeclarativeUIModel ノードをクリックして、右クリック・メニューから バインディングの作成アクションAction を選択します。
    • アクション・バインド・エディタが表示されます。 ExampleBeanDataControlをクリックして、 操作の選択 リストから decrement()メソッドを選択し、OKを押します。

    この明示的な方法によって、 decrement が新しく追加されます。

プロジェクト内で DataPageDeclarative.jsp ページを選択して、構造ペインから "UIモデル" タブをクリックすると、 図10のように、バインディングの作成結果を見ることができます:

DataPageDeclarative.jsp に対するバインディング定義の表示
図 10: DataPageDeclarative.jsp に対するバインディング定義の表示

バインディングが作成されたので、後は、前述の例と同じ見た目になるようにデザインを調整します。この結果、 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> セクションを見ると、 DataFormBindingContainerActionForm クラスで実装されていることがわかります。これは、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にプロパティをセットしようとすると、自動的に、適切なバインディングに対する値設定の作業が行われます。

注意:

BindingContainerActionForm FormBeanは、 ADFデータ・バインディングとStruts FormBeanを使用するすべてのページで利用できます。逆に、表示のみを行うページでは、このようなFormsBeanは必要ありません。
しかし、DataActionやDataPageから " DataForm" FormBeanを削除( name プロパティをブランクにする)しても、再度フロー図を開くと、JDeveloper が自動的に' DataForm' の設定を行います。これを回避するためには、プロパティをもたない DynaActionForm を " None" というような名前で設定しておきます。
          :
     <form-beans>
          :
        <form-bean name="None" type="org.apache.struts.action.DynaActionForm"/>
          :
     </form-beans>
          :


ViewController プロジェクトを実行して ADF Declarative Example リンクをクリックすると、 図11 のようなページが表示され、前述の例と同じ機能を確認できます:

ADF DataActionの宣言的機能の利用例
図 11: ADF DataActionの宣言的機能の利用例

では、この例がどのように動いていて、どんなコードが必要なのか確認しましょう。 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" アクション・バインディングは、 ExampleBeanDataControlincrement() メソッドと連結されており、 doIt()(上記コードでは invokeActionBinding() の内部でコールされる )をコールすることで、ADFバインディング層の ExampleBeanincrement()を起動し、ビーンのプロパティ値を 1 だけ増加させます。まったく同じメカニズムで、 ExampleBeandecrement() メソッドが、ページ上の 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
}

これでお分かりのように、必要に応じて、カスタムのコントローラ・コードは、アクションを起動する前後に好きなように記述できます。

注意:

注意深い読者は、 DataPageExampleDataPageDeclarative  のわずかな違いに気付いたかもしれません。それは、イベント名の一文字目が大文字だった(Increment, Decrement)のが、小文字(increment, decrement)に変わっていることです。これは、 ExampleBean クラスに用意したメソッドが increment()/ decrement() という、小文字で始まる名前であるためです。ADFの設計サポート機能は、デフォルトのアクション・バインディング名を、対応するメソッド名と一致するように命名します。

Increment / Decrement のように、大文字で始まるイベント名である必要がある場合は、宣言的に作成されたアクション・バインディング名( increment / decrement )を変更する必要があります。バインディングの名前を変更するには、構造ペインのUIモデルタブでそれらを選択した状態で、プロパティ・インスペクタから ID プロパティを修正します。


宣言的なイベント・ベースのページ・ナビゲーション

ADF DataAction のイベント処理メカニズムのもう一つの主要な機能を理解するために、次のサンプルである Events, Actions, and Forwards Example に移ります。これは、 図1のフロー図では、上部に位置する2つのページで構成されているものです。

UpdateEmp1UpdateEmp2 データページは、(Oracleデータベースの SCOTT ユーザーが所有する) EMP表からの従業員情報を表示するものです。 図12 のように、 UpdateEmp1.jsp ページは、従業員の名前と役職を表示し、 UpdateEmp2.jsp ページは給与情報を表示します。

UpdateEmp1.jsp
図 12: UpdateEmp1.jsp

ページの上部にある2つのボタンはタブ・ページの切替のように機能し、2つのページ間を移動する目的で使用します。下部にある4つのボタンは、すべての変更の保存、取消と、編集する従業員情報を次に進む、または一つ戻るために使用できます。ユーザーは、Deptno の情報に関しては、部門番号のような数値ではなく、それと関連する実際の部門名で構成されるリストから選択できるようになっています。

注意:

ここで紹介したデータ連携されたリストボックスの宣言的な作成方法に興味のある方は、 UpdateEmp1.jsp に対するバインディング・コンテナ内(UIモデルタブ内)の Deptno という名の LOVバインディングをクリックして、右クリック・メニューから 編集... を選択してみてください。これにより、このリストボックスのために設定されるバインディングに関する情報を確認できます。


EmpServiceDataControl図13)は、表示および編集対象となる Employees データソースと、"Deptno" リストボックスの中身になるデータを保持する DepartmentLOV データソースを提供します。このデータ・コントロールの実体は、 Model プロジェクト内の model.EmpService (ADFアプリケーション・モジュール・コンポーネント)です。それには、 Employees および DepartmentLOV という名前のインスタンスが含まれており、それぞれ、 model.views.Employees および model.views.DepartmentLOV というビュー・オブジェクト定義に対応しています。

The EmpServiceDataControl
図 13: EmpServiceDataControl

2つの従業員情報編集ページはいくつかの機能をサポートします:

ADF DataAction のイベント処理メカニズムの機能を活用すると、これらの機能はすべて、カスタムのコントローラ・コードなしで実装できます。以降では、その機能や設定方法を説明することで、ADF DataActionの宣言的アプローチについて究明していきます。

Events, Actions, and Forwards Example アプリケーションにはカスタム・コードが必要ないため、 struts-config.xml でも、 UpdateEmp1UpdateEmp2 のアクションに対するクラスは 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)を確認できます。

UpdateEmp1.jspページに対するバインディング情報
図 14: UpdateEmp1.jspページに対するバインディング情報

CommitRollbackのアクション・バインディングは、データ・コントロールの組み込みメソッド( Commitおよび Rollback)と結合しています。このため、これらに関して、追加のカスタム・コードは必要ありません 。 NextPrevious のアクション・バインディングは、イテレータに対する組み込みメソッド( Next および Previous)と結合しています。このため、同じ理由で、カスタム・コードは不要です。 これらのバインディングは、データ・コントロール・パレットから組み込みの操作(Operation)をJSPビジュアル・エディタ上にドラッグ&ドロップすれば、暗黙的に作成されます。一点注意すべきなのは、 図9 で説明したように、ドラッグ&ドロップする際に、HTMLフォームの内部に追加するように気をつけることくらいです。もう一つの残っているイベント ShowCompensationInfo については後ほど説明します。

同様にして、 UpdateEmp2.jsp ページにも SaveAll, CancelAll, Next, Previous, ShowNameInfo イベントに対応するボタンがあります。 図15 では、このページに対するUIモデルで、対応する SaveAll, CancelAll, Next, Previous のアクション・バインディングがあることが確認できます。

UpdateEmp2.jspページに対するバインディング情報
図 15: UpdateEmp2.jspページに対するバインディング情報

UpdateEmp1.jsp と同じ方法でこれらのボタンに対するバインディングを設定できます。ここには、ADF設計時サポート機能によって作成されたデフォルトのイベント名ではない名前を使用したい場合のために、その例を用意してあります。デフォルトでは、 Commitおよび Rollbackの操作をページに追加すると、そのまま (Commit) および (Rollback) ボタンが次のように作成されます:

同時に、暗黙的に、 Commitおよび Rollbackというアクション・バインディングも作成されます。この、 Commit および Rollback というイベントを起動する (Commit) / (Rollback) ボタンを、 SaveAll および CancelAllというイベントを起動する (Save All Changes) / (Cancel Changes) ボタンに変更するには、各ボタンを次のように変更します

UpdateEmp1UpdateEmp2 のページの下部にあるボタンに関する実行時の処理については理解できたことと思います。ボタンはイベントを起動しますが、ADF DataActionによって、そのイベントに名前が一致するアクション・バインディングを実行する処理を自動的に実施できるように、名前づけします。これらのアクション・バインディングは、ADFのデータ・コントロールおよびイテレータに対して組み込みで提供される操作であり、前述の例で取り上げた、カスタム・メソッドを起動する increment/ decrement アクション・バインディングとは違うように思えるかもしれません。しかし、メカニズムは、組み込みメソッドでもカスタム・メソッドでも同じで、処理されるイベントの名前と一致するアクション・バインディングによって起動されます。

最後に残った理解すべき 2つのイベントが、 UpdateEmp1.jsp ページの ShowCompensationInfo イベント、および UpdateEmp2.jsp ページの ShowNameInfo イベントです。これらのイベントは、 図16 でわかるように、 UpdateEmp1UpdateEmp2 のページ間のフォワードの名前でもあります。

DataActionEventsExample ページフローのUpdateEmpページ部分
図 16: DataActionEventsExample ページフローのUpdateEmpページ部分

ShowCompensationInfo という、 UpdateEmp1 から UpdateEmp2 へのフォワードがあります。デフォルトでは、ページ処理ライフサイクルの findForward() のフェーズで、ADF DataAction は、DataActionContextgetActionForward() をコールして、開発者が、事前にActionForwardの名前を設定しているかどうか確認します。このチェックで nullが返ってくる場合、これは開発者が、使用するActionForwardをプログラム的に設定していないことを意味し、便利な代替の振る舞いとして、イベントと同じ名前のフォワードを探そうとします。名前が一致するフォワードが見つかると、デフォルトの findForward() 実装で、そのActionForwardが設定されます。

このような理由から、カスタムのコントローラ・コードを記述していなくても、ユーザーが (Show Compensation Info) ボタンをクリックすると、 UpdateEmp2.jsp ページが表示されます。 つまり、 ShowCompensationInfo イベントが発生すると、ADF DataAction によって、宣言的ナビゲーションの実施のために、名前が一致する ShowCompensationInfo フォワードが使用されます。

例にはあげていませんが、この宣言的ページ・ナビゲーションと、ここまでに学んだイベント処理とを組み合わせて利用することも、もちろん可能です。カスタムのデータ・アクション・クラス内で onShowCompensationInfo() メソッドを用意しているような場合、イベント処理コードは、ライフサイクルの findForward() フェーズより前にコールされます。イベント処理コードで、プログラム的に DataActionContextsetActionForward() がコールされると、宣言的ページ・ナビゲーションの動作は迂回されます。つまり、プログラム的に設定された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"へのページ・ナビゲーションの両方を実施します。

The "Compensation Info Page" (UpdateEmp2.jsp)
図 17: "Compensation Info Page" (UpdateEmp2.jsp)

これでわかることは、宣言的なアクション・バインディング起動と、宣言的なページ・ナビゲーションは同時に利用可能だということです。最後に、ここまでのいくつかの例を通して見てきた、アプリケーションに有用で、どんな組み合わせでも利用可能な、ADF DataActionの3つの重要な機能についてまとめておきます。 YourEvent という名のイベントが起動された場合...

  1. リクエストを処理するデータ・アクション・クラスに public void onYourEvent(DataActionContext ctx) というメソッドがある場合、そのカスタム・コードがイベントを処理するために起動されます。
  2. YourEventという名前のアクション・バインディングがある場合、それが起動されます。
    1 のメソッドと組み合わせて使用する場合、イベント処理コードでは、明示的に次のようなコードで現行イベントに対するアクションを起動する必要があります:

     
    if (ctx.getEventActionBinding() != null) ctx.getEventActionBinding().doIt();
  3. YourEventという名前のフォワードがある場合、それが次の制御先のページを決定するために使用されます。
    1 のメソッドと組み合わせて使用する場合、イベント処理コードで ctx.setActionForward() をコールすると、それによって設定された転送先のほうが優先されます。



メソッド結果に対するデータ・バインディング

『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「 実際のアプリケーション例によるケーススタディ 」で紹介した ADFBindingIntro サンプル・アプリケーションでは、コレクション値のデータ・コントロール・プロパティへのデータ・バインディングを紹介しました。具体的には、そのサンプルでは、次のようなことが行われていました:

JavaBeans の標準に従い、ADFデータ・コントロールはプロパティとメソッドの両方を公開します。ADF設計機能サポートでは、次のような名前のメソッドがある場合に、 Somethingという名前のデータ・コントロール・プロパティの存在を認識します:

その他の名前のメソッドはサービス・メソッドとして取り扱われます。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 は、このクラスに対して、アプリケーション・ナビゲータで右クリック・メニューから データ・コントロールの作成 を選択した後の、データ・コントロール・パレットです。これを見ると、 arrayOfMyTypearrayOfStrings という名前のコレクション値プロパティに加えて、3つのカスタム・メソッドが Operations フォルダの下に小さな " f()" アイコンと共に表示されていることに気付きます。メソッドのノードを開くと return ノードを確認できます。これはメソッドの戻り値を表したものです。

データ・コントロール・パレット上のコレクションとメソッドの戻り値
図 18: データ・コントロール・パレット上のコレクションとメソッドの戻り値

メソッドが JavaBean や、JavaBean のコレクションを返す場合は、さらに return ノードを開いてメソッド結果の構造を見ることができます。メソッドの戻り値がシンプルなスカラー値や、スカラー値の配列の場合は、 return ノードの下に付随する構造はなく、シンプルな戻り値として表現されます。

シンプルなデモを作成するために、 図19のようなページフロー図を構成しています。 index.jsp ページと、4つの異なるデータ・バインディング・シナリオへのリンクがあります。

メソッド結果へのバインディングに関するサンプル・ページフロー
図 19: メソッド結果へのバインディングに関するサンプル・ページフロー

HTMLテーブル内に結果を表示するデータページのコンテンツは、次のような基本的な手順を繰り返して作成します:

  1. データページを作成後、ダブルクリックして、作成するJSPページ名を決定する
  2. JSPビジュアル・エディタを使用して
    • return ノードを "Read-Only Table" としてデータ・コントロール・パレットからページ上にドラッグ&ドロップする
    • ページタイトルを追加して "Heading 2" スタイルに設定する
    • 追加されたHTMLテーブルにデフォルトで用意されている、現行レコード位置マークの列を削除する
    • テーブルをセンター位置に配置する
    • プロパティ・インスペクタを使用してテーブルの境界線プロパティを設定する
  3. コンポーネント・パレットの "CSS" ページで "Blaf" (browser look and feel)スタイルシートをページ上にドロップする

MethodBindingExample ワークスペースの ViewController プロジェクトを実行すると、 図20 のようなページが現れます。

Method Binding Example のトップページ
図 20: Method Binding Example のトップページ

コレクション値プロパティに対するイテレータ

Collections of Strings and Beans リンクは CollectionsWithNoArgs データページにリンクしています。このページでは、2つのコレクション値プロパティのデータ( MyServiceDataControlarrayOfStringsarrayOfMyType )とバインドしています。先にあげた 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 BeansCollectionsWithNoArgs データページを実行した後で、ブラウザで再読み込みしてみてください。現行時間が元のまま同じで、更新されていないことに気付きます。ADFバインディング層では、ページのリフレッシュ時には getArrayOfString() メソッドを再実行していません。これはなぜでしょう? これを理解することは、アプリケーション上のコントロールの振る舞いを理解するためにとても重要です。

ADF DataAction ページ処理ライフサイクル内の prepareModel() フェーズで、 refreshControl() メソッドがコールされます。これによって、バインディング・コンテナ内の "最上位レベルの" イテレータはすべて、未実行の場合は実行されます。なお、ここでいう "最上位レベルの" イテレータとは、データ行の "最上位レベルの" コレクション上のイテレータ・バインディングのことで、それらに付随する子のプロパティにバインドしているものは含まれません。わかりやすい例をあげると、データベースのマスター/ディテール関係のデータの場合、マスターのデータ処理が "最上位レベル" に該当し、ディテールのデータが "最上位レベルに付随するプロパティ" に該当します。

したがって、最初に CollectionsWithNoArgs ページが表示されたときに、コレクション値である arrayOfString プロパティ上のイテレータが実行されます。これは、そのベースである getArrayOfStrings() メソッドが呼び出されることになります。その後のページ・リフレッシュでは、イテレータは既に実行済であるため、デフォルトでは再実行されません。

イテレータの再実行を強制するためには、単にイテレータに対する executeQuery() メソッドをコールします。このサンプルのページ内に用意された Refresh Results リンクでは、データのリフレッシュを実施するために Refresh という名前のイベントを起動させます:

 
<a href="CollectionsWithNoArgs.do?event=Refresh">Refresh Results</a>

このイベント処理はプログラミングによって、または宣言的な設定によって構成できます。プログラムによる構成は次のように行います:

  1. カスタムの CollectionsWithNoArgsAction DataActionサブクラスを作成します。
    これは、ページフロー図内で、データページのアイコンを右クリックして、メニューから コードに移動 を選択します。

  2. DataActionのイベント処理メソッドとして onRefresh() メソッドを用意します。
  3. イベント処理コードとして、一行だけ記述します。このコードでは、名前でイテレータ・バインディングを探し出して、その 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 リンクをクリックするたびに現行時間が更新されることが確認できます。

String 引数のあるメソッドへのバインディング

ここまでに説明したものが、プロパティ・ベースのバインディングであるのに対して、同サンプル内の残りの3つのページフローはすべて、引数をもつカスタム・メソッドの戻り値へのバインドです。
最初の例では、以下のように構成されています。

基本ページフローの設定

ページフローの基本構成を設定するためには、ページフロー図で以下のコンポーネントを作成します:

  1. AcceptStringsParams.jsp ページ(Page)
  2. CallFindStrings データアクション(Data Action)
  3. StringResults データページ(Data Page)

ページフロー図上で、 CallFindStrings データアクションと StringResults データページとを Struts フォワードでつなげる 前に、データ・コントロール・パレットから findArrayOfYourStrings オペレーションを CallFindStrings アクション上にドロップします。これによって、暗黙的に CallFindStringsUIModel バインディング・コンテナが作成され、オペレーションに対する定義情報からメソッド名とパラメータ情報を読み取って、メソッドを起動するアクション・バインディングと(引数用の)カスタムのStruts アクション・プロパティの定義が記述されます (図21)。

注意:

JDeveloper 10g の設計時サポート機能によって暗黙的に バインディング・コンテナ が作成されると、その名前は、 struts-config.xml 内で modelReference プロパティとして記述されます。もし、アクション・マッピングで modelReferenceプロパティが設定されていない、または空の場合、ダイアグラム上では "警告" の意味を表すマークがアイコン上に表示され( )、まだ完全に構成されていないことを認識できるようになっています。まだバインディングが構成されていないDataAction(名前が YourDataAction だとします)に対して、何かオペレーションをドラッグ&ドロップすると、 <<YourDataAction>>UIModel という名前のバインディング・コンテナが構成されます。

バインディング・コンテナが構成されていないDataAction への自動設定が行われるもう一つのタイミングが、そのDataActionから別のアクションやページへ、フォワードによってつなげる場合です。この場合、フォワード元のDataAction にバインディング・コンテナが構成されていなければ、フォワード先のターゲットと同じバインディング・コンテナを使用するように設定されます。
カスタム・メソッドを起動できるようにDataActionを構成するためには、それが、固有のバインディング・コンテナで構成されている必要があります。そういった理由から、上記の説明では、オペレーションをドラッグ&ドロップするタイミングを、DataActionと別のアクションやページをつなげる前に実施しています。


CallFindStrings DataActionに対するバインディング情報
図 21: CallFindStrings DataActionに対するバインディング情報

StringResultsページでメソッド戻り値を表示するために、Strutsページフロー図から対象のアイコンをダブルクリックして、作成するJSPページ名を確認します。その後、データ・コントロール・パレットから、 findArrayOfYourStrings オペレーションの下の return ノードを選択し、"Dynamic Read-Only Table"として、ページの設計画面上にドラッグ&ドロップします。 このような手順の結果 StringResultsUIModel図 22)のようなバインディング定義が構成されます。

StringResults.jspに対するバインディング情報
図 22: StringResults.jspに対するバインディング情報

次に、 CallFindStrings データアクションと StringResults データページとをフォワードでつなげます。

AcceptStringsParams.jsp ページを開くと、シンプルに onetwo という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.jspCallFindStrings の間に点線が表示され、要素間の依存性を認識できるようになります。

メソッド・パラメータに対するEL式表現の設定

最後に、 findArrayOfYourStrings オペレーションに対して引き渡す2つのパラメータを宣言的に設定する方法について説明します。『Oracle ADFによるデータ・バインディング - データ・バインディングの仕組み』の「 DataAction 機能をフル活用した宣言的手法 」セクションでは、メソッド起動に関する宣言的定義情報は、Strutsアクション・マッピングのカスタム・プロパティとして記述されることを学びました。データ・コントロール・パレットからオペレーションをドラッグ&ドロップすると、それらの定義情報が自動的に構成されます。 struts-config.xmlに対する構成ペイン(図23)を見ると、 methodName, resultLocation, numParams, paramNames[0]および paramNames[1]といった追加のプロパティを確認できます。

CallFindStrings アクション・マッピングに対する構成の詳細
図 23: CallFindStrings アクション・マッピングに対する構成の詳細

paramNames[0]paramNames[1]に対して、渡すべきパラメータのEL表現で設定を行います。最初の引数として oneという名のリクエスト・パラメータ値を渡せるように、プロパティ・インスペクタから、 paramNames[0]に対する設定値を、EL表現 ${param.one} として設定します。同様に、 paramNames[1] の値を ${param.two} にして、 two という名前のパラメータ値が渡されるようにします。 

注意:

${param.paramname} という構文は、JSTL のELを用いて、 paramname というリクエスト・パラメータを参照する標準的な方法です。 


ViewController プロジェクトを実行して Results of Method Taking String Arguments リンクをクリックすると、ここまでの説明内容を実際に確認できます。 

アクション・バインディングが2度実行されない理由

注意深い読者は、 findArrayOfYourStrings アクション・バインディングが CallFindStrings データアクションと StringResults データページの両方のバインディング定義内にあることに気付いたかもしれません。どうして、"メソッドは2度実行" されないのでしょうか? ここでは、それについて説明します。

CallFindStringsUIModelfindArrayOfYourString アクション・バインディングを選択すると、プロパティ・インスペクタに 結果位置 というプロパティを確認できます。 結果位置 プロパティの値は、実行時にフォワード先アクションがメソッド結果を参照できるように、EL表現によって格納する場所を指し示します。 findArrayOfStrings アクション・バインディングの場合は、デフォルトで次のように設定されます:

 
MyServiceDataControl.methodResults.MyServiceDataControl_dataProvider_findArrayOfYourStrings_result

これによって、ADFバインディング層は、メソッド実行結果を、 MyServiceDataControl の methodResults HashMap オブジェクトに、 MyServiceDataControl_dataProvider_findArrayOfYourStrings_result というキー名で格納します。 StringResultsUIModelfindArrayOfYourString アクション・バインディング定義をプロパティ・インスペクタで見てみると、 結果位置 プロパティに同じ値が設定されていることがわかります。ADFバインディング層では、一つのリクエストの過程で、メソッドに対するアクション・バインディングが実行される場合に、一旦、その 結果位置 プロパティにあたるオブジェクトが null であるかどうかを評価し、null ではない場合(これはつまり、一連のページフロー内の事前に実施されたアクションで既にメソッドが実行され、結果が取得されていることを意味します)、それをメソッド結果として利用します。このようにして、ベースのサービス・メソッドを何度も実行してしまうコストを避けるよう工夫されています。

String および int 引数のあるメソッドへのバインディング

メソッド結果へのバインディングに関する基礎知識を理解したところで、2つ目の例を使って、コンセプトにより深く迫りましょう。このセクションで触れる例は、次のように構成されています:

サービス・メソッドは、4つの引数( String および int型)を受け取って、 MyTypeの配列を返します。 MyType は、シンプルなJavaBeanで、 nameage というプロパティを持ちます。さらに、 AcceptMyTypeArgs.jsp 、 CallFindMyType データアクション、 MyTypeResults データページを前の例と同様の手法で作成しています。

前の例と比べて、今回の例で興味深い2つのポイントがあります:

配列値パラメータのメソッド引数に対するEL表現の設定

AcceptMyTypeArgs.jsp を見ると、4つのテキスト入力フィールドがあるHTMLフォームで、そのうちの2つが" name" という名前で、もう2つが " age" という名前であることを確認できます( 図24)。一般に、HTMLフォーム内で繰り返しのフィールド名で送信を行うと、それは自動的に String[] 型の値として取得されます。したがって、このフォームでは、 nameage という名前の2つの配列値パラメータを取得します。EL表現内の paramValues キーワードは配列値HTTPパラメータに対しても利用可能です。

AcceptMyTypeArgs.jsp 内の、繰り返しのフォーム・パラメータ
図 24: AcceptMyTypeArgs.jsp 内の、繰り返しのフォーム・パラメータ

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]}

最初の nameage の値が 1つ目および2つ目のメソッド引数で、次の nameage が3つ目および4つ目のメソッド引数として渡されます。

MyType ビーンのコレクションに対する JSTL表現

データ・コントロール・パレットから findArrayOfMyType(String,int,String,int) オペレーションの下の return ノードを "Read-Only Table" として、 MyTypeResultsUIModel.jsp にドラッグ&ドロップすると、暗黙的に次のものが作成されます:

既に、 MyTypeResultsUIModelfindArrayOfMyType1 のようなレンジ・バインディングについては、本ドキュメント内の多くの例で見てきました。ここで唯一見ておきたい点は、 MyTypeResultsUIModel.jsp 内で、メソッド結果のコレクション値である MyType ビーンのプロパティ属性を参照するEL表現についてです。

以下の MyTypeResults.jsp 内のコードは、 <c:forEach> ループによって、 findArrayOfMyType1という名前のレンジ・バインディングの範囲内のデータに対して繰り返し処理している部分です。メソッドは MyType[]を返すため、結果の配列内の各データは、 MyType インスタンスの nameage のプロパティを持ちます。ループの各回で Row 変数を使って、現行の MyType インスタンスのビーン・プロパティにアクセスできます。

 
<!-- Snippet from MyTypeResults.jsp -->
<c:forEach var="Row" items="${bindings.findArrayOfMyType1.rangeSet}">
  <tr>
    <td><c:out value="${Row['age']}"/>&nbsp;</td>
    <td><c:out value="${Row['name']}"/>&nbsp;</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 プロパティの値を取得できます。

Beanデータ・コントロールの引数としての利用

最後の例では、2つの興味深いポイントがあります。

  1. 引数として、スカラー値ではなく構造型( MyType)を渡す
  2. MyType自体をデータ・コントロールにして、パラメータ・コレクションの過程でそれをデータ・バインディングに含める

この例は次のように構成されています:

MyTypeResults2.jsp ページは、前述の例の MyTypeResults.jsp とほぼ同じで、時間をかけて見るほどの違いはありません。ただし、このシナリオでは、パラメータ・ページである AcceptMyTypeArgs2 をデータページとして実装しています。このベースとなる AcceptMyTypeArgs2.jsp ページは、 AcceptMyTypeArgs.jsp と似ていますが、以下の点に違いがあります:

アプリケーション・ナビゲータ上で MyType.java に対して、右クリック・メニューから データ・コントロールの作成 を選択して MyTypeDataControlを作成します。これにより、 MyType ビーン・インスタンスへのバインディングが可能になります。 MyTypeDataControlnameage プロパティを "Input Field" として AcceptMyTypeArgs2.jsp ページにドラッグ&ドロップすると、 図25のようなUIモデルが構成されます。ここには、 MyType ビーンのシングルトン・インスタンスに対するイテレータ・バインディング MyTypeDataControl_rootIterator と、 name / age プロパティに対するコントロール値バインディングが含まれます。

AcceptMyTypeArgs2.jspに対するバインディング情報
図 25: AcceptMyTypeArgs2.jspに対するバインディング情報

AcceptMyTypeArgs2 データページ自身に「ポストバック」されると、ADFバインディング層では、(以前に学んだ BindingContainerActionForm を経由して)HTMLフォームから送信された対応する値を使って、 MyType ビーンの MyTypeDataControl のインスタンスの nameage のプロパティを更新します。 

(Submit) ボタンの名前は event_Callになっています。したがって、このボタンを押すと、"Call" イベントが発生します。 AcceptMyTypeArgs2 データページと CallFindMyType2 データアクションの間のフォワードの名前が " Call" であることから、処理されるイベントの名前と一致するため、自動的にフォワード処理されます。これはつまり、 (Submit) ボタンが押されると、モデルの値を更新したうえで、 CallFindMyType2 アクションによってメソッドが起動され、結果を表示する MyTypeResults2 データページへと転送されます。

図26CallFindMyType2 データアクションのUIモデルです。データ・コントロール・パレットからデータアクション上にオペレーションをドラッグ&ドロップして findArrayOfMyType アクション・バインディングを追加し、さらに、手動で(UIモデルタブ上の右クリック・メニューによって)MyTypeDataControl に対するルートのイテレータ・バインディングも追加しています。そのイテレータ・バインディングの名前は好きにつけられますが、これまでのほかの例と同じ命名パターンにならって、 MyTypeDataControl_rootIterにしています。

CallFindMyType2 DataActionに対するバインディング情報
図 26: CallFindMyType2 DataActionに対するバインディング情報

このイテレータ・バインディングの存在によって、 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 クラスの findArrayOfMyType の実際のコードを見て確認してください。

カスタム・メソッド起動のために独立したデータアクションを使用する理由

図19にあるページフロー図を見ると、メソッド結果へのバインディングを行った3つのページフローに共通な事実として、カスタム・メソッド起動のために独立したデータページを使用していることに気づきます。そのデータアクションは、その後、適切な "結果の" データページへ、メソッド結果の表示のために転送します。これら3つの例を見ると、次のような疑問をもつかもしれません:

"このようなデータアクションをはさまずに、結果を表示するデータページ上に、直接、起動するメソッドをドラッグ&ドロップすることはできないか? メソッド起動のために独立したデータアクションがなぜ必要なのか?"

これはするどい質問で、その答えの背景にある詳細な事実は知っておく価値が十分にあるものです。

DataAction ライフサイクルのメソッドへの深い考察

独立したデータアクションでカスタム・メソッドを実行することの必要性を理解するには、データアクションのライフサイクルの中で、メソッドがどういう 順番で起きているかを深く理解することが重要です。 表1 は、ライフサイクルを構成する主要なメソッドを浮き彫りにしたものです。 prepareModel() フェーズにおいて、バインディング・コンテナ内のすべての最上位レベルのイテレータ・バインディングが(必要に応じて)実行されるという事実を思い出してください。 invokeCustomMethod() フェーズでは、データアクションに関連付けられたカスタム・メソッドが実行されます。一般に、そのようなカスタム・メソッドは、データ・コントロール・パレットからデータアクションやデータページ上にドラッグ&ドロップした結果、関連付けられます。

表 1: DataAction ライフサイクルの主要メソッド一覧
手順 メソッド 説明
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() フェーズで、バインディング層によってサービス・メソッドが実行されますが、それは、コントローラ層によるメソッド引数の初期化処理の手順を通過する前です。このため、サービス・メソッドが引数なしで実行可能でない限り、必然的に IllegalArgumentExceptionNullPointerException のような例外が発生します。

注意:

ADF アクション・バインディングのパラメータ定義では、任意にEL表現によってパラメータのデフォルト値を指定することを 可能にしています。これは、UIモデルから、対象となるアクション・バインディングのパラメータ名を選択して、プロパティ・インスペクタから値を設定します。この際、表記は ${} で囲む必要はありません。ただし、この表現は、バインディング・コンテキスト内のオブジェクトの参照の場合のみに有効です。たとえば、SomePageUIModel という名前のバインディング・コンテナ内のDeptnoという属性バインディングの値を引き渡したい場合、" SomePageUIModel.Deptno"と記述できます。この、アクション・バインディングのパラメータとしての宣言的表現手法は、現在、HTTPリクエストのオブジェクト(HTTPパラメータなど)は参照できません。


対応策

この問題の解決のためには、メソッドに渡すべきパラメータの設定を行う前にメソッド・イテレータが実行されるのを避けるようにしなければなりません。カスタム・メソッドの起動を独立したデータアクションに分けて実行し、その後で、その結果に対するメソッド・イテレータを使用する "結果" のページへフォワードするようにすると、宣言的手法のまま対応できます。

この分割型手法では、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データアクションとの連動によってもたらされる機能についての技術詳細への理解も深まったことでしょう。