ADF ToyStore
- 4: サンプル・アプリケーションの分析 「ビュー層」 -

前に戻る | 目次に戻る | 次へ進む

ここでは、次の内容を中心としてサンプル・アプリケーションの詳細について説明します。

注意

ここでは、 「 サンプル・アプリケーションのインストールと設定」の項の指示に従っていること、 ADFToyStore.jwsのワークスペースがJDeveloper で開かれていること を前提にしています。


JSPページとJSTLを使用したビュー層の構築

図47で示すように、ADF ToyStoreサンプル・アプリケーションのビュー層の大部分は、JSPページを使用して実装されています。ただし、いくつかのページは、JSPの代替を利用できることを示す例として、XML/XSLTベースのアプローチを使用しています。 意図的に WEB-INFディレクトリのサブ・ディレクトリにページを配置したため、ユーザーはこのページを直接参照することはできません。 これは、ユーザーのすべてのリクエストが、必ずコントローラ層を経由するようにするための手法であり、ブックマークなどを使用して、コントローラ層を通過せずにJSPページに直行することは不可能にしています(J2EE仕様のルールにより、 WEB-INF以下のページは、コントローラ層から制御を転送することはできますが、直接参照することはできません)。 したがって、コントローラ層での完全な管理を保証するためには、これが安全でベスト・プラクティスなアプローチです。

ToyStoreViewプロジェクトにおける表示に関連するリソースとページ
図47: ToyStoreViewプロジェクトにおける表示に関連するリソースとページ

toystore.viewパッケージには、 ToyStoreResources.propertiesファイルと GlobalErrors.propertiesファイルが含まれています。これらのファイルには、ビュー層ページで使用される翻訳可能な文字列が、デフォルトの言語(英語)で保存されています。 名前に 「_it」および 「_de」が付いている、これらの2つのファイルの別のバージョンには、それぞれ同じ文字列のイタリア語とドイツ語の翻訳が含まれています。

DataBindings.cpxはADFのバインディング・コンテキスト・ファイルで、データ・コントロールに関する定義情報、およびバインディング・コンテナの名前が格納されています。 *UIModel.xmlファイルには、詳細なバインディング・コンテナの定義情報が含まれており、それぞれに含まれているバインディングについて詳しく記述しています。 JDeveloperのアプリケーション・ナビゲータで DataBindings.cpxをクリックし、構造ウィンドウで詳細を調べると、このサンプル・アプリケーションには、 ToyStoreServiceという名前のデータ・コントロールのみが定義されていることがわかります。

モデル・データへのアクセスと反復処理

JSPページの1つのビュー層、 yourcart.jspページを例に、どのように構築されているかを調べてみましょう。「StrutsとADFを使用したWebページ・リクエストのライフサイクル」の項で説明したように、ADFBindingFilterDataAction、および DataControlが連携して、アプリケーション・モジュールのインスタンスを取得し、それをコントローラ層とビュー層に対してデータ・コントロールとして利用可能にし、リクエストの最後に解放しています。 コントローラ層は、各リクエストの最初にDataAction が作成するDataActionContextオブジェクトを使用して、次の項目にアクセスできます。

一度データ・コントロールを取得すると、そのデータ・プロバイダにアクセスして、それをアプリケーション・モジュールのビジネス・サービス・インタフェースに型変換し、モデル・データ・マップ内のデータ転送オブジェクトのコレクションに直接、または間接的にアクセスすることができます。 また、バインディングを使用すれば、ビジネス・サービスを意識することなく処理することも可能です。コントローラ層とビュー層がMVCアーキテクチャとして機能すべき役割としては、コントローラ層がモデル・データを修正し、ビュー層は、表示のためにモデル・データを反復処理することだけであることを再認識しておきましょう。

JSTLおよびそのExpression Languageの概要

JSP Standard Tag Library(JSTL)には、事実上すべてのJSP Webページの開発者が必要な便利なタグのセットが用意されています。 JSPページは、JSTLタグを使用して、ページ、リクエスト、セッション、およびアプリケーション・スコープから属性を読込み/書込みし、それらの値に対しての反復処理を行い、必要に応じて、ページにそれらの値を出力たり、ページのレンダリングの条件付けとして利用することができます。 もちろん、これらのタスクは、JSTLを使わずにJSPページのコードだけでも実現できますが、JSTLを使用すると、ページの管理を複雑にするJSPスクリプトレットに頼ることなく、これらのタスクを実行できます。

ユーザーはJSTLタグを使用して、JSTLのExpression Language(EL)と呼ばれる簡単なドット表記法によって操作対象のオブジェクトとプロパティを特定できます。EL式は、 ${expression}のように ${}で囲まれたJSTLタグの属性値で表現されます。

たとえば、 bindingsという名前の属性値を参照するには、 ${bindings}という式を使用します。 返されたオブジェクトがプロパティを持つJavaBeanである場合、または名前付きのメンバーを持つ Mapである場合は、ドット表記法を使用して、このメンバーを参照できます。 たとえば、 bindingsというオブジェクトが、 ShoppingCartというメンバーを含んでいるマップの場合は、 ${bindings.ShoppingCart}の式を使用してこのメンバーを参照できます。

ADFバインディング・オブジェクトは、JavaBeanやJava Collections Frameworkを操作可能な任意のテクノロジーから簡単に使用することができます。JSTLタグ・ライブラリもそのようなテクノロジーの一つです。 それぞれのリクエストで、ADFフレームワークの DataActionは、現行のバインディング・コンテナを bindingsという属性として参照できるようにし、現行のバインディング・コンテキストを dataという属性として参照できるようにします。これらのすべての情報の参照のために、JSTLタグのEL式を使用できます。

JSTLタグを使用する際には、タグ名およびタグ属性の入力支援機能である通常の「コード・インサイト」だけでなく、 図48のように、EL式の記述支援のためのEL用コード・インサイト機能も提供されます。

EL式に対するコード・インサイト
図48: EL式に対するコード・インサイト

次の項では、ADF ToyStoreサンプル・アプリケーションのすべてのJSPページで、ADFバインディング層によって公開されるデータを表現するために、JSTLとEL式を使用していることについて確認します。


買い物かご機能の確認

最初に、買い物かごがどのように機能するかを説明します。 /yourcart DataPageは、ビュー層のUI表示のために /WEB-INF/jsp/yourcart.jspページを使用するもので、次のように構成されています。

    <action path="/yourcart"
            className="oracle.adf.controller.struts.actions.DataActionMapping"
            type="toystore.controller.strutsactions.YourCartAction"
            name="DataForm"
            parameter="/WEB-INF/jsp/yourcart.jsp"
            unknown="false">
      <set-property property="modelReference"
                    value="WEB_INF_jsp_yourcartUIModel"/>
      <forward name="reviewcheckout" path="/reviewcheckout.do"/>
    </action>

これは YourCartActionアクション・クラスにマップされており、適切なモデル・データを取得するために必要な手順を実行します。 modelReferenceプロパティの値から、 /yourcart DataPageのバインディング・コンテナ名は、 WEB_INF_jsp_yourcartUIModel(ADFの設計時支援ツールによって生成された名前)であることがわかります。 yourcart.jspページがアクティブな状態で、構造ウィンドウの 「UIモデル」タブをクリックすると、 図49のように、ページのバインディング・コンテナの内容が表示されます。 ここには、1つのイテレータ・バインディング (ShoppingCartIterator)と1つのレンジ・バインディング (ShoppingCart)があります。

yourcart.jspのバインディング・コンテナ
図49: yourcart.jspのバインディング・コンテナ

実行時には、DataForwardActionのデフォルト動作により、UI表示のために、付随しているJSPページに制御がフォワードされます。 この例では、yourcart.jspページへフォワードするように ShowProductDetailsアクションが構成されています。

ページでは、次の4つのタグ・ライブラリを宣言することから始まっています。

  1. Struts Beanタグ・ライブラリ - このタグには bean:という接頭辞が付きます。
  2. Struts HTMLタグ・ライブラリ - このタグには html:という接頭辞が付きます。
  3. JSTL Coreタグ・ライブラリ - このタグには c:という接頭辞が付きます。
  4. ADFタグ・ライブラリ - このタグには adf:という接頭辞が付きます。

Struts Beanタグ・ライブラリを使用すると、便利な <bean:message>タグを利用できます。これを使用すると、 details.titlecart.addItemimages.buttons.addtocartなどの文字列キーに基づいて、翻訳テキスト文字列をページに簡単に組み込むすることができます。

Struts HTMLタグ・ライブラリを使用すると、 <html:errors>タグを利用できます。これを使用すると、実行時の処理で発生したすべてのエラーを、標準の方法でページの最上部に簡単に表示できます。

JSTL Coreタグ・ライブラリを使用すると、データ・コレクションを反復処理し、属性値をページに組み込むことができます。このページで使用しているJSTLタグには、次のものがあります。

ADFタグ・ライブラリは、 <adf:render>タグを利用するために使用しています。このタグを利用すると、言語環境に応じた適切な書式マスクでフォーマットされた属性データを出力できます。ここで利用される書式マスクは、ビジネス・コンポーネントの定義情報の中で指定できます。

これらにより、ブラウザに表示される出力は、 図50のようになります。

「Your Cart」ページ
図50: 「Your Cart」ページ

買い物かごの中身は、一時的なビュー・オブジェクト行に保持されており、ユーザーが買い物かごに対して商品を追加および削除したときに、プログラムによって移入されることを思い出してください。 このため、前の項の /yourcartの例では、ページの表示処理にデータベース問合せが含まれていません。

バインド変数を使用して問合せの結果を表示するページ

次に、問合せされたデータベース情報を表す、 /showproductdetails DataPageなどのページについて説明します。 /showproductdetails DataPageは、ビュー層のUI表示のために showproductdetails.jspページを使用するもので、次のように構成されています。

<action path="/showproductdetails"
        className="oracle.adf.controller.struts.actions.DataActionMapping"
        type="toystore.controller.strutsactions.ShowProductDetailsAction"
        name="DataForm"
        parameter="/WEB-INF/jsp/showproductdetails.jsp"
        unknown="false">
  <set-property property="modelReference"
                value="WEB_INF_jsp_showproductdetailsUIModel"/>
  <forward name="addToCart" path="/yourcart.do"/>
</action>

これは ShowProductDetailsActionアクション・クラスにマップされており、適切なモデル・データを取得するために必要な手順を実行します。 このアクションには、 例9に示すようなコードが含まれています。

例9: ShowProductDetailsページに対してモデルを初期化するコード
/* From: toystore.controller.strutsactions.ShowProductDetailsAction */
  /**
   * Model initialization logic for this page.
   * 
   * When data action is not handling an event-postback, call
   * ToyStoreService method prepareToShowProductDetails() to set
   * required bind variables. 
   * 
   * @param ctx The DataAction context.
   */
  protected void initializeModelForPage(DataActionContext ctx) {
    String id = ctx.getHttpServletRequest().getParameter("id");
    getToyStoreService(ctx).prepareToShowProductDetails(id);
  }

DataActionの拡張クラスを用意して、このような initializeModelForPage()メソッドをライフサイクルに追加するよう、カスタマイズしました。 さらに、DataActionの prepareModel()ライフサイクル・フェーズで問合せが実行される前に 、ビュー・オブジェクトの問合せのバインド変数値をセットするように、アクションのメソッドをオーバーライドしました。 これは、追加のライフサイクル・メソッドによって処理が容易になることを示した代表的なユースケースです。

注意

prepareModel()フェーズの前にバインド変数を設定していない場合は、次のエラーが提示されます。

JBO-27122: SQL error during statement preparation. Statement: ...

これは、ベースとなる次のデータベース・エラー・メッセージによって発行されます。

ORA-01008: not all variables bound

ToyStoreDataForwardActionを見てみると、既存の prepareModel()メソッドをオーバーライドし、イベントを処理していない場合は、最初に initializeModelForPage()をコールするようデフォルト動作を補強することによって、この新しいライフサイクル・メソッドを導入していることがわかります。

/* From: toystore.fwk.controller.ToyStoreDataAction */
  /**
   * Overridden method.
   *
   * Add two new overrideable methods into the ADF DataAction lifecycle as
   * part of the prepareModel() processing to make handling the page
   * initialization use case easier.
   */
  protected void prepareModel(DataActionContext ctx) throws Exception {
    if (!handlingEvents(ctx)) {
      initializeModelForPage(ctx);
    }
    super.prepareModel(ctx);
    if (!handlingEvents(ctx)) {
      initializeBindingsForPage(ctx);
    }
  }

例9initializeModelForPage()メソッド本体では、DataActionContextを使用して HttpServletRequestから idパラメータを取得し、それを引数として ToyStoreServiceビジネス・サービス・インタフェースの prepareToShowProductDetails()メソッドに渡しています。 層に依存しない ToyStoreServiceインタフェースの背後にある ToyStoreServiceImpl実装クラスでは、 prepareToShowProductDetails()メソッドは次のように書かれています。

/* From: toystore.model.services.ToyStoreServiceImpl */
  public void prepareToShowProductDetails(String id) {
    getFindItems().setItemToFind(id);
    getFindItems().executeQuery();
  }

これは、検索対象となる商品IDの設定と、ビュー・オブジェクトの問合せの再実行の処理をカプセル化しています。 getFindItems()メソッドはビュー・オブジェクト・インスタンスを取得するメソッドで、ユーザーがアプリケーション・モジュールのデータ・モデルに対して FindItemsというビュー・オブジェクト・インスタンスを追加したときに、ADFの設計時支援ツールによって生成されたものです。
ビュー層は、適切なコントロール値バインディング・オブジェクトを使用することによって、ビュー・オブジェクトの問合せ結果を表すデータ転送オブジェクトにアクセスできます。 このようなコントロール値バインディングは、イテレータ・バインディングに関連付けられて、そのイテレータ・バインディングによって、モデル・データ・マップの FindItemsコレクションからデータが取得されます。

もう一度 例9を見てみると、 ToyStoreServiceDataForwardActionスーパークラスの getToyStoreService()ヘルパー・メソッドを使用していることがわかります。これは、 ToyStoreServiceというビジネス・サービスのカスタム・サービス・インタフェースを返します。

/* From toystore.controller.strutsactions.ToyStoreServiceDataForwardAction */
  protected ToyStoreService getToyStoreService(DataActionContext ctx) {
    return (ToyStoreService) getApplicationModule(DATACONTROLNAME, ctx);
  }

次にこのコードは、そのスーパークラス ToyStoreDataForwardActionから、 getApplicationModule()ヘルパー・メソッドをコールします。 次に示すように、このメソッドは名前によってデータ・コントロールを検索し、それがADFアプリケーション・モジュールに基づくデータ・コントロールの型である場合、 ApplicationModuleインタフェースとして機能するサービスのインスタンスを返します。

/* From toystore.fwk.controller.ToyStoreDataForwardAction */
  protected ApplicationModule getApplicationModule(String dataControlName,
    DataActionContext ctx) {
    DCDataControl dc = ctx.getBindingContext().findDataControl(dataControlName);
    if ((dc != null) && dc instanceof DCJboDataControl) {
      return (ApplicationModule) dc.getDataProvider();
    }
    return null;
  }

実行時には、 initializeModelForPage()メソッドが適切なバインド変数値を設定した後で、 DataForwardActionのデフォルト動作によって、UI表示のために、付随しているJSPページに制御がフォワードされます。この例では、 例10に示すような showproductdetails.jspページにフォワードするように ShowProductDetailsアクションが構成されています。 ここで使用されているタグ・ライブラリは、 yourcart.jspページで使用しているのと同じものです。 唯一、異なる点は、このページは FindItemsIteratorの1行につきデータを表示しているため、バインディング・コンテナの中にレンジ・バインディングは必要なく、 <c:forEach>ループが必要ないことです。 条件による表示ロジックも使用していないため、このページには、 <c:choose>または <c:if>タグもありません。

例10: showproductdetails.jspページ
<%@page import="org.apache.struts.action.ActionErrors" %>
<%@ taglib uri="http://xmlns.oracle.com/adf/ui/jsp/adftags" prefix="adf"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<html>
  <head>
    <title><bean:message key="details.title"/></title>
    <link href="css/ToyStore.css" rel="stylesheet">    
  </head>  
  <body bgcolor="white">
    <jsp:include page="header.jsp" flush="true"/>
    <jsp:include page="navbar.jsp" flush="true"/>
    <html:errors bundle="GlobalErrors"
                 property="<%= ActionErrors.GLOBAL_ERROR %>"/>
    <table bgcolor="white" width="100%">
      <tr>
        <td>
          <font size="5" color="#003399">
            <c:out value="${bindings.Name}"/>
          </font>
        </td>
        <td>
          <adf:render model="bindings.ListPrice"/>
        </td>
        <td>
          <c:out value="${bindings.InStock}"/>
        </td>
        <td>
          <a href="showproductdetails.do?event=addToCart&id=<c:out value="${bindings.ItemId}"/>"
             ><img src="<bean:message key='images.buttons.addtocart'/>" border="0"
                   alt="<bean:message key="cart.addItem"/>"></a>
        </td>
      </tr>
      <tr>
        <td class="wrap" colspan="4" valign="middle">
          <img align="left" border="0" src="images/<c:out value="${bindings.Picture}"/>">
          <c:out value="${bindings.Description}"/>
        </td>
      </tr>
    </table>     
  </body>
</html>

ブラウザに表示される出力は、 図51のようになります。

製品の詳細表示ページ
図51: 製品の詳細表示ページ

ADF ToyStoreサンプル・アプリケーションのすべてのJSPページは、この基本的なアプローチに従ってデータの反復処理やフォーマットをしています。

バインディングとJSTLを使用した再利用可能なページング・コントロールの作成

このサンプル・アプリケーションでは、次の3つのページを用意しており、それぞれ検索結果を表示することができます。

このようなページに対して 1-3 of 18 といった表示で現行のレコード位置を知らせたり、条件に応じて、 PreviousNext のリンクを表示するような、ページング・コントロールを作成し、かつ、再利用可能なものにしたいと考えていました。 これを作成するには、次の情報が必要です。

上記の3つのページには、それぞれのバインディング・コンテナ内にレンジ・バインディングが1つ含まれています。 レンジ・バインディングは、データ行を区切られた範囲ごとに表示し、一度に1ページ分(または1つの「範囲」分)データをナビゲートできるよう設計されているADFのバインディング・オブジェクトです。 ユーザーは、イテレータ・バインディングのレンジ・サイズを使って、一度に1ページに表示する行数を設定できます。 レンジ・サイズを -1の値に設定すると、データ・セット内のすべての行が一度に表示されます。

図52は、 /search DataPageのバインディング・コンテナ、およびその FindProductsレンジ・バインディングと、レンジ・バインディングが関連付けられているFindProductsIteratorイテレータ・バインディングを示しています。

Search ページのバインディング・コンテナ
図52: Search ページのバインディング・コンテナ

実行時には、レンジ・バインディングは、JSTLのEL式を介してアクセスできるプロパティを公開します。これらのプロパティによって、関連するイテレータとバインドされているデータ・コレクションの情報が取得できます。 これらのプロパティには、次のものがあります。

これらの3つの情報を使用して、必要なすべての情報を計算することができます。 例11は、 ToyStoreViewControllerプロジェクト内にある pagingControl.jspのコードを示しています。 このコードでは、次のJSTLタグを利用して、EL式によって、必要なバインディング・オブジェクトにアクセスしていることがわかります。

条件によって表示される「Previous」および 「Next」のリンクは、それぞれのURLで、 event=Previousおよび event=Nextパラメータを持つように生成されます。これによって、名前が PreviousおよびNextに一致するアクション・バインディングが、現行ページのバインディング・コンテナ内で宣言的に実行されます。

例11: 再利用可能なJSTLページング・コントロール
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<%--
 | Setup page variables uses for displaying the navigation
 | "control" at the top that looks like:
 | Previous N - M of Z Next
 +--%>
<c:set var="rangeBinding" value="${bindings[param.rangeBindingName]}"/>
<c:set var="totalRows" value="${rangeBinding.estimatedRowCount}"/>
<c:set var="firstRowShown" value="${rangeBinding.rangeStart + 1}"/>
<c:choose>
  <c:when test="${not empty param.extraParams}">
    <c:set var="queryStrBase" value='?${param.extraParams}&'/>
  </c:when>
  <c:otherwise>
    <c:set var="queryStrBase" value="?"/>
  </c:otherwise>
</c:choose>
<c:choose>
  <c:when test="${rangeBinding.rangeSize == -1}">
    <c:set var="rowsPerPage" value="${totalRows}"/>
  </c:when>
  <c:otherwise>
    <c:set var="rowsPerPage" value="${rangeBinding.rangeSize}"/>
  </c:otherwise>
</c:choose>
<c:choose>
  <c:when test="${firstRowShown + rowsPerPage - 1 > totalRows}">
    <c:set var="lastRowShown" value="${totalRows}"/>
  </c:when>
  <c:otherwise>
    <c:set var="lastRowShown" value="${firstRowShown + rowsPerPage - 1}"/>
  </c:otherwise>
</c:choose>
<c:if test="${totalRows > rangeBinding.rangeSize}">
  <c:choose>
    <c:when test="${firstRowShown > 1}">
      <a href="<c:out value="${param.targetPageName}${queryStrBase}"/>event=Previous"
        ><bean:message key="paging.previous"/></a> 
    </c:when>
    <c:otherwise>
      <font color="#e0e0e0"><bean:message key="paging.previous"/></font>
    </c:otherwise>
  </c:choose>
</c:if>
  <c:out value="${rangeBinding.rangeStart + 1}"/>-
  <c:out value="${lastRowShown}"/> <bean:message key="paging.of"/>
  <c:out value="${totalRows}"/>
<c:if test="${totalRows > rangeBinding.rangeSize}">
  <c:choose>
    <c:when test="${lastRowShown < totalRows}">
      <a href="<c:out value="${param.targetPageName}${queryStrBase}"/>event=Next"
       ><bean:message key="paging.next"/></a> 
    </c:when>
    <c:otherwise>
      <font color="#e0e0e0"><bean:message key="paging.next"/></font>
    </c:otherwise>
  </c:choose>
</c:if>

search.jspページのソース・コードを見てみると、 pagingControl.jspページを再利用していることがわかります。ここでは、 <jsp:include>を使用し、ネストされている <jsp:param>タグによって、ページで必要なパラメータの組を渡しています。 ここでは、 <c:choose>タグを使用して、 not emptyというELの演算子によって、製品データが見つかったかどうかをテストしています。 製品データが見つからなかった場合は、結果として空の表を示すのではなく、「No matching products」というメッセージを出力します。

    <c:choose>
      <c:when test="${not empty bindings.FindProducts.rangeSet}">
        <jsp:include page="pagingControl.jsp">
          <jsp:param name="rangeBindingName" value="FindProducts"/>
          <jsp:param name="targetPageName" value="search.do"/>
        </jsp:include>
        <table border="0" bgcolor="#003399">
          <!-- etc. -->
        </table>
      </c:when>
      <c:otherwise>
        <br><br><bean:message key="search.nomatchingproducts"/>
      </c:otherwise>
    </c:choose>

pagingControl.jsp では、最初に、レンジ・バインディング・オブジェクトを検索するために必要な "名前" の情報を得るために、次の構文を使用して、渡される引数から rangeBindingNameパラメータの値を取得します。

<c:set var="rangeBinding" value="${bindings[param.rangeBindingName]}"/>

pagingControl.jspでは、EL式を使用してレンジ・バインディングのプロパティを参照し、ページ・コントロールの表示をレンダリングする作業を実行します。 この式では、 ${bindings.SomeRangeBinding}のようなELのドット表記法を使用せずに、角括弧を使用した配列インデックス・スタイルの表記法を使用していることに注意してください。 これによって、(この場合は bindingsオブジェクトに対して)アクセスしようとしているメンバー名を、そのまま名前で提供するのではなく、式値として提供することができます。

ユーザーは、「1ページに表示する行数をどこで設定するか」という疑問を持つでしょう。 これは重要なポイントです。 図52に示されている「UIモデル」タブで FindProductsIteratorをクリックし、プロパティ・インスペクタを見てみると、 レンジ・サイズ プロパティの値が 3に設定されていることがわかります。 これは、実行時にこのイテレータが、一度に3行までの結果をページに提示することを意味しています。 一度に5つの結果を表示するよう変更するには、コードを変更するのではなく、このイテレータ・プロパティの値を 5に修正するだけで済みます。 ユーザーが、実行時に1ページに表示する行数を変更できるようにする機能を提供したい場合は、イテレータ・バインディングに対して setRangeSize()をコールすることができます。

通常のフォーム・レイアウト・アプローチを使用したデータ入力フォーム

/editaccount DataPageは、ユーザー・プロファイル情報を編集するためのデータ入力フォームを表示するページを表しています。 これは、使用する各コントロールをHTMLフォーム内に配置するという、通常のJSPページ・レイアウトのアプローチを使用しています。

モデル層データの設定

EditAccountActionは、これまでに説明したアクションと同様に、initializeModelForPage() メソッド内でモデル層を設定します。 ここで、対象のユーザー名を引数として ToyStoreServiceインタフェースのカスタム・サービス・メソッド prepareToEditAccountInfoFor()をコールします。

ToyStoreServiceImplクラスにおけるこのメソッドの実装は、 例12のようになります。 ここでは、次の3つの基本的な手順を実行します。

  1. 渡された対象ユーザーの名前に基づいて、 oracle.jbo.Keyオブジェクトを作成します。
  2. このキーを、ビュー・オブジェクトの findByKey()メソッドに渡して、 Accountsビュー・オブジェクトの既存の行を参照します。
  3. ビュー・オブジェクト内で、この行を現在行として設定します。
例12: ToyStoreServiceメソッドによるアカウント情報の編集準備
/* From: toystore.model.services.ToyStoreServiceImpl */
  public boolean prepareToEditAccountInfoFor(String username) {
    Key k = new Key(new Object[] { username });
    ViewObject vo = getAccounts();
    /*
     * We don't want the view object to execute any other query
     * than the one row we will be finding by key, so we mark
     * it's max fetch size to zero.
     */
    vo.setMaxFetchSize(0);
    Row[] r = vo.findByKey(k, 1);
    if (r.length < 1) {
      return false;
    }
    Row rowFound = r[0];
    /*
     * Set the row we found as the current row in the VO
     */
    vo.setCurrentRow(rowFound);
    return true;
  }

HTMLフォームのレイアウト

editexistingaccount.jspという名前の、対応するJSPページでは、Struts HTMLタグ・ライブラリから <html:form>タグを使用し、次のようにアクションの宛て先を自分自身のDataPageへ戻る(バック)ように送信(ポスト)することによって、「ポストバック・パターン」を実装します。

<html:form action="/editaccount.do" method="post">

実行時に、Strutsの <html:form>タグは、action属性値である /editaccount.do を参照して、アクション・マッピング情報から、このフォームをレンダリングするために DataFormというFormBeanが使用されていることを判断します。 DataFormフォームBeanは struts-config.xmlで定義されており、ADFの BindingContainerActionFormを使用しています。

ユーザー・アカウント情報の1行のみについて、データ入力フォームをレンダリングしているため、このページではJSTL <c:forEach>を使用する必要はなく、バインディング・コンテナにもレンジ・バインディングは必要ありません。 通常のHTMLテーブル・タグを使用して、単純にそれぞれのフィールドをフォームにフォーマットし、ラベルと入力コントロールを一列に並べています。 このページは、いくつかの有用なテクニックの使用例でもあるため、次にそれぞれの重要なポイントを中心にして説明します。

データページのバインディング・コンテナ

図53は、 EditAcountページのバインディング・コンテナを示しています。 ここには、Country除けば、 その他のAccountsの属性に対して、基本的な属性バインディングが用意されています。 Country に対して用意されているものはリスト・バインディングです(そのアイコンはポップリストを表しています)。また、ここには、2つのイテレータ・バインディングがあります。1つは、編集しているメインの Accounts情報の AccountsIteratorで、もう1つは、ユーザーが Country属性に対して選択できる有効な国の名前を示すポップリストを提示する CountryListIteratorです。

EditAccount ページのバインディング・コンテナ
図53: EditAccount ページのバインディング・コンテナ

ToyStoreServiceデータ・コントロールの組込みのCommit操作にバインドされている、 saveというアクション・バインディングもあります。

フォームでの読取り専用のデータの表示

例13は、 Usernameプロパティに対するラベルとデータを含むHTMLテーブルの行を出力する、ページのタグを表しています。 このアプリケーションのユーザー名は一度作成すると更新できない仕様にしていますので、データに対して入力フィールドを表示する必要はありません。 <c:out>タグを使用すると、対応するバインディング・オブジェクトを使用して、表示用のフィールド値を簡単に出力することができます。 <bean:message>タグは、 ToyStoreResources.propertiesプロパティ・ファイルから翻訳可能な文字列を出力して、ツールチップとユーザー名のラベルを設定しています。

例13: c:outを使用した読取り専用データの表示
<%-- Username field --%>
<tr>
  <th align="right" title="<bean:message key="account.username.tooltip"/>">
    <bean:message key="account.username.label"/>
  </th>
  <td title="<bean:message key="account.username.tooltip"/>">
    <c:out value="${bindings.Username}"/>
  </td>
</tr>

フォームでの入力フィールドの作成

データを入力または編集する必要がある場合は、StrutsのHTMLライブラリの他のタグを使用して、データバインド・コントロールを表現できます。 例14は、 <html:password>タグを使用して Passwordプロパティを表示していることを示しています。

<html:password property="Password" size="25" maxlength="30"/>

ADFの BindingContainerActionForm は、Struts(およびここでは特に、StrutsのHTMLタグ・ライブラリのタグ)に対して提示される DynaActionForm Beanで、対象のバインディング・コンテナのバインディングと関連付けられ、同じ名前のプロパティを持っていることを思い出してください。このため、 <html:password>タグが、このフォームBean経由で Passwordプロパティの値を取得(get)および設定(set)すると、ADFは、内部でこのフォームBeanのプロパティと対象のバインディング・オブジェクトの値を調整します。

例14: StrutsのHTMLタグを使用してデータバインド・フォーム・コントロールを作成する
<%-- Password field --%>
<tr>
  <th align="right" title="<bean:message key="account.password.tooltip"/>">
    <bean:message key="dataentryform.mandatory"/>
    <bean:message key="account.password.label"/>
  </th>
  <td title="<bean:message key="account.password.tooltip"/>">
    <html:password property="Password" size="25" maxlength="30"/> 
  </td>
  <td><html:errors property="Password"/></td>
</tr>

この例では、StrutsのHTMLタグ <html:errors>を使用して、 Password属性に固有の検証エラーを表示していることも表しています。 もちろん、フォームが最初に出力されるときは検証エラーがないため、この表のセルは空になります。 ただし、ユーザーがフォームを送信し、モデル層で検証エラーがスローされた場合は、このページがもう一度出力されるときに、 Passwordに関連するエラーが、画面上の Passwordフィールドの隣に表示されます。 また、このフィールドは必須であることがわかっているため、 <bean:message>タグを使用して、キー dataentryform.mandatoryに対応する文字列によって、このフィールドが入力必須であることをユーザーに示す視覚的なマーカーとして表示しています。 デフォルトでは、アスタリスクが出力されます。

ELを使用したラベル、ツールチップなどの情報の利用

ADF Business Componentsのエンティティ・オブジェクトおよびビュー・オブジェクトのコンポーネントには、ロケールによって異なるラベル、ツールチップ、書式マスクなどのコントロールに対する追加情報を開発者が定義するための多数の組込み機能が用意されています。 ADFのバインディング層には、ビュー層ページから簡単にアクセスできるよう、バインディング・オブジェクト上でこの情報を公開しています。 例15<c:out>タグは、ビジネス・オブジェクト属性またはビュー・オブジェクト属性に関連付けられているツールチップおよびラベルの情報に対するEL式を表しています。 あるエンティティ・オブジェクトの、たとえば Firstnameという名前の属性に対して、ツールチップが定義されている場合、 Firstname が含まれているすべてのビュー・オブジェクトでもこのツールチップ定義が継承されます。 もちろん、必要に応じて、個々のビュー・オブジェクトで、これらのコントロール・ヒントをオーバーライドできます。

例15: ADFバインディングのコントロール・ヒント情報へのアクセス
<%-- Firstname field --%>
<tr>
  <th align="right" title="<c:out value='${bindings.Firstname.tooltip}'/>">
    <c:if test="${bindings.Firstname.mandatory}">
      <bean:message key="dataentryform.mandatory"/>
    </c:if>
    <c:out value="${bindings.Firstname.label}"/>
  </th>
  <td>
    <html:text property="Firstname" size="30" maxlength="35"/>
  </td>
  <td><html:errors property="Firstname"/></td>
</tr>

各バインディング・オブジェクトは、バインド対象のオブジェクトに関する実行時の定義情報を公開します。ユーザーは、バインドされているオブジェクトに対してEL式を使用して実行時にアクセスできます。 たとえば、属性にバインディングされているコントロール値は、ベースとなっているモデル層の属性の情報を公開します。 例15では、このメタデータを使用して、 Firstnameなどの属性が必須かどうかを実行時に検出しています。 JSTLタグの <c:if>と組み合せると、この情報を使用して、必要なフィールドに、条件に応じて必須のマーカーを出力できます。

<c:if test="${bindings.Firstname.mandatory}">
  <bean:message key="dataentryform.mandatory"/>
</c:if>

一つ覚えておくと便利なTipsとして、バインディング・オブジェクトで使用できるプロパティ情報の取得方法を紹介します。構造ウィンドウの「UIモデル」タブで対象のバインディングをクリックして、 F1キーを押します。 このようにすると、対象のバインディング・オブジェクトについてのオンライン・ヘルプのトピックが表示され、参照することができます。

データバインドされたポップリスト・コントロールの配置

最後に、データバインドされたフォーム・コントロールの例として、ユーザーが居住している国を表示するポップリストについて説明します。 図54に示すように、このコントロールには 2つの側面があります。

  1. ベースとなる Countryバインディングの値。リストで選択した内容が反映されます。
  2. 使用できるすべての国名のリスト。このリストから選択します。
ポップリストには、現行の値と有効値のリストの両方がある
図54: ポップリストには、現行の値と有効値のリストの両方がある

ADFには、このように、データ・バインディングの要件に対して複数の局面を持つコントロールを処理するための、より高度なバインディング・オブジェクトが提供されています。 ADFリスト・バインディングは、ポップリスト・タイプのコントロール要件を満たすべく、現行のバインディング属性値と、ユーザーに提示するための有効な選択リストの両方を管理できます。また、階層的なデータに対しては、場合によってはさらに便利に使用できるツリー・バインディング・オブジェクトを提供しています。

editexistingaccount.jspがアクティブなときに、構造ウィンドウの 「UIモデル」タブをクリックすると、 図53のようなバインディング情報が表示されます。 CountryListIteratorを選択してプロパティ・インスペクタをみると、このレンジ・サイズが -1になっていることがわかります。 この値は、部分的な行だけを表示するのではなく、国のリストにすべての行を表示することを意味します。

注意

イテレータ・バインディングのレンジ・サイズは、デフォルトで 10になります。 リスト・バインディングの選択リストを使用するイテレータでは、ここで行ったように、通常レンジ・サイズを -1に設定します。


Countryバインディングをクリックし、マウスを右クリックしてメニューで 「編集...」を選択すると、 図55のような リスト・バインド・エディタ が表示されます。 ここでは、 Countryポップリストをサポートするために必要な、次の定義情報が表示されます。

LOVの表示属性」タブをクリックすると、 CountryListIteratorDescription属性が、ユーザーに表示される値としてリストに示されることがわかります。

リスト・バインド・エディタにおけるCountryリスト・バインディング
図55: リスト・バインド・エディタにおけるCountryリスト・バインディング

例16は、 <html:select>および <html:optionsCollection>を使用して、 Countryリスト・バインディングでページにポップリストを表示する方法を表しています。 <html:select>は、フォームBeanの Countryプロパティにバインドされており、リスト・バインディングに関連付けられています。 <html:optionsCollection>は、同じ Countryバインディングの、ネストされたリスト値である displayDataからデータを取得します。 この表示用のデータ・コレクション内の各Beanは promptおよび indexプロパティを持っており、リストの各選択項目の labelおよび valueとして使用するように設定されています。

注意

ネットワーク最適化のために、ADFのバインディング層では、入出力されるリストのインデックスが、ゼロで始まる番号であることを想定しています。 ADFのリスト・バインディングは、バインディング値の読込みおよび書込みのときに、ベースとなる Countryの値 (ITなど)と値リストのインデックス値(86など)を変換します。


例16: html:selectを使用してデータバインドされたポップリストを出力する
<%-- Country field --%>
<tr>
  <th align="right" title="<c:out value='${bindings.Country.tooltip}'/>">
    <c:if test="${bindings.Country.mandatory}">
      <bean:message key="dataentryform.mandatory"/>
    </c:if>
    <c:out value="${bindings.Country.label}"/>
  </th>
  <td>
    <html:select  property="Country" >
      <html:optionsCollection label="prompt"
                              value="index"
                              property="Country.displayData" />
    </html:select>
  </td>
  <td><html:errors property="Country"/></td>
</tr>

メタデータを使用した、より汎用的な手法によるデータ入力フォームの出力

ToyStoreサンプル・アプリケーションには、前に説明した「通常の」テクニックとは異なる、より汎用的なメタデータ駆動の手法でデータ入力フォームを出力するページも含まれています。 これらの2つの手法によるフォームは、両方とも、 Accountsデータに対する同じコントロール・セットを出力するため、2つのアプローチを比較して、自分のアプリケーションに適した方を簡単に選択できます。

「Register New User」ページ

例17は、 (/register DataPageで使用される) registernewuser.jspページを表しています。このページは、データ入力フォームを出力して、最初のユーザー登録ができるようにします。 このページのブラウザで生成される結果は、前述の editexistingaccount.jspページのものとほとんど同じですが、この例からわかるように、フォーム全体は、1つの <jsp:include page="formControl.jsp">タグによって出力されます。 このタグは、再利用可能コンポーネントのように formControl.jspページのコンテンツをページに組み込みます。 ネストされている <jsp:param>タグは、再利用可能なコンポーネント・ページに次の3つのパラメータを渡します。

  1. dataPage - 現行のデータページの名前
  2. saveButtonLabelKey - 「(Save)」ボタンの表示ラベルのためのメッセージ・バンドル・キー
  3. saveButtonEvent - 「(Save)」ボタンに関連付けるイベントの名前
例17: 「Register New User」ページ
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
  <head>
    <title><bean:message key="registernewuser.title"/></title>
    <link href="css/ToyStore.css" rel="stylesheet">      
  </head>
  <body bgcolor="white">
    <jsp:include page="header.jsp" flush="true"/>
    <jsp:include page="navbar.jsp" flush="true"/>  
    <h2><bean:message key="registernewuser.header"/></h2>
    <jsp:include page="formControl.jsp">
      <jsp:param name="dataPage" value="register"/>
      <jsp:param name="saveButtonLabelKey" value="dataentryform.register"/>
      <jsp:param name="saveButtonEvent" value="save"/>
    </jsp:include>
  </body>
</html>

したがって、行われる実際の処理は、 formControl.jspコンポーネント・ページの中にあります。 このページは、現行のバインディング・コンテナのコントロール値バインディングそれぞれに対応するコントロールを持つ、データ入力フォームを作成します。

汎用的なフォーム・コントロール・ページ

このページでは、最初に、 <c:choose><c:if>、および <c:set>タグを使用して、渡されたパラメータの値に応じて、eventNamebuttonLabel、および buttonLabelKeyというページのローカル変数の値を設定するコードで始まっています。 これらのページの変数は、後に、生成されたフォームの下部の 「(Save)」ボタンを構成する際に使用します。

<c:choose>
  <c:when test="${not empty param.saveButtonEvent}">
    <c:set var="eventName" value="${param.saveButtonEvent}"/>
  </c:when>
  <c:otherwise>
    <c:set var="eventName" value="Commit"/>
  </c:otherwise>
</c:choose>
<c:if test="${not empty param.saveButtonLabel}">
  <c:set var="buttonLabel" value="${param.saveButtonLabel}"/>
</c:if>    
<c:if test="${not empty param.saveButtonLabelKey}">
  <c:set var="buttonLabelKey" value="${param.saveButtonLabelKey}"/>
</c:if> 

formControl.jspページは、次に入力フォームの「グローバル・エラー」セクション部分として <html:errors>タグを使用します。ここでは、属性固有ではない全般的なエラーが表示されます。

<center>
  <table border="0">
    <tr>
      <td><html:errors bundle="GlobalErrors"
                       property="<%= ActionErrors.GLOBAL_ERROR %>"/></td>
    </tr>
  </table>
</center>

次にこのフォームは、 <html:form>の開始タグの一部として、 jsp:includeによって渡される dataPageパラメータの値を使用します。 <html:form>タグのアクション属性の中ではEL式を直接使用できないため、 <c:set>を使用して、EL式の値を使用してnameというページ・ローカル変数を設定し、次にJSPスクリプトレットを使用して、このname変数の値を action属性へ渡していることがわかります。

<c:set var="name" value="/${param.dataPage}.do"/>
<html:form action='<%= pageContext.getAttribute("name")%>'>
  <!-- etc. -->
</html:form>

このフォームには、非表示フィールドが含まれています。ADFのコントローラ層はこのフィールドを使用して、ユーザーが連続して同じフォームを何度も送信しようとしたかどうかを判断します。

<input type="hidden" name="<c:out value='${bindings.statetokenid}'/>"
       value="<c:out value='${bindings.statetoken}'/>"/>

コントロール値バインディングの反復処理によるフォームの作成

次に、バインディング・コンテナ内の各コントロール値バインディングに対応するコントロールを作成するループを開始します。 <table>タグの中には、次の <c:forEach>の反復処理があります。

<c:forEach var="curBinding" items="${bindings.ctrlBindingList}">
  <% JUControlBinding cb =
       (JUControlBinding)pageContext.getAttribute("curBinding");
     if (cb instanceof JUCtrlValueBinding &&
       !(cb instanceof JUCtrlRangeBinding) &&
       !(cb instanceof JUCtrlHierNodeBinding)) { %>
   <!-- Build control for current control value binding in here -->
 <% } %>
</c:forEach>

<c:forEach>のループは、バインディング・コンテナ内のコントロール値バインディングのリストを反復処理します。 このリストには、アクション ・バインディングが含まれている可能性があるため、入力コントロールを出力する場合はこれらのバインディングをスキップする必要があります。 ここでは、簡潔にするために、レンジ・バインディングとツリー・バインディングもスキップしています。 EL式の言語には、組込みの instanceofオペレータがないため、JSPスクリプトレットを使用して、通常のJava言語の if文を使用し、 instanceofによるチェックを実行しています。

注意

同じ手法によって、バインディング・コンテナで検出されるすべてのアクション・バインディングに対して汎用的にボタン・セットを出力することも可能です(アクション・バインディングは oracle.jbo.uicli.bindingパッケージの JUCtrlActionBinding型になります)。しかし、この例では、簡単のために、フォームには 「(Save)」ボタンのみを出力するようにしています。


<c:forEach>タグに var="curBinding"属性を指定したため、フォームの入力コントロールの汎用的な生成処理の一部として、ループ内でこの curBindingループ変数を参照することで、現在のコントロール値バインディングにアクセスすることができます。EL式の中で、 tooltipmandatorylabelなどのバインディング・プロパティを使用して、現在のコントロール・バインディングから情報を取得している様子を確認してください。

<c:forEach var="curBinding" items="${bindings.ctrlBindingList}">
  <% JUControlBinding cb =
       (JUControlBinding)pageContext.getAttribute("curBinding");
     if (cb instanceof JUCtrlValueBinding &&
       !(cb instanceof JUCtrlRangeBinding) &&
       !(cb instanceof JUCtrlHierNodeBinding)) { %>
   <tr>
     <th align="right" title="<c:out value='${curBinding.tooltip}'/>">
       <c:if test="${curBinding.mandatory}">*&nbsp;</c:if>
       <c:out value="${curBinding.label}"/>
     </th>
     <td>
        <c:set var="name" value="bindings.${curBinding.name}"/>
        <adf:inputrender model='<%= pageContext.getAttribute("name")%>'/>
     </td>
     <c:set var="name" value="${curBinding.name}"/>
     <td>
       <html:errors property='<%= pageContext.getAttribute("name") %>'/>
     </td>
   </tr>
 <% } %>
</c:forEach>

HTMLフォーム・コントロールの実際の出力のために、 <adf:inputrender>タグを使用しています。このタグは、現在のバインディングの属性値よって適切なタグを出力します(後で説明しますが、追加の属性メタデータを使用することで、 <adf:inputrender>タグによる出力をカスタマイズすることができます)。 ここでも <c:set>によるテクニックを使用して、文字列 「 bindings.」と現在のバインディングの名前を連結して、 nameというローカル・ページ変数に設定しています。これは、 <adf:inputrender>タグの model属性の値として参照されています。

最後に、 <html:errors>タグを使用して、発生する可能性のある属性レベルの検証エラーを、該当するコントロールの隣に示しています。 ここでも <c:set>のテクニックを使用して、 <html:errors>タグの property属性の値を現在のバインディングの名前に設定しています。

また、 <c:choose>を使用して、適切にラベルが付加された 「(Save)」ボタンをフォームの下部に配置しています。 ユーザーがボタン・ラベル、またはボタン・ラベル・キーを指定したかどうかによって、指定されたラベル文字列を使用するか、または bean:messageタグを使用してラベル・キーを参照するかを決定しています。 ページの上部に設定しているページのローカル変数 eventNameを使用して、ボタンに適切な名前を適用し、これによって、ユーザーがボタンをクリックしたときにイベントを生成しています。

<c:choose>
  <c:when test="${not empty buttonLabel}">
    <input name="event_<c:out value="${eventName}"/>" type="submit"
            value='<c:out value="${buttonLabel}"/>'/>
  </c:when>
  <c:when test="${not empty buttonLabelKey}">
    <input name="event_<c:out value="${eventName}"/>" type="submit"
            value='<bean:message name="buttonLabelKey"/>'>
  </c:when>
  <c:otherwise>
     <input name="event_<c:out value="${eventName}"/>" type="submit"
            value='Submit'/>
  </c:otherwise>                      
</c:choose>

メタデータを使用したカスタムの編集フィールド・レンダラの設定

ADF Business Components は、カスタム・プロパティの設定を機能があります。カスタム・プロパティは実行時に読み込むことが可能で、これを使用してメタデータ駆動の動作を実現することができます。 ADFのエンティティ・オブジェクトとビュー・オブジェクトでは、個々の属性レベルでもカスタム・プロパティを設定できます。

属性のプロパティを編集するには、それぞれのオブジェクト・エディタで「属性」を展開し、プロパティを編集したい属性の名前をクリックします。 次に、右側のエディタ・パネルで 「属性プロパティ」タブをクリックします。 図56は、 Accountsビュー・オブジェクトの Country属性のカスタム属性プロパティを編集する際に、どのようになるかを示しています。

<adf:inputrender> タグの実装は、属性に対してカスタム・レンダラが指定されているかどうかの判断として、EditRendererという属性プロパティの値を確認します。 カスタム・レンダラを指定していない場合は、デフォルトのルールにより、適切なコントロールが選択されます。

Accountsビュー・オブジェクトのCountry属性に対するカスタム属性プロパティ
図56: Accountsビュー・オブジェクトのCountry属性に対するカスタム属性プロパティ

この例では、ADFのリスト・バインディングである Country に対して、カスタマイズされたポップリスト・レンダラを実装するクラス名 toystore.fwk.view.ListBindingPoplistRendererを指定しました。 このカスタム・フィールド・レンダラはデフォルトの oracle.jdeveloper.html.StaticPickListレンダラを拡張したもので、リスト・バインディング・オブジェクトから取得できる情報に基づいて、いくつかの追加プロパティを移入します。 例18に、カスタム・レンダラのソース・コード(サンプル・アプリケーションの FwkExtensionsプロジェクトから取得できます)を示します。 このコードでは、データソースから JUControlBindingオブジェクトにアクセスし、これが JUCtrlListBindingであることをチェックした後で、リスト・バインディングで getDisplayData()をコールしてリスト表示データにアクセスできることがわかります。 ラベルおよび値の String[]変数にデータを移入するために、表示データのコレクションを反復し、コレクションの各Beanから、 prompt属性をラベルの配列に追加します。 ADFのバインディング層は、ページから戻ってくる値が数値の行番号であることを想定しているため、反復処理内で、ループ変数 zを文字列に変換し、値の配列にデータを移入しています。
このカスタム・レンダラによる最終的な利点は、汎用的な formControl.jspコンポーネント・ページによって、現在のバインディング・コンテナのバインディングに対してHTMLフォームを出力するときに表面化します。このカスタム・レンダラによって、 Countryバインディングは、モデル・データ・マップの CountryListという名前の(値オブジェクトによる)表示データのコレクションからデータを移入して構成される、データバインドされたポップリストとして出力されることになります。

例18: adf:inputrendererタグで使用されるカスタム・フィールド・レンダラ
package toystore.fwk.view;
import java.util.List;
import java.util.Map;
import oracle.jbo.Row;
import oracle.jbo.html.BindingContainerDataSource;
import oracle.jbo.uicli.binding.JUControlBinding;
import oracle.jbo.uicli.binding.JUCtrlListBinding;
import oracle.jdeveloper.html.StaticPickList;

public class ListBindingPoplistRenderer extends StaticPickList {
  /**
   * Overrides renderToString() in StaticPickList
   */
  public String renderToString(Row row) {
    BindingContainerDataSource ds = (BindingContainerDataSource)getDatasource();
    JUControlBinding b = ds.getControlBinding();
    String[] labels = null;
    String[] values = null;
    if (b instanceof JUCtrlListBinding) {
      JUCtrlListBinding listBinding = (JUCtrlListBinding)b;
      List valueList = listBinding.getDisplayData();
      int size = valueList.size();
      values = new String[size];
      labels = new String[size];
      for (int z = 0; z < size; z++) {
        labels[z] = (String)((Map)valueList.get(z)).get("prompt");
        values[z] = Integer.toString(z);
      }
      setValue(Integer.toString(listBinding.getSelectedIndex()));
    }
    setDataSource(labels,values);
    return super.renderToString(row);
  }  
}

アプリケーションで、このような汎用的なデータ・フォームのレンダリング・テクニックを採用すると、アプリケーション内のすべてのデータ入力フォームで外観を統一し、同じように動作することを簡単に保証できます。これは、すべての入力フォームが汎用的な同じコードによって出力されるためです。

多言語アプリケーションを作成するためのStrutsとADFの機能

JSPページのいくつかの例で、翻訳可能な文字列の使用や、翻訳可能な属性ラベル、ツールチップ、書式マスクへの参照について見てきました。 この項では、StrutsおよびADFのフレームワークが提供する機能による、多言語対応のユーザー・インタフェースをサポートするアプリケーションの作成について簡単に説明します。

Strutsのメッセージ・リソース・ファイルのサポート

Strutsには、標準のJava *.propertiesファイルに格納されている、翻訳済のメッセージを使用するための基本的な機能があります。 デフォルトのメッセージ・リソースに加えて、ユーザー自身でセカンダリ・メッセージ・リソースを定義することもできます。 ADF ToyStoreサンプル・アプリケーションでは、次のメッセージ・ファイルを使用しています。

例19に示すように、 struts-config.xmlでメッセージ・リソース・ファイルの名前をStrutsに認識させます。 key属性を持たない <message-resources>要素は、デフォルトのメッセージ・リソースの場所を定義しています。 セカンダリ・メッセージ・リソースは、 key属性の値を指定することによって、そのキーを表しています。

例19: Strutsのデフォルトおよびセカンダリのメッセージ・リソース・ファイルを構成する
<struts-config>
    :
  <!--
   | This entry tells Struts where to find the default application
   | resources properties file
   +-->
  <message-resources parameter="toystore.view.ToyStoreResources"/>
  <!--
   | Resource used to render globals errors with <html:errors>
   +-->
  <message-resources key="GlobalErrors" parameter="toystore.view.GlobalErrors"/>
</struts-config>

メッセージは、文字列キーとテキスト・メッセージがペアになったプロパティとして格納されています。 たとえば、 ToyStoreResources.propertiesに次の2行があります。

  :
cart.addItem=Add Item to Your Shopping Cart
cart.removeItem=Remove Item from Your Cart
  :

ユーザーは、同じディレクトリ内に、ロケールの接尾辞を付加したファイル名でプロパティ・ファイルを作成して、リソース・ファイルのメッセージの翻訳版を提示することができます。 たとえば、 ToyStoreResources.propertiesのメッセージのイタリア語の翻訳は、同じディレクトリの ToyStoreResources_it.propertiesファイルになります。翻訳版となるメッセージ・リソース・ファイルには、デフォルトの言語と同じ文字列キーで、メッセージ文字列を翻訳したものを含みます。 たとえば、前述の2つのメッセージ(英語版)は、メッセージ・リソース・ファイルのイタリア語版で次のようになります。

  :
cart.addItem=Aggiungi al carrello
cart.removeItem=Togli dal carrello
  :

翻訳が不要なメッセージは、翻訳版のメッセージ・リソース・ファイルで繰り返す必要はありません。 ロケール固有のメッセージ・バンドルでメッセージを見つけられない場合は、デフォルトのメッセージ・リソース・ファイルのものが使用されます。

注意

ロケールの言語 および国の組み合わせによって異なるメッセージを提供したい場合は、 ToyStoreResources_de_CH.propertiesのように、これらの2つが含まれている接尾辞を使用できます。 これは、ロケールでスイス(CH)の国に対してドイツ語(de)を使用するよう設定した場合に、使用されます。


前述の項のとおり、StrutsのBeanタグ・ライブラリの <bean:message>タグを使用すると、文字列 keyを指定することで、メッセージ・リソース文字列をJSPページに含めることができます。

<bean:message key="cart.addItem"/>

実行時に、Strutsの <bean:message>タグは、一致した最も有効なメッセージ・リソース・ファイルの文字列を返そうとします。 たとえば、ユーザーのロケールがスイス系ドイツ語(Swiss German:de_CH)の場合は、次のようになります。

  1. ToyStoreResources_de_CH.propertiesがある場合は、ここから文字列を返します。次に、
  2. ToyStoreResources_de.propertiesがある場合は、ここで文字列を検索します。次に、
  3. デフォルトのメッセージ・ソース・ファイルから、キーと一致する文字列を返します。

Strutsは、ブラウザが各リクエストで送信したHTTPリクエストのヘッダー・プロパティを参照して、現行ブラウザのユーザーのロケールを推測します。これによってユーザーの注文リストがユーザーの優先言語で表示されます。より詳細に説明すると、Strutsの RequestProcessorを介したそれぞれのリクエストで、 processLocale()メソッドのデフォルトの実装によって、Action.LOCALE_KEY定数で定義されている名前のHTTPセッション属性が存在するかどうかのチェックが行われます。 セッション属性が存在する場合は、処理を続行します。 存在しない場合は、Strutsはロケールを推測し、それを先のセッション属性として保存します。 このようにして、最終的に、Strutsによって1つのセッションにつきブラウザ・ユーザーの言語が1回特定されることになります。

注意

properties ファイルの日本語版を作成したい場合、XXXX_ja.properties という名前にします。このファイル内に日本語の翻訳メッセージを含めることで、ページに表示されるメッセージを日本語に変えることができるようになります。ただし、properties ファイルを日本語で翻訳した場合、ファイル自体を Unicodeエスケープする必要があります。これは、J2SDKに標準で付属の native2ascii ツールを使用することで実施できますが、JDeveloperには、IDEからnative2ascii を簡単に実行できる拡張機能が用意されています。これは、OTNの Extension Exchange、 または、メニューの「ヘルプ→更新の確認」でアクセスできるウィザードからダウンロードできます。


カスタムADF Business Componentsのカスタム・メッセージ・バンドル

ADF Business Componentsのエンティティ・オブジェクトおよびビュー・オブジェクトは、Javaメッセージ・バンドルを関連付けることができます。 このコンポーネント用のメッセージ・バンドルには、ラベル、ツールチップ、書式マスクといった属性レベルのコントロール・ヒントの他に、コンポーネント用の例外メッセージなどの、ロケールによって異なる値を格納できます。 ADFの設計時ウィザードは、設定されるメッセージに対して、 デフォルトの言語用のコンポーネント・メッセージJavaクラスを自動的に作成し、そこで管理します。ユーザーは、名前の最後にロケール固有の接尾辞を付けて、翻訳版のメッセージ・バンドルを作成できます。

たとえば、 toystore.model.businessobjects.Accountというエンティティ・オブジェクトの場合は、メッセージ・バンドルは AccountImplMsgBundle.javaという名前になり、 例20に示すようになります。 パッケージ toystore.model.businessobjectsのコンポーネントの場合は、メッセージ・バンドル(およびカスタム・クライアント・インタフェース)は、 toystore.model.businessobjects.common サブパッケージに自動的に作成されます。 このパッケージのネーミングは、メッセージ・バンドル・クラスやクライアント・インタフェースがクライアント層とサーバー層の両方で共通 して使用されることを表しています。これに対し toystore.model.*パッケージのすべての *Impl.javaクラスおよびXMLファイルは、両方の層で共有する必要がありません。 これらの使用はサーバー層に制限されます。このように、クライアント層またはWeb層が、インタフェース(およびメッセージ・バンドルなどの最少数のクラス)のみで機能するようにすることは、ベスト・プラクティスのテクニックで、ADFの設計機能は、それを強力に促進すべく、一貫したネーミング・パターンとデプロイ/パッケージング・サポートを実施します。

例20: Accountエンティティ・オブジェクトのリソース・バンドル
package toystore.model.businessobjects.common;
import oracle.jbo.common.JboResourceBundle;
import oracle.jbo.*;
import toystore.fwk.exceptions.ErrorMessages;
//  ---------------------------------------------------------------
//  ---    File generated by Oracle Business Components for Java.
//  ---------------------------------------------------------------
public class AccountImplMsgBundle extends JboResourceBundle  {
  public AccountImplMsgBundle() {}
  /**
   * @return an array of key-value pairs.
   */
  public Object[][] getContents() {
    return super.getMergedArray(sMessageStrings, super.getContents());
  }
  static final Object[][] sMessageStrings = {
    {"Country_Rule_0", "Invalid country code"},
    {"Addr1_LABEL", "Street Address"},
    {"Addr1_TOOLTIP", "Enter your street address"},
    /* etc. */
    {"Zip_LABEL", "Postal Code"},
    {"Zip_TOOLTIP", "Enter your postal code"}
  };
}

ToyStoreサンプル・アプリケーションには、次のような、エンティティ固有の例外メッセージを、文字列の2次元配列として手動で追加しています。

static final Object[][] sMessageStrings = {
    :
  {ErrorMessages.ENTITY_ALREADY_EXISTS,
   "Another user has already chosen this name. Please try another."},
    :
}

Strutsのメッセージ・リソースのプロパティ・ファイルにおいて文字列キーの順序が意味を持たないのと同様に、 Object配列の {String,String}要素の順序も意味がありません。

たとえば、このリソース・バンドルの翻訳バージョンをイタリア語で作成するには、 toystore.model.businessobjects.common パッケージに AccountImplMsgBundle_itクラスを作成します。 簡単な方法としては、

  1. AccountImplMsgBundle.javaをコピーして、 AccountImplMsgBundle_it.javaを作成します。
  2. 新しい AccountImplMsgBundle_it.javaで、クラスの宣言名を変更し、デフォルトの言語メッセージ・バンドル・クラスを拡張した形に修正します。
    public class AccountImplMsgBundle extends JboResourceBundle
    この行を次のように変更します。
    public class AccountImplMsgBundle_it extends AccountImplMsgBundle
  3. 新しい AccountImplMsgBundle_it.javaファイルで、デフォルトのコンストラクタを修正します。
    public AccountImplMsgBundle(){}
    この行を次のように変更します。
    public AccountImplMsgBundle_it(){}
  4. ユーザーに表示される文字列をイタリア語で編集します。

これで、 例21のような、翻訳されたメッセージ・バンドルを作成できます。

例21: Accountエンティティ・オブジェクトのリソース・バンドルのイタリア語バージョン
package toystore.model.businessobjects.common;
import oracle.jbo.common.JboResourceBundle;
import toystore.fwk.exceptions.ErrorMessages;

public class AccountImplMsgBundle_it extends AccountImplMsgBundle  {
  public AccountImplMsgBundle_it() {}
  public Object[][] getContents() {
    return super.getMergedArray(sMessageStrings, super.getContents());
  }
  static final Object[][] sMessageStrings = {
    {"Country_Rule_0", "Codice di paese inesistente"},
    {ErrorMessages.ENTITY_ALREADY_EXISTS,
    "Un altro utente ha già scelto questo nome. Prova un altro."},  
    {"Addr1_LABEL", "Indirizzo"},
    {"Addr1_TOOLTIP", "Inserisci il tuo indirizzo"},
    /* ecc. */
    {"Zip_LABEL", "CAP"},
    {"Zip_TOOLTIP", "Inserisci il tuo codice di avviamento postale"}
  };
}

メッセージ・バンドル・クラスは、フレームワークのベース・クラスとして用意されている oracle.jbo.common.JboResourceBundleを拡張するため、メッセージの「マージ」機能を継承することができます。 マージは、メッセージ・バンドルの getContents()メソッドから super.getMergedArray()を呼び出したときに実施されます。 これによって、メッセージ・バンドル・クラスには翻訳が必要なメッセージのみを記述し、そのほかのメッセージは、スーパークラスから継承することができます。 メッセージのマージが確実に機能するように、翻訳版のリソース・バンドルには、オーバーライドされた getContents()メソッドも含めるようにします。

注意

properties ファイルと同様に、メッセージ・バンドルの日本語版を作成する場合は、 XXXX_ja という名前にしますが、以下に示すような注意が必要になります。
このサンプル・コードに含まれるプロジェクトのうち、 ToyStoreModelFwkExtensions では、それらのプロジェクトに含まれるイタリア語用のメッセージ・バンドル・クラスを正常にコンパイルできるように、プロジェクトのコンパイル時の文字コードが cp1521 に設定されています(「プロジェクト・プロパティ」の「プロファイル - コンパイラ」ページの「文字コード」設定)。 このため、これらのプロジェクト内に日本語を含むクラスを作成しても、コンパイルに失敗します。したがって、上記手順によって日本語版のメッセージ・バンドル (XXXX_ja)を作成した場合は、追加で次の作業も行います:

  1. プロジェクトを新規作成し、そこに日本語版のメッセージ・バンドル(XXXX_ja)を移動させます。
  2. そのプロジェクトで正常にコンパイルできるように、プロジェクトのライブラリ設定やプロジェクトの依存性設定を修正します。
  3. ファイルが正常にコンパイルできるようになったら、それをJARとしてパッケージするために、デプロイメント・プロファイルを作成します(「新規ギャラリ」の「General - Deployment Profiles」カテゴリの「JARファイル」)。
  4. 元のプロジェクトでは、次の作業を行います:
    • 「プロジェクト・プロパティ」の「共通 - 依存性」パネルで、新規作成したプロジェクトをチェックし、日本語のメッセージ・バンドル・クラスが正しく参照されるようにします。
    • プロジェクト内のデプロイメント・プロファイルの設定の「プロファイルの依存性」パネルで、3. で作成したデプロイメント・プロファイル(.deploy)をチェックします。これによって、アプリケーションのデプロイ時にも、日本語のメッセージ・バンドル・クラスが正しくパッケージングされるようになります。

ビュー・オブジェクトも、ロケールによって異なるコントロール・ヒントの値を格納するため、カスタム・メッセージ・バンドルをサポートしています。 some.pkg.ViewObjectNameというビュー・オブジェクトの場合は、カスタム・メッセージ・バンドルは some.pkg.common.ViewObjectNameRowImplMsgBundle.javaという名前になります。 表3は、関連するカスタム・メッセージ・バンドルを持つADF ToyStoreサンプル・アプリケーションのコンポーネントを示しています。

表3: カスタム・メッセージ・バンドルを持つコンポーネント
toystore.model.* コンポーネント 含まれている内容
*.businessobjects.Account
  1. 属性のデフォルト・ラベルとツールチップ。
  2. toystore.fwk.model.businessobjectsパッケージの汎用的な EntityAlreadyExistsカスタム例外に対する、 Account固有のエラー・メッセージ。
  3. Country属性にアタッチされている ListValidationBeanルールに対するカスタム・エラー・メッセージ。このルールは、国コードが、 toystore.model.dataaccess.CountryListビュー・オブジェクトのデフォルト行セットの中の値コードになっているかどうかをチェックします。
*.businessobjects.Item Listprice属性に対する書式マスク、および数値フォーマッタ・クラスを表します。
*.businessobjects.Orders validateCreditCardExpiration()検証メソッドによってスローされる、カスタム検証エラー・メッセージが含まれています。
*.businessobjects.Signon
  1. 属性のデフォルト・ラベルとツールチップ。
  2. toystore.fwk.model.businessobjectsパッケージの汎用的な EntityAlreadyExistsカスタム例外に対する、 Signon固有のエラー・メッセージ。
*.dataaccess.ShoppingCart Listpriceおよび ExtendedTotal属性に対する書式マスク、および数値フォーマッタ・クラスを表します。
*.dataaccess.Accounts Statusおよび Userid属性を非表示にするためのコントロール・ヒント。

モデル層をサポートしているビジネス・サービスは、アクセスしているWeb層からリモートに配置されることもありえるため、モデル層は、ユーザーのロケールを、ユーザー・セッションごとに追跡します。 ADFBindingFilterも、同じような言語の優先機能を実装しており、ADFバインディング・コンテキスト上に現在の言語情報を設定します。 そこで、ADFの各データ・コントロールは、そのバインディング・コンテキストのロケールを取得します。

ADF、XSQLページ、XSLTおよびXMLスキーマの連携利用

Oracle XSQLページは、XMLベースの情報のフレキシブルな出力手段の一つで、 Oracle XML Developers Kit for Javaにバンドルされています。これを使用すると、XMLベースのページ・テンプレートを作成でき、その中にデータ連携をサポートする「アクション・ハンドラ」タグを組み込むことができます。 これには、SQL問合せや、ストアド・プロシージャ、Webサービス、および他のソースからXMLコンテンツを引き出すための、多数の組込みのアクション・ハンドラが付随しており、XSLT変換と組み合せることで、要求に応じたさまざまな種類のデータ・フォーマットを生成できます。 最も一般的なフォーマットはHTML、XML、またはシンプル・テキストですが、PDF(Apache FOPと組み合せた場合)、SVG、WML、および他のフォーマットにも対応できます。

ADFのイテレータ・バインディングに対するカスタムXSQLアクション・ハンドラ

組込みのアクション・ハンドラ・タグだけではニーズに合わない場合は、独自のカスタム・アクション・ハンドラを記述(『Oracle XML Developer's Kit プログラマーズ・ガイド』の「XSQL Pages パブリッシング・フレームワーク」章の「カスタムXSQL アクション・ハンドラの作成」を参照)して、それによって、要求に見合うようなXMLデータを取得したり、他のプログラム・タスクをXSQLページ処理の一部として実行するように、パブリッシング・エンジンを拡張することができます。

ADF Business ComponentsにはXMLメッセージの入出力サポート機能があるため、それを利用することで、ADFビュー・オブジェクトからXML形式でデータを出力する、XSQLのカスタム・アクション・ハンドラを簡単に作成できます。 例22は、この処理に必要なコードを示しています。

例22: ビュー・オブジェクト・イテレータに対するカスタムXSQLのアクション・ハンドラ
package toystore.fwk.xsql;
// imports removed for brevity
public class ADFViewObject extends XSQLActionHandlerImpl {
  public void handleAction(Node result) throws SQLException {
    XSQLPageRequest req = getPageRequest();
    if (req.getRequestType().equalsIgnoreCase("servlet")) {
      XSQLServletPageRequest xsqlHttpReq = (XSQLServletPageRequest) req;
      HttpServletRequest servletReq = xsqlHttpReq.getHttpServletRequest();
      DCBindingContainer bc = (DCBindingContainer) servletReq.getAttribute("bindings");
      if (bc != null) {
        Element action = getActionElement();
        String iteratorBinding = getAttributeAllowingParam("iterator", action);
        DCIteratorBinding iter = bc.findIteratorBinding(iteratorBinding);
        DCDataControl dc = iter.getDataControl();
        if (dc instanceof DCJboDataControl) {
          ViewObject vo = iter.getViewObject();
          if (vo != null) {
            Node n = vo.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS);
            ((XMLDocument) result.getOwnerDocument()).adoptNode(n);
            result.appendChild(n);
          }
        }
      }
    }
  }
}

注意

この資料作成時点では、XMLの読み書きをサポートしているデータ・コレクションはADF Business Componentsのビュー・オブジェクトだけです。したがって、このコードでは、名前を指定したイテレータに関連付けられているデータ・コントロールが、ADF Business Componentsベースのデータ・コントロール ( DCJboDataControl)である、つまりADFアプリケーション・モジュールをベースとしていることをチェックしています。将来的には、すべての種類のADFデータ・コレクションで readXML()および writeXML()をサポートする予定です。


このカスタム・アクション・ハンドラは、次の構文を使用してXSQLページ・テンプレートの内部で使用することができます。

<xsql:action handler="toystore.fwk.xsql.ADFViewObject" iterator="YourIteratorName"/>

以降の項では、このハンドラの面白い使い方についていくつか説明します。

XMLスキーマ・ダイアグラムによる設計とサービス

たとえば、ユーザーにXMLフォーマットで注文の内容を確認して欲しい場合があります。 これは、ワークフローのような自動化プロセスの一環として使用される場合であり、この方式を使うと、発行した注文の監査をある程度自動化できます。 一般的に、2つのプログラムでXMLをやり取りしなければならない場合は、交換するXMLの構造を記述したXMLスキーマの認識が必要です。その後、実行時に、このスキーマに準拠したXMLドキュメントを交換します。

JDeveloperのXMLスキーマ設計ツールを使用して、 図57のような「Toystore Orders」XMLスキーマを設計しました。 これによって、XMLスキーマの低レベルの詳細についてほとんど知識がなくても、XMLスキーマを作成できました。

角の丸い長方形のボックスは、スキーマのXML要素を表しています。 Orderのid属性などの属性は、これらの要素のボックス内でネストされています。 ある要素に、サブ要素のシーケンスを含める必要がある場合は、コンポーネント・パレットから シーケンス・コネクタ を図中にドラッグ&ドロップします。また、この中には、 PersonTypeItemTypeなどのカスタム・タイプを定義してあります。これらのカスタム・タイプは、 OrderedBy要素(PersonTypeの再利用)や Line要素(ItemTypeの再利用)で行ったように、スキーマの他の部分で再利用できます。

JDeveloperのXMLスキーマ設計ツールにおけるToystore Orderスキーマ
図57: JDeveloperのXMLスキーマ設計ツールによるToystore Orderスキーマ

このXMLスキーマに準拠した注文確認のXMLデータを提供するために、次の処理を行いました。

  1. Strutsページフロー図で /revieworderxml DataPageを作成します。
  2. この新しいDataPageノードをダブルクリックし、ビュー層ページの名前として revieworderAsXML.xsqlを指定します。
  3. Strutsページフロー図で /revieworderxml DataPageをもう一度クリックし、構造ウィンドウの「UIモデル」タブをクリックします。「<バインディングなし>」と表示されます。
  4. <バインディングなし>」でマウスを右クリックし、 「UIモデルの作成」を選択します。
  5. 作成したバインディング・コンテナ・ノードでマウスを右クリックし、 「バインディングの作成データイテレータ」を選択して、ToyStoreServiceデータ・コントロールの ReviewOrderデータ・コレクションに対するイテレータ・バインディングを作成します。
  6. 前の項で作成したカスタム・アクション・ハンドラを使用した <xsql:action>タグをXSQLページに追加し、それに対して、使用するイテレータの名前を指定します。
    <Page xmlns:xsql="urn:oracle-xsql">
      <xsql:action handler="toystore.fwk.xsql.ADFViewObject"
                   iterator="ReviewOrderIterator"/>
    </Page>

ベースとなる ReviewOrderビュー・オブジェクトには、バインド変数を使用するための問合せがあります。 前の例で行ったように、バインド変数を設定するためには、オーバーライドされた initializeModelForPage()メソッドを使用します。 そのため、 /revieworderxmlに対するDataForwardActionクラスをカスタマイズし、このサンプル・アプリケーションにある ReviewOrderActionクラスを作成しました。このクラスは、具体的には、次のようになります。

package toystore.controller.strutsactions;
import oracle.adf.controller.struts.actions.DataActionContext;

public class ReviewOrderAction extends ToyStoreServiceDataForwardAction {
  /**
   * Model initialization logic for this page.
   */
  protected void initializeModelForPage(DataActionContext ctx) {
    String id = ctx.getHttpServletRequest().getParameter("id");
    getToyStoreService(ctx).prepareToShowReviewOrder(id);
  }
  /**
   * Illustrate using an alternative mechanism, implemented in the
   * base ToyStoreServiceDataForwardAction class, to release all
   * data controls in use by the current binding container in 
   * stateless mode.
   */
  protected boolean releaseStateless() {
    return true;
  }
}

これにより、次のURLを使用して /revieworderxmlデータページにアクセスしようとすると

http://localhost:8988/ADFToyStore/revieworderxml.do?id=1001

例23で見るような結果が表示され、ADFビュー・オブジェクトで生成される標準のXMLフォーマットが反映されます。

例23: ビュー・オブジェクトの標準のXMLフォ示するXSQLデータページ
<Page>
  <ReviewOrder>
    <ReviewOrderRow>
      <Orderid>1001</Orderid>
      <Orderdate>2004-06-15</Orderdate>
      <Totalprice>19.98</Totalprice>
      <Userid>j2ee</Userid>
      <Firstname>Jay</Firstname>
      <Lastname>Tooey</Lastname>
      <ReviewLineItems>
        <ReviewLineItemsRow>
          <Itemid>EST-27</Itemid>
          <Name>White Dice</Name>
          <Quantity>1</Quantity>
          <Unitprice>3.99</Unitprice>
          <Extended>3.99</Extended>
        </ReviewLineItemsRow>
        <ReviewLineItemsRow>
          <Itemid>EST-18</Itemid>
          <Name>Apollo-13 Rocket</Name>
          <Quantity>1</Quantity>
          <Unitprice>15.99</Unitprice>
          <Extended>15.99</Extended>
        </ReviewLineItemsRow>
      </ReviewLineItems>
    </ReviewOrderRow>
  </ReviewOrder>
</Page>

これを、XMLスキーマの規約で期待されるフォーマットに変換するには、 例24に示されているようなXSLTスタイルシートを作成する必要があります。このスタイルシートでは、前述のXML出力を「Toystore Orders」のXMLスキーマ準拠の構文に変換します。

例24: XSLTの変換によってXML出力をスキーマに準拠するよう変換する
<xsl:transform version="1.0" 
               xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
               xmlns="urn:oracle-toystore-order"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <xsl:template match="/">
    <xsl:apply-templates select="Page/ReviewOrder/ReviewOrderRow"/> 
  </xsl:template>
  <xsl:template match="ReviewOrderRow">
    <Order id="{Orderid}" xsi:schemaLocation="urn:oracle-toystore-order 
                                              schemas/Order.xsd" >
      <OrderDate><xsl:value-of select="Orderdate"/></OrderDate>
      <OrderTotal><xsl:value-of select="Totalprice"/></OrderTotal>
      <OrderedBy>
        <GivenName><xsl:value-of select="Firstname"/></GivenName>
        <FamilyName><xsl:value-of select="Lastname"/></FamilyName>
      </OrderedBy>
      <Lines>
        <xsl:apply-templates select="ReviewLineItems/ReviewLineItemsRow"/>
      </Lines>
    </Order>  
  </xsl:template>
  <xsl:template match="ReviewLineItemsRow">
    <Line id="{position()}">
      <ItemId><xsl:value-of select="Itemid"/></ItemId>
      <Description><xsl:value-of select="Name"/></Description>
      <Quantity><xsl:value-of select="Quantity"/></Quantity>
      <UnitPrice><xsl:value-of select="Unitprice"/></UnitPrice>
      <LineTotal><xsl:value-of select="Extended"/></LineTotal>
    </Line>  
  </xsl:template>
</xsl:transform>

最後に、XSQLページ・テンプレートを補強して、このXSLTスタイルシートで、クライアントに返す前にスムーズにXSQLデータページを変換できるようにする必要があります。 このためには、次のように revieworderAsXML.xsqlページの最初に特別な行を追加することが必要です。

<?xml-stylesheet type="text/xsl" href="revieworderASXML.xsl"?>

テンプレートにこの行を追加すると、XMLの注文確認のリクエストによって、次のようなスキーマ準拠のデータグラムが生成されます。

<Order id="1001" xsi:schemaLocation="urn:oracle-toystore-order schemas/Order.xsd"
       xmlns="urn:oracle-toystore-order"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <OrderDate>2004-06-15</OrderDate>
  <OrderTotal>19.98</OrderTotal>
  <OrderedBy>
    <GivenName>Jay</GivenName>
    <FamilyName>Tooey</FamilyName>
  </OrderedBy>
  <Lines>
    <Line id="1">
      <ItemId>EST-27</ItemId>
      <Description>White Dice</Description>
      <Quantity>1</Quantity>
      <UnitPrice>3.99</UnitPrice>
      <LineTotal>3.99</LineTotal>
    </Line>
    <Line id="2">
      <ItemId>EST-18</ItemId>
      <Description>Apollo-13 Rocket</Description>
      <Quantity>1</Quantity>
      <UnitPrice>15.99</UnitPrice>
      <LineTotal>15.99</LineTotal>
    </Line>
  </Lines>
</Order>

XSLTを使用したXMLの変換

ADF ToyStoreのWebサイトで注文を発行すると、ユーザーにはブックマーク可能なリンクが表示され、そこで自分の注文確認ができます。 この /revieworderページを、前述の /revieworderxmlアクションに類似したXSQLテンプレートに関連付けられているDataPageとして設計しました。 モデル層で必要な設定は、前述のXMLを利用した注文確認の例と同じであるため、すでに学習した同じ ReviewOrderActionクラスを再利用できます。この設定のために、Strutsページフロー図で /revieworder DataPageに対して、ダブルクリックして新しいアクション・クラスを作成するのではなく、プロパティ・インスペクタを使用して、 typeプロパティの値を toystore.controller.strutsactions.ReviewOrderAction(前述と同じクラス)に設定しました。

revieworder.xsqlテンプレートは、XSLTスタイルシートの名前が違うことを除けば、前項で学習したものと同じです。

<?xml-stylesheet type="text/xsl" href="revieworder.xsl"?>
<Page xmlns:xsql="urn:oracle-xsql">
  <xsql:set-stylesheet-param name="orderId" value="{@id}"/>
  <xsql:action handler="toystore.fwk.xsql.ADFViewObject"
               iterator="ReviewOrderIterator"/>
</Page>

これを配置して、注文を発行した後でADF ToyStoreの「Thank You」ページからリンクをクリックすると、 図58のようなXML/XSLTベースの注文確認が表示されます。 ./WEB-INF/xsqlディレクトリの revieworder.xslスタイルシートについて調べると、HTMLを生成するスタイルシートは、XMLを他のXMLフォーマットに変換するために使用する場合と同じくらい簡単であることがわかります。 変換元のXMLドキュメントはADFビュー・オブジェクトによるXML出力で、例23で見たものと同じです。

ADFとXSQLを使用して構築された注文の概要ページ
図58: ADFとXSQLを使用して構築された注文の概要ページ

前に戻る | 目次に戻る | 次へ進む