ナビゲーション・ヘッダーをスキップ
Oracle ADF UIX開発者ガイド 目次へ
目次
前のページへ戻る
前へ
次のページへ進む
次へ

3. ADF UIXでのデータ・バインディング

このトピックでは、ノード・ツリー自体は変更せずに、UIX XMLのデータ・バインディング属性および要素属性を使用して、ユーザーごとに動的コンテンツを生成するOracle ADF UIXページの作成方法を説明します。 UIXによるJavaBeansおよびデータをグループ化するためのMapのサポート方法や、配列およびListを持つデータの反復方法を説明します。 また、インライン・データをプロトタイプ用のUIX XMLに直接作成する方法も説明します。 次に、この機能をサポートするJava DataObjectBoundValueDataProviderおよびDataObjectListインタフェースや、これらのJava APIを使用してさらにカスタマイズされたデータ・バインディングを実装する方法を学習します。 最後に、JavaBeansに対するUIXサポートの詳細を説明します。

注意: このトピックでは、データ・バインドにEL(式言語)を使用しています。 ELは、UIXリリース2.2(JDeveloper 9.0.5に付属)で導入されました。現在では、UIXの以前のバージョンで使用されているdata:構文の代替として推奨されています(data:構文は現在のバージョンでもサポートされています)。 このトピックの内容は、以前のバージョンの『UIX開発者ガイド』のデータ・バインドのトピックと基本的に同じですが、data:構文のかわりにEL構文を使用しています。 data:構文をEL構文に変換する方法の詳細は、「data:の変換: 式構文からEL構文」を参照してください。

ここでは、次の項目について説明します。

UIX XMLからのデータ・バインド

以降のいくつかのセクションでは、UIX XMLを使用する際のUIXのデータ・バインドについて説明します。 Java APIのみを使用してUIXの開発を行っている場合は、「Java APIからのデータ・バインド」へスキップしてもかまいません。

UIX XMLページで使用するデータ・バインド構文の設定

UIX 2.2(JDeveloper 9.0.5に付属)からは、UIXでは新しいELデータ・バインド構文と古いdata:データ・バインド構文の両方がサポートされています。 このため、どちらの構文を使用するかを示す必要があります。

ページのバインド構文をELに設定する方法

UIXページのルート要素が<ctrl:page ...><ui:page ...>または<ui:templateDefinition ...>である場合、そのルート要素に属性expressionLanguage="el"を設定します。 次に例を示します。


<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
                     xmlns:ui="http://xmlns.oracle.com/uix/ui"
                     expressionLanguage="el">
 <content>
   <!-- body -->
 </content>
</templateDefinition>

ルート要素が前述以外のものである場合、次に示すとおり、<ui:page expressionLanguage="el">要素内にラップします。

次のようなルート要素をラップします。

<dataScope ...>
 <!-- body -->
</dataScope>

ラップ後は次のようになります。

<page xmlns="http://xmlns.oracle.com/uix/ui" expressionLanguage="el">
 <content>
  <dataScope ...>
   <!-- body -->
  </dataScope>
 </content>
</page>

ページのバインド構文をdata:に設定する方法

構文を明示的にELに設定しない場合、UIXでは、デフォルトで古いdata:構文を使用するとみなされます。 属性expressionLanguage="data"を使用して、data:構文を明示的に設定することもできます。

アプリケーションのデフォルトのバインド構文の設定

アプリケーションのデフォルトのバインド構文は、uix-config.xml構成ファイルに設定できます。 これを行うには、<application-configuration>要素の子である<default-expression-language>の値を設定します。 デフォルトをELに設定するには、値にelを使用します。 デフォルトをdata:に設定するには、値にdataを使用します。 次に例を示します。

   <default-expression-language>el</default-expression-language>

値を設定しない場合、アプリケーションによってデフォルトでdataが使用されます。 構成およびuix-config.xmlファイルの詳細は、「ADF UIXの構成」のトピックを参照してください。

UIXでの動的属性

前のトピックの一番初めの例を使用して、最も単純な例から説明します。

<text xmlns="http://xmlns.oracle.com/uix/ui"
      text="hello, world"/>

「hello, world」のデータ・バインドのサポートを、ある興味深い内容に追加します。 具体的には、非常に単純なJavaBeansを使用して、現在の日付と時刻を表示します。


 package yourpackage;
 import java.util.Date;

  public class CurrentDateBean
  {
    public CurrentDateBean() { }

    public String getTime()
    {
      return (new Date()).toString();
    }
  }

ここで、getTime()が使用できるようにページを変更します。 次の3つの作業が必要です。

  1. UIXで、text属性をデータ・バインドします。
  2. <dataScope>をページに追加し、コンテンツにデータを供給します。
  3. Beanにアクセス可能な小規模のデータ・プロバイダをJavaで作成します。

まず、textをデータ・バインドします。

<text xmlns="http://xmlns.oracle.com/uix/ui"
    text="${uix.data.currentDate.time}"/>

この例に、1点の小規模な変更が加えられました。 値が、データを定義する式である${uix.data.currentDate.time}に変更されています。 これは、timeプロパティをcurrentDateから取得することを意味します。

このサンプル・コードを実行しても、何も起こりません。 currentDateをページに設定していないため、データ・バインドに失敗し、textがNULLのままだからです。 この設定をするには、<dataScope>をページに追加します。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="currentDate">
      <method class="yourpackage.DataDemo" method="getCurrentDate"/>
    </data>
  </provider>
  <contents>
    <text text="${uix.data.currentDate.time}"/>
  </contents>
</dataScope>

これで、大幅な変更が加えられました。 結果は次のとおりです。

このページに入力してこれを実行した場合、yourpackage.DataDemoクラスがUIXによって検出されないとエラーが表示されます。 その際、このクラスを記述する必要があります。


  package yourpackage;
  import oracle.cabo.ui.RenderingContext;

  public class DataDemo
  {
    static public Object getCurrentDate(
      RenderingContext context, String namespace, String name)
    {
      return new CurrentDateBean();
    }
  }

これは、単純なクラスです。 UIX XMLの<method>要素によって参照されると記述された、すべてのJavaのデータ・プロバイダには、ここで確認できるものと同一のシグネチャが設定されている必要があります。 このサンプル・コードにある3つのパラメータはいずれも必要ではありませんが、このJavaBeansを返す必要があります。 このメソッドをCurrentDateBeanクラスに移動することも可能ですが、このコーディング・スタイルはお薦めしません。 JavaBeansはどのアーキテクチャでも再利用可能なように設計する必要があります。さらに、UIX、サーブレットまたは他のテクノロジへの依存をできるだけ排除する必要があります。

先へ進む前に、2つの疑問点を解決しましょう。

  1. DataDemo.getCurrentDate()がコールされるタイミング

    DataDemo.getCurrentDate()は、ページがレンダリングされるたびに1回コールされます。 CurrentDateBeanに複数のプロパティが設定されていて、そのすべてを使用した場合でも、Beanは1回ロードされます。 ただし、getCurrentDate()はページがレンダリングされるたびにコールされます。 ページを100回表示する必要がある場合、このメソッドは100回コールされます。

  2. CurrentDateBean.getTime()がコールされるタイミング

    CurrentDateBean.getTime()は最低1回コールされます。 UIXでは、これによって各プロパティが1回しか要求されないとはかぎりません。繰り返し要求される場合もあります。 通常getTimeによって新しいDateオブジェクトが作成されるので、ページでtimeを何度も使用すると、異なる時刻がページに表示される可能性があります。 これが問題であるならば、DateCurrentDateBeanコンストラクタに作成し、getTime()がコールされるたびに同じオブジェクトを再利用してください。

データ・バインドでのマップの使用

JavaBeansは、データをカプセル化する適切な方法です。ただし、UIXで状態を設定するたびに新しいJavaBeansクラスを記述したり、新しいプロパティが使用されるたびに新しいgetメソッドを追加することは、単調で時間を要します。 また、すべてのプロパティ名を事前に把握するのは不可能な場合もあります。その場合は実行時に検出するしかありません。 UIXでは、このような事態に対応するために、Java Mapインタフェースがサポートされています。

次に示すのは、JavaBeansのかわりにマップを使用するデータ・プロバイダです。


  package yourpackage;
  import oracle.cabo.ui.RenderingContext;
  import java.util.HashMap;

  public class DataDemo
  {
    static public Object getURLAndText(
      RenderingContext context, String namespace, String name)
    {
      HashMap map = new HashMap();
      map.put("text", "Oracle Corporation");
      map.put("url", "http://www.oracle.com");
      return map;
    }
  }

さらに、これを使用するUIXを次に示します。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="link">
      <method class="yourpackage.DataDemo" method="getURLAndText"/>
    </data>
  </provider>
  <contents>
    <text text="${uix.data.link.text}"
           destination="${uix.data.link.url}" />
  </contents>
</dataScope>

この方法で開発するのは簡単ですが、JavaBeansを使用する場合ほど適切ではありません。 いつどの方法を使用するかは個人の好みの問題であり、ほとんどの開発者は両方の手法を使用することになるでしょう。

<boundAttribute>要素

最初の例を少し拡張してみましょう。 単に時刻を表示するかわりに、時刻の前に「The time is:」と表示します。 この静的テキストが含まれた<text>要素を追加する方法もありますが、ここでは異なる手法を示します。 ${...}を使用する場合、JavaBeanから値を取得し、その値に単純な操作を行うことができます。 <boundAttribute>要素によって、高度な設計ができます。 簡単な連結法則を使用した、非常に複雑な式が可能です。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
  <provider>
    <data name="currentDate">
      <method class="yourpackage.DataDemo" method="getCurrentDate"/>
    </data>
  </provider>
  <contents>
    <text>
      <boundAttribute name="text">
        <concat>
          The time is:
          <dataObject select="time" source="currentDate"/>
        </concat>
      </boundAttribute>
    </text>
  </contents>
</dataScope>

上の例から変更された部分は<text>要素のみです。 ${...}を使用してtextをバインドするかわりに、<boundAttribute>を使用しています。 これには<concat>要素が含まれており、これによってBeanから(<dataObject>要素とともに取得された)timeが静的文字列に結合されます。

<boundAttribute>内で使用される要素は数十個ありますが、ここではすべてを説明しません。 これらの要素の詳細は、JDeveloperのUIXビジュアル・エディタにある「コード・インサイト」を使用するか、「UIX要素リファレンス」を参照してください。 これらの要素は、対応するJavaインタフェースに由来してBoundValue要素と呼ばれます。 他のいくつかのBoundValue要素には、次のものが含まれます。

これらの大半については後半のトピックで説明しますが、全リストは「要素リファレンス」を参照してください。

データ反復処理

ここまでで、動的データを含むページを設定する方法を学びました。 ただし、前述のインタフェースでは、順序付けられていない一連のプロパティがサポートされるだけです。 表形式のデータ、またはJava配列のようなその他のデータを表示する場合、これは非常に不便なインタフェースです。 UIXでは、ListおよびJava配列の反復をサポートしています。 また、UIX固有のAPIであるDataObjectListもサポートしています。これについては後述します。

ここで、Java配列およびListからデータを取り出して、ページに戻す方法について説明します。 それには、現在のDataObjectをコールする設定が必要です。 すべてのデータ・フレームワークで、データの反復処理がサポートされています。 これはJavaではforループに相当します。 データベースではカーソルに相当します。 UIX Componentsでは、現在のDataObjectの指定がそれに当たります。 現在のDataObjectからデータを取得するために、ソース全体を省略するか、uix.current暗黙オブジェクトを使用します。

<styledText>
 <boundAttribute name="text">
   <dataObject select="time"/>
 </boundAttribute>
</styledText>

<!-- OR -->

<styledText text="${uix.current.time}"/>

<dataScope>currentData属性を使用して、現在のDataObjectを明示的に設定できます。

<dataScope currentData="${uix.data.currentDate}">
 <!-- Now we have a current data object -->
 ...
 <styledText text="${uix.current.time}"/>
 ...
</dataScope>

... しかし、これは通常使用される方法ではありません。 さらに一般的な方法として、Listまたは配列を自動的に反復し、現在のDataObjectを自動設定するコンポーネントを使用します。 UIXの<table>は、このようなコンポーネントの1つです。 ここでは<table>の詳細は省略します。<table>は、1つのトピックを設ける必要があるほど多機能だからです。ここでの説明は、現在のDataObjectの機能を理解するためのみにとどめておきます。

最初に、表に対してListを作成します。 ここで、MapArrayListを使用します。 Mapの配列やJavaBeansの配列なども、同じくらい簡単に使用できます。


package yourpackage;

//...
import java.util.List;
import java.util.ArrayList;

public class DataDemo
{
  // ...

  static public Object getTableData(
     RenderingContext context, String namespace, String name)
  {
    // The following List is recreated every time this method is called
    // (which is once per page render). A better implementation would
    // move the List creation to a UIX Servlet Event Handler.
    // See the Using a Controller in ADF UIX chapter for more information.
    List list = new ArrayList();
    for (int i = 0; i < 5; i++)
    {
      HashMap row = new HashMap();
      row.put("text", "Row " + i);
      row.put("text2", "Column 2, row " + i);
      list.add(row);
    }

    return list;
  }
}

次に、データを<table>に格納します。 <table>の各列は、<contents>の単一の子によって表されています。 このような子が3つある場合、表には列が3つ設定されます。

異なる方法でレンダリングされる各子を行ごとに取得する点が特長です。 これは、現在のDataObjectの指定を使用することで可能になります。 表で最初の行をレンダリングする際、現在のDataObjectにより、Listの最初のMapがポイントされます。 2行目のレンダリング時には、現在のDataObjectにより、Listの2番目のMapがポイントされます。 以下同じ処理が繰り返されます。

ここで表に2つの列を追加します。1つは通常のテキスト列、もう1つは赤色の列です。 (この例では、ビルトインのUIX Stylesの1つである、OraErrorTextスタイルを使用しています。)


<dataScope>
  <provider>
    <data name="someTableData">
      <method class="yourpackage.DataDemo" method="getTableData"/>
    </data>
  </provider>
  <contents>
    <table tableData="${uix.data.someTableData['.']}">
      <contents>
        <styledText text="${uix.current.text}"/>
        <styledText styleClass="OraErrorText" text="${uix.current.text2}"/>
      </contents>
    </table>
  </contents>
</dataScope>

これですべてです。 コードはそれほど長くありませんが、利点は多いです。

まず、<table>要素は、データ・バインドされたtableData属性を使用してデータを取得しています。 ここで注目すべき点は、データ・バインドの式にあるピリオド(.)です。 先に記述したgetTableData()では、ArrayListが返されました。 表のデータには、ArrayListのプロパティではなく、ArrayListそのものが必要です。 この構文によって、それが可能です。

次に、2つの子が追加されました。 1つ目は、現在のDataObjectのtextキーからテキストを取得する<styledText>です。 2つ目は、別のキーにバインドされたテキストが設定された、もう1つの<styledText>です。 データ・モデルをまったく使用せずに、列の追加、削除およびレンダリングをUIで直接行えます。

これは単純な例です。列ヘッダーや行ヘッダー、あるいは表で実行可能な多数の処理は追加していません。また、編集不可の普通のテキストのみを使用しています。 ただし、これらはすべて同じ方法で機能します。 表の行ごとにレンダリング方法を変える場合、現在指定されているDataObjectに、その変更内容を示す情報が指定されている必要があります。

たとえば、各行にリンクを指定し、それぞれの宛先が異なるとします。 この場合は簡単です。 <link>要素を使用してdestination属性を現在指定されているDataObjectにデータ・バインドし、各行の宛先を含むようにDataObjectを更新します。 UIXでは、すべてについてデータ・バインドが可能なため、各行が異なるようにできます。

DataObject API

DataObject Javaインタフェースは、UIX ComponentsおよびUIX XMLのページの汎用データソースです。 UIXでは、JavaBeansやマップを渡す場合、軽量アダプタ・クラスを使用して、これらのオブジェクトをDataObjectに変換します。 このアダプタについては後述します。 ここでは、DataObjectそのもののJava APIについて、およびこれが関心を持たれる理由を説明します。 DataObjectは、1つのメソッドのみを含む、非常に単純な新たなインタフェースです。

public interface oracle.cabo.ui.data.DataObject
{
  /**
   * Select a single value out of the DataObject.
   * @param context the current RenderingContext
   * @param select  a select key
   * @return the selected value
   */
  public Object selectValue(RenderingContext context, Object select);
}

DataObjectは小規模のインタフェースであり、開発者またはデータ構造に対する要件はごくわずかです。 そのため、次のような任意のデータ形式に容易に適応できます。

UIXには、前述のJavaBeansおよびマップへのビルトイン・バインドが組み込まれています。 その他の多くのデータ・ソースも、ADFモデルを使用してUIXで使用できます。

ほとんどの開発者は、最初にselectパラメータを目にした際にHashtableのキーを想定するでしょうが、DataObjectはそのようには機能しません。 selectパラメータは次のいずれかになります。

UIXによる開発では多くの場合、DataObject APIに対してコード化する必要はありません。 しかし、マップやJavaBeansのかわりにDataObjectを使用することを考慮してもいい理由があります。 DataObjectはUIXでデータ用に使用される実際の概念です。 したがって、Java APIを使用してBeansを作成する場合、メソッドの取得対象であるDataObjectを使用する必要があります。 (後述するとおり、BeanAdapterUtilsを使用してJavaBeansおよびMapをDataObjectに簡単に変換することもできます。)

また、これらのJavaBeansに対するビルトイン・サポートによるオーバーヘッドがまったくないわけではありません。 パフォーマンス上のコストがかかります。 最初に、DataObjectを実装するアダプタ・オブジェクトを作成する必要があります。 これらは軽量であるとは言え、オブジェクト作成は可能ならば避けた方が無難です。 次に、イントロスペクション(実行時におけるメソッドの動的な検出およびコール)は本質的に動作が遅くなります。 このプロセスの最もコストが高い部分(Methodオブジェクトの検出)のキャッシュについては快適な動作が提供されますが、Methodオブジェクトへのコールについては依然として、手動コーディングされたDataObjectにおけるコンパイル済メソッド・コールよりも動作が遅くなります。

ただし、「早まった最適化は諸悪の根源である(Premature optimization is the root of all evil)」というプログラミングについての有名な格言を思い出してください。 手動コーディングされたDataObjectがより速いとしても、DataObjectの記述に時間を費やすべきということにはなりません。 適度に使用されていれば、JavaBeansの使用がパフォーマンスに重大な影響を与える理由はありません。 本質的に動作が遅いJavaBeans(データベースにアクセスする必要がある場合など)については、アダプタの追加オーバーヘッドは少なくほとんど問題になりません。 ただしプロファイリングを実行して、Beansの1つの適合オーバーヘッドがパフォーマンスに重大な影響を与えていると判断した場合は、オブジェクト作成およびイントロスペクション・オーバーヘッドの両方の問題に取り組むことができます。 このパフォーマンスに関する問題を解決するいくつかの簡単な方法は、後で説明します。

最後に、そして最も有用な点ですが、DataObjectRenderingContextを受け取ります。 RenderingContextには多くの有用な値が含まれています。 Localeを使用してdate文字列を適切に書式化する例を示します。


 package yourpackage;
 import oracle.cabo.ui.RenderingContext;
 import oracle.cabo.ui.data.DataObject;
 import java.text.DateFormat;
 import java.util.Date;
 import java.util.Locale;

  public class CurrentDateObject implements DataObject
  {
    public Object selectValue(RenderingContext context, Object select)
    {
      if (!"time".equals(select))
        return null;
      Locale locale = context.getLocaleContext().getLocale();
      DateFormat formatter =  DateFormat.getDateTimeInstance(
        DateFormat.MEDIUM, DateFormat.MEDIUM, locale);

      return formatter.format(new Date());
    }
  }

CurrentDateBeanのかわりにこのクラスを使用する場合、日付は現在のLocaleに対して自動的に正しく書式化されます。

UIX XMLでのデータおよびデータ・リストの作成

UIX XML内にデータとデータ・リストの両方を直接定義できます。 ページが実際のデータソースに連結される前にダミー・データを作成できるため、非常に便利です。

まず、DataProviderの新しい要素、<inline>を紹介します。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
           xmlns:demo="http://www.example.org/">
 <provider>
   <data name="linkData">
     <inline>
        .... data object is in here ...
     </inline>
   </data>
 </provider>

 <contents>
    ...
 </contents>
</dataScope>

この要素は、前述した<method>要素に似た動作をしますが、Javaコードはコールしません。 UIX XML内でデータを直接定義します。

<inline>のすべての属性によって、作成されるデータ内にキーと値のペアが定義されます。 次に例を示します。

<inline url="http://example" text="Inline text">
</inline>

上の例では、2つの値を持つデータを定義しています。1つのキーはurl、もう1つのキーはtextです。 簡単に定義できます。

ただし、子要素を使用し、サブ値としてデータおよびデータ・リストを追加することもできます。 要素の名前は、子のデータまたはデータ・リストが格納されるキーです。 同じ要素名が複数ある場合は、それらの要素がまとまってデータ・リストを定義します。 1つしかない場合、1つのデータの集合と単一要素のデータ・リストの両方が定義されます。 次の例でわかりやすく示します。

<inline foo="Inline text">
 <stuff random="Stuff text"/>

 <row text="First row"/>
 <row text="Second row"/>
</inline>

この例には、3つの内容を含むデータがあります。

非常に簡単です。 ここで表の例に戻ります。今回は、インライン・データのみを使用します。

<dataScope>
 <provider>
   <data name="inlineData">
     <inline>
       <tableKey text="Row 0" text2="Column 2, Row 0"/>
       <tableKey text="Row 1" text2="Column 2, Row 1"/>
        ... etc ...
       <tableKey text="Row 4" text2="Column 2, Row 4"/>
     </inline>
   </data>
 </provider>

 <contents>
   <table tableData="${uix.data.inlineData.tableKey}">
     <contents>
       <styledText text="${uix.current.text}"/>
       <styledText styleClass="OraErrorText" text="${uix.current.text2}"/>
     </contents>
   </table>
 </contents>

</dataScope>

UIX XMLでの複雑なデータ・バインド

このセクションでは、キーとデータ・オブジェクトをネストして、さらに複雑なデータ・バインドを生成する方法を説明します。 最初に、次のフォームを使用して、ネストされたキー・データ・バインドを生成します。

attributeName="${uix.data.name.key1.key2...keyN}" 

この場合、この属性の値を計算する際に、nameで参照されるDataObjectについてkey1が使用され、別のDataObjectが生成されます。2番目のDataObjectにはkey2が使用され、3番目のDataObjectが生成されます。 このプロセスは、keyNが使用され、この属性の最終値が生成されるまで続きます。 すべてのDataObjectの値自体がDataObjectであることが必要です。最後のキーkeyNの値のみは、(属性が必要とするタイプであれば)DataObjectでなくてもかまいません。 ネストされたキー・バインドの例を次に示します。

<dataScope>
 <provider>
   <data name="families">
     <inline>
       <Smith members="4">
         <address number="2255" street="37th Ave" city="San Francisco"
            state="CA" zip="94116" />
       </Smith>
       <Jones members="3">
         <address number="500" street="Oracle Parkway" city="Redwood Shores"
           state="CA" zip="94065" />
       </Jones>
     </inline>
   </data>
 </provider>
 <contents>
   <messageTextInput prompt="The Smiths live in "
        text="${uix.data.families.Smith.address.city}" />  <!--San Francisco-->
   <messageTextInput prompt="The Jones's live in "
        text="${uix.data.families.Jones.address.city}" /> <!--Redwood Shores-->
 </contents>
</dataScope>

上の例では、最初のmessageTextInputで「The Smiths live in San Francisco」が生成され、2番目で「The Jones's live in Redwood Shores」が生成されます。

ネストされたデータ・オブジェクト・バインドを生成するために大カッコも使用できます。 次の例について考えてみます。

attributeName="${uix.data.name2[uix.data.name1.key1]}"

上の例では、name1(で参照されるDataObject)でkey1が使用されてキーを生成し、そのキーがname2で使用されてこの属性の最終値を生成しています。 これは、キー自体がデータ・バインドされる例です。 .keyは、['key']の簡易表記法です。

<!-- These two are equivalent -->

attributeName="${uix.data.name.key1.key2}"
attributeName="${uix.data.name.key1['key2']}"

次に示すのは、前の例で作成されたfamiliesデータ構造を使用した大カッコの例です。これによって、「The Jones family has 3 members.」が生成されます。

<dataScope>
 <provider>
   <data name="families">
      ... as above ...
   </data>
   <data name="selection">
     <inline current="Jones" />
   </data>
 </provider>
 <contents>
    The <link text="${uix.data.selection.current}"/> family
    has <link text="uix.data.families[uix.data.selection.current].members" /> members.
 </contents>
</dataScope>

大カッコは、次の例のようにネストすることもできます。


attributeName="${uix.data.name3[uix.data.name2[uix.data.name1.key1]]}"

この例では、key1name1で使用されてキーを生成し、そのキーがname2で使用され、最終値を計算するname3に対する別のキーを生成します。

キーを省略すると、次の例のようにDataObject自体が返されます。

<!-- The following attribute is bound to the data object itself -->

attributeName="${uix.data.name}"

キーおよびデータ・オブジェクト名には一定の制限があります。たとえば、キーのテキストには[記号を使用できません。 ただし、uix.data.name["["]という大カッコ形式を使用すると、キー内でこの記号を使用できます。

ELの暗黙変数および関数

このセクションでは、UIX Componentsのデータ・プロバイダによって実装される暗黙変数を示します。

UIXファイルで使用可能なUIX Componentsの関数を次に示します。

Java APIからのデータ・バインド

次のいくつかのセクションで、Java API固有の多くの詳細を説明します。 全体をUIX XMLから開発している場合、独自に作成したUIX XMLの機能およびフレームワークの拡張方法を取得するとき、これは重要になる可能性があります。

動的属性: BoundValue

BoundValueインタフェースは、UIX Componentsの動的ページにとって不可欠です。 これは、次のようにRenderingContextが渡され、任意のJavaオブジェクトが返される1つのメソッドで構成されています。

public interface oracle.cabo.ui.data.BoundValue
{
  public Object getValue(RenderingContext context);
}

この単純なインタフェースが便利なのは、すべてのUIX Components Beanであらゆる属性値のかわりに使用できるためです。 属性の値がBoundValueに設定されている場合、その属性の値を取得しようとしてもBoundValueは返されません。 UIX ComponentsではBoundValue.getValue()をコールし、結果を返します。 次に例を示します。

  // Remember the code to set the text of a StyledTextBean...
  styledTextBean.setText("Some text");
  // ... is really a cover for:
  styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
                                   "Some text");

  // So you can also set a BoundValue:
  BoundValue boundValue = ...;
  styledTextBean.setAttributeValue(UIConstants.TEXT_ATTR,
                                   boundValue);

この後、表示時にStyledTextBeanのテキストを要求する場合、次のように指定します。

  text = styledTextBean.getAttributeValue(renderingContext,
                                          UIConstants.TEXT_ATTR)
  // .. is equivalent to writing:

  BoundValue boundValue = ...;
  text = boundValue.getValue(renderingContext);

setAttributeValue()BoundValueを使用して、すべての属性をデータ・バインドできることを覚えておいてください。 属性のデータ・バインドのための専用のメソッドがそのBeanにあるかどうか、以前にその属性がデータ・バインドされたかどうか、あるいはその属性が必要とするJavaオブジェクトのタイプはどれかなどは関係ありません。 すべての属性をデータ・バインドできます。

多くのBeanで、属性をバインドするための便利なメソッドが用意されています。 StyledTextBeantext属性もその1つです。 次のように記述できます。

  BoundValue boundValue = ...;
  styledTextBean.setTextBinding(boundValue);  

ただし、これは利便性が高いというだけです。 便利なメソッドがあるかどうかにかかわらず、すべてのBeanですべての属性をバインドできます。

例: 現在の日付

BoundValueには多数の実装が用意されていますが、次にカスタムの実装について説明します。 次のコードでは常に現在の日付が返されます。

  public class CurrentDate implements BoundValue
  {
    public Object getValue(RenderingContext context)
    {
      return new Date();
    }
  }

これをDateFieldBeanで使用します。

  DateFieldBean dateField = new DateFieldBean();
  dateField.setValueBinding(new CurrentDate());

上の例で生成される日付フィールドは、常に現在の日付で初期化されます。 上のコードは次のコードとよく似ています。

  DateFieldBean dateField = new DateFieldBean();
  dateField.setValue(new Date());

ただし大きな違いがあります。 2番目の例では日付の設定は1回です。 Beanを1回のみ使用して解放する場合は、問題ありません。 ただし、UIX ComponentsではBeanは何度も再利用できます。 最初の例で使用したBoundValueの場合、UIX Componentsにより、ページのレンダリングのたびに日付が要求されます。このため、DateFieldBeanが作成された後でも常に正確な月日が表示されます。

次に、独自のDataObjectクラスの記述方法を説明します。

例: BundleDataObject

このクラスでは、JavaのResourceBundleDataObjectに変換します。 このクラスは大変便利なため、UIX Componentsに含まれています。ここではその説明を通して、DataObjectを実装する際の基本的な考え方を示します。 この例では、DataObjectで例外を処理する方法の1つも示されます(他の方法は「ADF UIXでのエラーの処理」のトピックを参照してください)。

  import java.util.MissingResourceException;
  import java.util.ResourceBundle;

  import oracle.cabo.ui.data.DataObject;

  public class BundleDataObject implements DataObject
  {
    public BundleDataObject(ResourceBundle bundle)
    {
      _bundle = bundle;
    }

    public Object selectValue(RenderingContext context, Object select)
    {
      try
      {
        return _bundle.getObject(select.toString());
      }
      catch (MissingResourceException e)
      {
        context.getErrorLog().logError(e);
        return null;
      }
    }

    private ResourceBundle _bundle;
  }

留意点:

  1. ResourceBundleをラップした場合、1つのオブジェクトしか割り当てられません。 しかし、Mapを作成してすべてのデータをResourceBundleから取り出した場合、さらに多数のオブジェクトを作成する必要があります。 また、前述の例の場合は、実行時に実際に使用されているキーのみが取得されます。
  2. DataObjectで、例外がスローされないようにする必要があります。 ここでは、MissingResourceExceptionsを検出し、標準のUIX ErrorLogに記録してから、安全な値すなわちnullが返されています。

DataObjectの独自の実装は、非常に簡単に記述できます。 カスタムの実装を使用することによって、アプリケーションの効率やパフォーマンスが向上します。

DataObjectRenderingContext

DataObjectの作成後、RenderingContextインタフェースに連結して、UIX Componentsのページで使用できるようにします。 (前述のように、このインタフェースが、UIX Components BeanとUIX XMLのページに、レンダリングに必要なコンテキストを渡します)。 ここで、RenderingContextの特定のメソッドに注目します。

  public DataObject getDataObject(String namespaceURI,
                                  String localName)

このメソッドによって、UIX Components BeanまたはBoundValueでは、任意の数のDataObjectへのアクセスが可能になります。 UIXでは通常、これらのDataObjectは2つの文字列で識別されます。 WebサイトのURLに基づく1つ以上のネームスペースと、 他のコードと競合しない任意のローカル名を使用します。

DataObjectへのバインド: DataBoundValue

ここで、DataObjectRenderingContextに渡す方法からは一度離れ、DataObjectからデータを取得してUINodeまたはUIXに渡す方法を説明します。 ここで、DataBoundValueクラスが渡されます。UIX Componentsでは、DataBoundValueクラスを使用して、RenderingContextDataObjectを取得できます。

  LinkBean bean = new LinkBean();
  DataBoundValue destinationBinding =
    new DataBoundValue(_EXAMPLE_NAMESPACE,
                       "dataObjName",
                       "url");
  bean.setAttributeValue(UIConstants.DESTINATION_ATTR,
                         destinationBinding);

  // ...

  static private final String _EXAMPLE_NAMESPACE =
     "http://www.example.org";

これは、レンダリング時に次のJavaコードを実行していることと同じです。

  // ...
  DataObject dataObject = context.getDataObject("http://www.example.org",
                                                "dataObjName");
  Object destination = dataObject.selectValue(context, "url");

  //  ...

DataBoundValueが有用なため、DataBoundValueを作成する次のような便利なメソッドがBeanに用意されています。

  LinkBean bean = new LinkBean();
  bean.setDestinationBinding(_EXAMPLE_NAMESPACE
                             "dataObjName",
                             "url");

DataObjectの登録: DataProvider

ここまでで、独自のDataObjectを記述する方法、それらをRenderingContextから取得する方法、およびそれらを属性にバインドする方法を学びました。 この他に説明が必要なものは、DataObjectRenderingContextに渡す方法です。 この方法では別のインタフェース、DataProviderインタフェースを使用します。 これは小規模なインタフェースです。

  public interface DataProvider
  {
    public DataObject getDataObject(
      RenderingContext context,
      String           namespace,
      String           name);

    // ... methods omitted
  }

まだ説明していないメソッドがいくつかありますが、中でもgetDataObjectは重要なメソッドです。これは、DataObjectを取得するためにRenderingContextでコールするメソッドです。 このメソッドにはネームスペースと名前が渡されるため、各DataProviderでは様々な多くのDataObjectを提供できます。 最後に、DataProviderRenderingContextに連結する方法です。これは、もう1つのメソッドを使用するだけで可能です。

  public interface RenderingContext
  {
    // ...

    public void addDataProvider(DataProvider provider);

    // ...
  }

DataProviderRenderingContextにする回数で何度でもこのメソッドをコールできます。 (実際には、すべてのDataProviderTableDataProviderの中に入れ、それをCachingDataProviderの中に入れる方が適切です。) これで準備ができました。 次の要約の後、全工程を示す例に進みます。 図3-1を参照してください。

図3-1: DataObject、DataProvider、RenderingContextおよびUINodeの関係

DataObject、DataProvider、RenderingContextおよびUINodeの関係の論理表示

  1. DataProviderDataObjectのコレクションです。
  2. DataObjectは任意のデータのコレクションです。
  3. RenderingContextは、DataProviderからDataObjectを取得します。
  4. DataBoundValueオブジェクト(またはUIX XMLのdataObject要素)によって、すべてがUINode属性に連結されます。

DataProviderの例: UIX Components

非常に単純なページを作成します。 リンクが1つのみあり、コンテンツを1つのDataObjectにデータ・バインドします。 すべてをJSPで表示します。 最初にいくつかの定数を定義します。

public class DataDemo
{
  // ...
  private static final String _DEMO_NAMESPACE   = "http://www.example.org/";
  private static final String _DATA_OBJECT_NAME = "TextData";

  private static final Object _TEXT_KEY = "textKey";
  private static final Object _DESTINATION_KEY = "urlKey";
}

ここで、UIX Componentsのノードを作成する関数を記述します。

public class DataDemo
{
  static public UINode getPage()
  {
    // An ultra-simple example - we'll create a single link:
    LinkBean link = new LinkBean();
    link.setTextBinding(
      new DataBoundValue(_DEMO_NAMESPACE,
                         _DATA_OBJECT_NAME,
                         _TEXT_KEY)
                       );

    // Or, we can use the built-in DataBoundValue convenience methods
    link.setDestinationBinding(_DEMO_NAMESPACE,
                               _DATA_OBJECT_NAME,
                               _DESTINATION_KEY);

    // And put the link inside of a "BodyBean" (see below)
    BodyBean body = new BodyBean();
    body.addIndexedChild(link);
    return body;
  }

  // ...
}

このコードは、前に作成したコードによく似ています。 リンクのBeanを作成し、2つの属性をデータ・オブジェクトにバインドしています。 新しく追加したのはBodyBeanのみです。 これはデータ・バインドの一部ではありません。HTMLの<body>タグの作成に常に使用されるUIX Components Beanです。

ここで、DataProviderを設定する関数を記述します。

注意: これは非常に基本的な例です。データがハードコードされており、基本のDictionaryDataクラスを使用してそのデータを格納しています。 ただし、レンダリング時にJSPのPageContextを渡すことができます。つまり、そのコンテキストを使用してここで必要なすべての状態を取得できます。 さらに、DataProviderおよびDataObjectにデータが要求される際、RenderingContextはその両方に渡されます。 また、ServletRequestHttpSessionなどの一般的なサーブレット・オブジェクトをRenderingContextから取得できるため、すべてのデータを事前にロードしておく必要はありません。 データが実際に要求されるまで待機できます。

public class DataDemo
{
  // ...

  static public DataProvider getDataProvider()
  {
    DataObject data = _getData();

    // And put it in a DataProvider
    TableDataProvider provider = new TableDataProvider();
    provider.put(_DEMO_NAMESPACE, _DATA_OBJECT_NAME, data);

    return provider;
  }

  static private DataObject _getData()
  {
    // Build up a DataObject
    DictionaryData data = new DictionaryData();
    data.put(_TEXT_KEY, "Shameless promotion!");
    data.put(_DESTINATION_KEY, "http://www.oracle.com");

    return data;
  }


  // ...
}

ここで使用したTableDataProviderに注意してください。 このクラスでは、DataObjectやその他のDataProviderを、ネームスペースおよび名前によって分割します。

最後に、レンダリングを行うJSPを記述します。

<%@ page contentType="text/html" %>
<%@ page import='oracle.cabo.ui.ServletRenderingContext'%>
<%@ page import='oracle.cabo.ui.beans.StyleSheetBean'%>
<%@ page import='oracle.cabo.ui.data.DataProvider'%>
<%@ page import='yourpackage.DataDemo'%>
<html>
<head>
<%
  // Create a rendering context
  ServletRenderingContext rContext =
    new ServletRenderingContext(pageContext);

  // Include the stylesheet that UIX Components needs
  StyleSheetBean.sharedInstance().render(rContext);
%>
</head>
<%
  // Get the data provider, and attach it to the
  // rendering context
  DataProvider provider = DataDemo.getDataProvider();
  rContext.addDataProvider(provider);

  DataDemo.getPage().render(rContext);
%>
</html>

この例を実行すると、「Shameless promotion!」という語句が表示されます。これは、オラクル社のWebサイトにリンクされています。

この例では、別のBean、StyleSheetBeanも使用しています。 このBeanでは、UIX Componentsのすべてのページで必要とされるスタイルシートを自動的に追加します。 このBeanの使用による、1行のコードでの高性能処理やカスタマイズの実現については、後半のトピックで説明します。 ここでは、このBeanがHTMLのすべての<head>に属することを理解してください。

必要な作業はこれですべてです。1つの単純なリンクをレンダリングするには少々過度な処理です。 ただし、データベースとの対話、ローカライズされたテキストの提供、またはローカライズされたデータに関するその他のソースの提供を可能にする、汎用のDataProviderが使用されています。

データ反復処理

ここまでで、BoundValueDataObjectを使用して、動的データを含むページを設定する方法を学びました。 ただし、DataObjectインタフェースでは、一連のプロパティがサポートされるだけです。 表形式のデータ、またはJava配列のようなその他のデータを表示する場合、これは非常に不便なインタフェースです。 そのような場合に、DataObjectListインタフェースが使用されます。

DataObjectList

このトピックで説明する最後の新規インタフェースは、単純なインタフェースです。

  public interface DataObjectList
  {
    public int getLength();
    public DataObject getItem(int index);
  }

DataObjectListは、実際にはDataObject配列です。 (実際には配列を使用しないため、DataObjectListを不変に保つことができます。何かを設定するメソッドはないため、DataObjectを作成するか、DataObjectインスタンスを再利用することになります。)

ArrayDataSetクラスは、DataObjectListの単純で便利な実装です。 DictionaryDataのように使用方法が単純ですが、すべてのDataObjectを事前に作成する必要があります。

  DataObject[] array = new DataObject[10];
  for (int i = 0; i < 10; i++)
  {
    array[i] = ...;
  }

  ArrayDataSet list = new ArrayDataSet(array);

DataObjectListへのバインド: 現在のDataObject

ここで、データをDataObjectListから取り出して、ページに戻す方法について説明します。 それには、現在のDataObjectを指定することが必要です。

すべてのデータ・フレームワークで、データの反復処理がサポートされています。 これはJavaではforループに相当します。 データベースではカーソルに相当します。 UIX Componentsでは、現在のDataObjectの指定がそれに当たります。

このトピックで前述したRenderingContext.getDataObjectメソッドを思い出してください。 これまで触れていませんが、DataObjectを取得するためのメソッドがもう1つあります。

public interface RenderingContext
{
  // ..

  /**
   * Get a DataObject by namespace and name.
   */
  public DataObject getDataObject(String namespaceURI,
                                  String localName)

  /**
   * Get the "current" DataObject.
   */
  public DataObject getCurrentDataObject();

  // ..
}

この新しいメソッドgetCurrentDataObject()は、現在のDataObjectを返します。これは、データベース・カーソルに相当するUIX Componentsのメソッドであり、DataObjectListへのバインドに使用します。

Javaで現在のDataObjectからデータを取得するには、1つの引数を使用するDataBoundValueの特別なコンストラクタ、または1つの引数を使用するBeanの便利なメソッドを使用します。

  StyledTextBean textBean = new StyledTextBean();
  textBean.setAttributeValue(UIConstants.TEXT_ATTR,
                             new DataBoundValue(TEXT_KEY));

  // ... OR, EASIER ...

  textBean.setTextBinding(TEXT_KEY);

ここまでの内容をすべて使用して、TableBeanのためのDataObjectListを作成してみましょう。

public class DataDemo
{
  // ...

  static private DataObjectList _getDataForTable()
  {
    DataObject[] rows = new DataObject[5];
    for (int i = 0; i < rows.length; i++)
    {
      DictionaryData row = new DictionaryData();
      row.put(_TEXT_KEY, "Row " + i);
      row.put(_TEXT_2_KEY, "Column 2, row " + i);
      rows[i] = row;

    }

    return new ArrayDataSet(rows);
  }

  // ...

  private static final Object _TEXT_2_KEY = "text2Key";
}

ここでも、例を単純にするためにハードコードされたデータを使用しましたが、実際のアプリケーションではより多くのデータを処理できます。 ここで、このDataObjectListを表に入れる必要があります。 次のようにコールできます。

  TableBean table = new TableBean();
  table.setTableData(_getDataForTable());

ただし、このように設定されたデータは動的にならないため、リクエストごとにTableBeanを再利用することはできません。 データを動的にするために、前のデモで使用したものと同じDataObjectにデータを追加できます。 新しいキーも必要になります。また、コードを変更して、TableBeanをページに追加します。

public class DataDemo
{
  // ...
  static public UINode getPage()
  {
    // ... old code ...

    // And now, let's create the table
    TableBean table = new TableBean();
    table.setTableDataBinding(_DEMO_NAMESPACE,
                              _DATA_OBJECT_NAME,
                              _TABLE_DATA_KEY);
    body.addIndexedChild(table);

    return body;
  }


  static private DataObject _getData()
  {
    // Build up a DataObject
    DictionaryData data = new DictionaryData();

    // ... old code ...

    // And now, add the DataObjectList
    data.put(_TABLE_DATA_KEY, _getDataForTable());
    return data;
  }


  // ...

  static private final Object _TABLE_DATA_KEY = "tableKey";
}

前に示した例のように、DataProviderを使用して、DataObjectRenderingContextに追加されます。 このコードを変更する必要はありません。 また、表データのバインドに以前と同じコードを使用していることに注意してください。tableDataは単に別の属性であり、その他の要素は変わっていません。

ここには多数のオブジェクトがあります。 作成したオブジェクトについて見なおします。

  1. DataProviderは1つのDataObjectを提供します。
  2. このDataObjectには、2つのテキストと1つのDataObjectListが含まれます。
  3. DataObjectListには、5つのDataObjectが含まれます。
  4. それぞれのDataObjectには2つのテキストが含まれます。

現在のDataObjectの指定が機能している例はまだ示していませんが、表に列を追加した後で、現在のDataObjectの指定を使用する必要が生じます。 TableBeanの各列は、索引付けされた子によって表されます。 addIndexedChild()を3回コールすると、表に3つの列が作成されます。 これらの各列は、行ごとに子要素をスタンプとして一度使用することによってレンダリングされます。

異なる方法でレンダリングされる各要素を行ごとに取得する点が特長です。 これは、現在のDataObjectの指定を使用することで可能になります。 表で最初の行をレンダリングする際、RenderingContext.getCurrentDataObject()によって、tableDataDataObjectListの最初のDataObjectが返されます。 2行目のレンダリング時には、getCurrentDataObject()は2番目のDataObjectを返します。 以下同じ処理が繰り返されます。

ここで表に2つの列を追加します。1つは通常のテキスト列、もう1つは赤色の列です。 (この例では、StyleSheetBeanで提供されるスタイルの1つである、ビルトインのOraErrorText CSSスタイルを使用しています。)

public class DataDemo
{
  // ...
  static public UINode getPage()
  {
    // ... old code ...
  
    // And now, let's create the table
    TableBean table = new TableBean();
    table.setTableDataBinding(...);

    // Create the first column
    StyledTextBean firstColumn = new StyledTextBean();
    // Use _TEXT_KEY for the data of the first column
    firstColumn.setTextBinding(_TEXT_KEY);
    // Add it to the table
    table.addIndexedChild(firstColumn);

    // Create the second column, using _TEXT_2_KEY
    StyledTextBean secondColumn = new StyledTextBean();
    secondColumn.setStyleClass("OraErrorText");
    secondColumn.setTextBinding(_TEXT_2_KEY);
    table.addIndexedChild(secondColumn);
  
    // ...
  }
 
  // ...
}

これで終了です。 ここで、このレンダリング方法について説明します。 表では、外枠や背景の設定など多くの処理が実行されます。 その後で最初のセルがレンダリングされます。

行0、列0については、表は最初のStyledTextBeanを使用します。 RenderingContext.getCurrentDataObject()によって、DataObjectListの最初のDataObjectが返されます。これには、「Row 0」および「Column 2, row 0」のエントリがあります。 StyledTextBeanによって、テキストであるDataBoundValueが要求されます。 現在のDataObjectが取得され、_TEXT_KEYが要求されます。_TEXT_KEYは「Row 0」を返し、そのセルにレンダリングされます。

次に、行0、列1が処理されます。同じ、現在指定されているDataObjectが有効ですが、もう1つのStyledTextBeanがレンダリングを行います。 これは_TEXT_2_KEYを要求し、「Column 2, row 0」をレンダリングします。

次は行1です。最初のStyledTextBeanに戻りますが、ここではすでにDataObjectListの次のDataObjectに移動しています。 そのため、RenderingContext.getCurrentDataObject()は異なるDataObjectを返します。 この行の最初の列には「Row 1」、2番目の列には「Column 2, row 1」が入ります。

表全体についてこの処理が繰り返されます。 これは単純な例です。列ヘッダーや行ヘッダー、あるいは表で実行可能な多数の処理は追加していません。また、編集不可の普通のテキストのみを使用しています。 ただし、これらはすべて同じ方法で機能します。 表の行ごとにレンダリング方法を変える場合、現在指定されているDataObjectに、その変更内容を示す情報が指定されている必要があります。

たとえば、各行にリンクを指定し、それぞれの宛先が異なるとします。 この場合は簡単です。 LinkBeanを使用して、destination属性を現在指定されているDataObjectにデータ・バインドし、各行の宛先を含むようにDataObjectListを更新します。 UIX Componentsでは、すべてについてデータ・バインドが可能なため、各行が異なるようにできます。

JavaBeans、マップおよびリストの適用: BeanAdapterUtils

多くの開発者にとって、JavaBeans、マップ、リストおよび配列がDataObjectまたはDataObjectListに変換されるメカニズムは依然として理解しにくいでしょう。 しかし、詳細を理解するのは有用だと考える開発者もいるでしょう。 まず、いくつかのAPIは、特にJava Beansで、DataObjectまたはDataObjectListを明示的に受け入れます。 第2に、アダプタをカスタマイズして、機能を追加したり、パフォーマンスを改善したりする場合があります。 どちらの場合も、oracle.cabo.ui.data.bean.BeanAdapterUtilsを使用する必要があります。

任意のオブジェクト(Bean、MapまたはDataObjectそのもの)をDataObjectに変換するには、BeanAdapterUtils.getAdapter()メソッドの1つを使用します。ほとんどの場合、次のとおりです。

  public static DataObject getAdapter(RenderingContext context,
                                      java.lang.Object instance)

配列またはListDataObjectListに変換するには、次のメソッドを使用します。

  public static DataObjectList getAdapterList(RenderingContext context,
                                              java.lang.Object listInstance)

JavaBeanアダプタのパフォーマンスの改善

パフォーマンスを改善する前に、問題があることを確認します。 アダプタ・レイヤーは軽量でパフォーマンスが良好なので、ほとんどのアプリケーションではコードの一部の最適化を問題視する必要がありません。

まずは、必要になるたびにアダプタ・オブジェクトを作成することを避けます。 UIXで自動的にアダプタを作成するのではなく、手動で作成してその結果をキャッシュします。 次に例を示します。

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    return _sAdapter;
  }

  static private LinkDataBean _sInstance =
       new LinkDataBean("http://www.oracle.com",
                        "Shameless bean promotion!");

  // Create a static adapter:
  static private DataObject _sAdapter;
  static
  {
    try
    {
       _sAdapter = BeanAdapterUtils.getAdapter(_sInstance);
    }
    catch (InstantiationException ie) { }
    catch (IllegalAccessException iae) { }
  }
}

これは、Beanの存続期間が長い場合のみ有効です。 Beanが1つのリクエストのみで使用されている場合、アダプタのキャッシュはそれほど有効ではありません。ただしそれがセッション・レベルまたはアプリケーション・レベルのBeanの場合、これはわずかながら簡単なパフォーマンス向上につながります。

次に、独自のDataObjectアダプタを記述して、イントロスペクションの本質的なパフォーマンスの問題に取り組みます。 または、より効果的な方法として、アダプタを自動的に記述できます。 UIXには、コンパイル済JavaBeansを取得しDataObjectアダプタを自動的に記述する、BuildBeanDOAdapterというJavaベースのツールが組み込まれています。 コマンドラインで次のように入力します。

  java oracle.cabo.ui.tools.BuildBeanDOAdapter yourpackage.LinkDataBean

ここで生成されるコードすべては列記しませんが、次に注目すべき部分を示します。selectValue()コードが例のLinkDataBeanに対して生成されています。

  public Object selectValue(RenderingContext context, Object select)
  {
    LinkDataBean instance = _instance;
    if (instance == null)
      return null;

    try
    {
      if ("url".equals(select))
        return instance.getUrl();
      if ("text".equals(select))
        return instance.getText();
    }
    catch (Exception e)
    {
      context.getErrorLog().logError(e);
    }
    return null;
  }

これはイントロスペクションよりもはるかに速い動作です。 最新のベンチマーク(Windows 2000上のJava 1.3.0)では、20から30倍速いとされています。 ただし、数百ものプロパティがある大規模なJavaBeansでは、String.equals()に対するこれらのコールは重大な問題となり、この最適化が実際にはパフォーマンスにとって有害となる可能性があります。 これらのケースのため、アダプタ構築ツールは-fastコマンドライン・オプションをサポートしています。 結果として生成されたクラスはStringハッシュコードを効果的に使用して、selectValue()のスピードを基本イントロスペクションよりも速いレベルまで再び引き上げます。 どちらの方法でアダプタ・クラスを構築しても、これらのアダプタは、booleanおよびintの戻り値をBooleanおよびIntegerのオブジェクトに変換するような一般的なイントロスペクションよりもはるかにスムーズでもあります。

これらのアダプタ・クラスを一度作成すると、データ・バインド・コードを変更してこれらのアダプタを明示的に作成できます。

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    LinkDataBean  bean = ...;
    return new LinkDataBeanDataObject(bean);
  }
}

ただし、ここまでのすべての手順を確実にこなしていくことを確認するのは冗長な(エラーも発生しやすい)作業です。 代替案としてアダプタを、静的なregisterAdapter()コールによってBeanAdapterUtilsに直接登録できます。 このコールを初期化コードに追加すると、残りのコードは以前のようにBeanインスタンスを続けて返すことができます。

public class DataDemo
{
  static public Object getLinkBean(
    RenderingContext context,
    String           namespace,
    String           name)
  {
    // Under the covers, UIX will use LinkDataBeanDataObject to
    // adapt this bean, but you don't need to know that here 
    return new LinkDataBean(...);
  }

  // Register the adapter once
  static
  {
    LinkDataBeanDataObject.registerAdapter();
  }
}

疑似JavaBeansのサポート

UIXでは、JavaBeans仕様のすべての側面にデータが完全に依存している必要はありませんが、完全な方がより好ましい状態です。 次のような状況はすべて自動的に処理されます。

  1. クラスが、引数を持たないコンストラクタや、publicコンストラクタさえまったく持つ必要がない。

  2. クラスが、シリアライズ可能である必要がない。

  3. クラスが、setメソッドを持つ必要がない(少なくともデータ・バインドに関してはgetメソッドのみ留意される)。

  4. メソッドのみではなく、アクセス可能なpublicフィールドもサポートされるため、LinkDataBeanを次のように記述することも可能です。
    public class LinkDataBean
    {
      public LinkDataBean(String url, String text)
      {
        this.url = url;
        this.text = text;
      }
    
      public String url;
      public String text;
    }

    これは通常、独自のJavaBeansではよい設計と言えませんが、特別なアダプタがなくてもjava.awt.Pointのようなレガシー・クラスをサポートできることを意味しています。

  5. JavaBeansではメソッドのみでなく、クラス自体がpublicになることが必要です。 よい設計の原則でこれらのクラスをpublicにしないように推奨される場合があるため、この制限事項には少々問題があります。 UIXの内部変換で次の代替方法をサポートします。

data:の変換: 式構文からEL構文

UIXリリース2.2以上(Oracle9i JDeveloper 9.0.5以上に付属)では、EL(式言語)データ・バインドがサポートされます。 これは、現在ではUIXでのデータ・バインドの推奨方法です。 このセクションでは、UIXの以前のバージョンで使用されているdata:構文をEL構文に変換する方法を説明します。

注意: UIXでは古いdata:構文もサポートされていますが、ELの方がより強力です。また、ELは、Oracle Application Development Framework(ADF)およびOracle9i JDeveloperでの標準のデータ・バインド・メカニズムです。 このトピックの最初に示したとおり、このトピックの内容は基本的には以前のバージョンと同じですが、データ・バインド構文がELに置き換えられています。

変換の基本的な手順

既存のUIX XMLページの古い構文を新しい構文に変換する基本的な手順を次に示します。

  1. EL構文を使用することを指定します。 これは、アプリケーション・レベルまたはページごとにデフォルトとして設定できます。

  2. UIX XMLページの<page>要素から、dataネームスペースへの参照を削除します。 すなわち、xmlns:data="http://xmlns.oracle.com/uix/uiを削除します。

  3. 個々の要素の古いdata:構文を新しいEL構文に変換します。 正式な構文は、UIX 2.2のリリース・ノートを参照してください。 次のセクションの例も参照してください。

古い構文を新しい構文に変換する例

このセクションでは、変換の正式な構文を説明するかわりに(この説明は複雑になるため)、古い構文を新しい構文に変換する例を多数示します。 既存のUIX XMLコードに類似した例を検索し、 その例に示されている新しい構文を参照して変換できます。 これらの例は、UIXバンドルに含まれているショッピング・カート・デモからの抜粋です。

  1.  
    <!-- Old syntax: -->
      <header data:text="ToDoList@text">
    
    <!-- New syntax: -->
      <header text="${text.ToDoList}">
  2.  
    <!-- Old syntax: -->
       <contentContainer width="30%" data:text="NewToPurchasing">
    
    <!-- New syntax: -->
       <contentContainer width="30%" text="${uix.current.NewToPurchasing}">
  3.  
    <!-- Old syntax: -->
      <link data:text="BrowseCategories@text"
        ctrl:destination="BrowseCats"
        ctrl:event="BrowseCatalog" />
    
    <!-- New syntax: -->
      <link text="${text.BrowseCategories}"
          destination="${ctrl:destination(uix, 'BrowseCats','BrowseCatalog')}" />
  4.  
    <!-- Old syntax: -->
      <link data:text="More@text"
            ctrl:destination="OrderHistory" />
    
    <!-- New syntax: -->
      <link text="${text.More}"
            destination="${ctrl:pageUrl(uix,'OrderHistory')}"/>
  5.  
    <!-- Old syntax: -->
      <link text="text"
            ctrl:event="Order" />
    
    <!-- New syntax: -->
      <link text="text"
            destination="${ctrl:eventUrl(uix,'Order')}" />
  6.  
    <!-- Old syntax: -->
      <frame ctrl:source="panel" />
    
    <!-- New syntax: -->
      <frame source="${ctrl:pageUrl(uix,'panel')}" />
    
  7.  
    <!-- Old syntax: -->
       <contents data:childData="link@builtInLinks">
          <link data:text="linkText">
            <boundAttribute name="destination">
              <ctrl:pageURL event="BrowseCatalog"
                            name="BrowseCats">
                <ctrl:parameters>
                  <ctrl:parameter key="ID"
                            data:value="linkID"/>
                </ctrl:parameters>
              </ctrl:pageURL>
            </boundAttribute>
          </link>
        </contents>
    
    <!-- New syntax: -->
        <contents childData="${builtInLinks.link}">
          <link text="${uix.current.linkText}">
            <boundAttribute name="destination">
              <ctrl:pageURL event="BrowseCatalog"
                            name="BrowseCats">
                <ctrl:parameters>
                  <ctrl:parameter key="ID"
                            value="${uix.current.linkID}"/>
                </ctrl:parameters>
              </ctrl:pageURL>
            </boundAttribute>
          </link>
        </contents>
  8.  
    <!-- Old syntax: -->
      <boundAttribute name="text">
        <messageFormat data:format="WelcomeMask@text">
          <dataObject select="username" source="loginData"/>
        </messageFormat>
      </boundAttribute>
    
    <!-- New syntax: -->
      <boundAttribute name="text">
        <messageFormat format="${text.WelcomeMask}">
          <dataObject select="username" source="loginData"/>
        </messageFormat>
      </boundAttribute>
  9.  
    <!-- Old syntax: -->
      <tableLayout>
        <contents>
          <messageStyledText data:prompt="NumberOfHits@text"
                             data:text="hits@counter"/>
          <messageStyledText data:prompt="UserSessions@text"
                             data:text="sessions@counter"/>
          <messageStyledText data:prompt="Since@text" >
            <boundAttribute name="text">
              <dateFormat timeStyle="short"
                           dateStyle="full">
                <dataObject select="date" source="counter"/>
              </dateFormat>
            </boundAttribute>
          </messageStyledText>
        </contents>
      </tableLayout>
    
    <!-- New syntax: -->
      <tableLayout>
        <contents>
          <messageStyledText prompt="${text.NumberOfHits}"
                             text="${counter.hits}"/>
          <messageStyledText prompt="${text.UserSessions}"
                             text="${counter.sessions}"/>
          <messageStyledText prompt="${text.Since}" >
            <boundAttribute name="text">
              <dateFormat timeStyle="short"
                          dateStyle="full">
                <dataObject select="date" source="counter"/>
              </dateFormat>
            </boundAttribute>
          </messageStyledText>
        </contents>
      </tableLayout>
  10.  
    <!-- Old syntax: -->
      <table name="OrderHistory" width="60%">
        <columnHeaderData>
          <t data:value="Order@text" />
          <t data:value="Created@text" />
          <t data:value="Status@text" />
        </columnHeaderData>
        <tableData>
          <!-- In a real application, the values for "created" would -->
          <!-- be generated on the server and properly formatted per -->
          <!-- locale. Literal values are given to simplify the example. -->
          <rows order="1234" created="May 5 2000"
                data:status="Submitted@text" />
          <rows order="2312" created="April 13 2000"
                data:status="Approved@text" />
          <rows order="2311" created="February 27 2000"
                data:status="Approved@text" />
        </tableData>
        <columnHeaderStamp>
          <text data:text="value"/>
        </columnHeaderStamp>
        <contents>
          <styledText data:text="order" />
          <styledText data:text="created" />
          <styledText data:text="status" />
        </contents>
      <table>
    
    <!-- New syntax: -->
      <table name="OrderHistory" width="60%">
        <columnHeaderData>
          <t value="${text.Order}"/>
          <t value="${text.Created}"/>
          <t value="${text.Status}"/>
        </columnHeaderData>
          <tableData>
          <!-- In a real application, the values for "created" would -->
          <!-- be generated on the server and properly formatted per -->
          <!-- locale. Literal values are given to simplify the example. -->
            <rows order="1234" created="May 5 2000"
                  status="${text.Submitted}" />
            <rows order="2312" created="April 13 2000"
                  status="${text.Approved}" />
            <rows order="2311" created="February 27 2000"
                  status="${text.Approved}" />
          </tableData>
          <columnHeaderStamp>
           <text text="${uix.current.value}"/>
          </columnHeaderStamp>
          <contents>
            <styledText text="${uix.current.order}" />
            <styledText text="${uix.current.created}" />
            <styledText text="${uix.current.status}" />
        </contents>
      </table>
  11.  
    <!-- Old syntax: -->
      <boundAttribute name="text">
        <messageFormat data:format="WelcomeMask@text">
          <dataObject select="username" source="loginData"/>
        </messageFormat>
      </boundAttribute>
    
    
    <!-- New syntax: -->
      <boundAttribute name="text">
        <messageFormat format="${text.WelcomeMask}">
          <dataObject select="username" source="loginData"/>
        </messageFormat>
      </boundAttribute>
  12.  
    <!-- Old syntax: -->
      <bulletedList>
        <contents>
          <link data:text="Shop@commonText"
                ctrl:destination="BrowseCats"/>
          <link data:text="OrderHistory@commonText"
                ctrl:destination="OrderHistory"/>
          <link data:text="ShoppingCart@commonText"
                ctrl:destination="ShopCart"/>
          <link data:text="Preferences@commonText"
                ctrl:destination="Preferences"/>
        </contents>
      </bulletedList>
    
    <!-- New syntax: -->
      <bulletedList>
        <contents>
          <link text="${commonText.Shop}"
                destination="${ctrl:pageUrl(uix, 'BrowseCats')}"/>
          <link text="${commonText.OrderHistory}"
                destination="${ctrl:pageUrl(uix, 'OrderHistory')}"/>
          <link text="${commonText.ShoppingCart}"
                destination="${ctrl:pageUrl(uix, 'ShopCart')}"/>
          <link text="${commonText.Preferences}"
                destination="${ctrl:pageUrl(uix, 'Preferences')}"/>
        </contents>
      </bulletedList>
  13.  
    <!-- Old syntax: -->
      <text data:text="street@address@userData"/>
    
    <!-- New syntax: -->
      <text text="${userData.address.street}"/>
    
    
  14.  
    <!-- Old syntax: -->
      <text data:text="name@(selectedItem)@userData"/>
    
    <!-- New syntax: -->
      <text text="${userData[uix.current.selectedItem].name}"/>
    
    
  15.  
    <!-- Old syntax: -->
      <text data:text="name@(person@selectedItem)@userData"/>
    
    <!-- New syntax: -->
      <text text="${userData[selectedItem.person].name}"/>
    
    
  16.  
    <!-- Old syntax: -->
      <text data:text="name@prefix:userData"/>
    
    <!-- New syntax: -->
      <text text="${prefix:data().userData.name}"/>
    
    
  17.  
    <!-- Old syntax: -->
      <submitButton text="Foo" ctrl:event="Bar"/>
    
    <!-- New syntax: -->
      <submitButton text="Foo" event="Bar"/>
    
    
  18.  
    <!-- Old syntax: -->
      <formValue ctrl:event="Bar"/>
    
    <!-- New syntax: -->
      <formValue name="${ui:encodeParameter(uix,'event')}" value="Bar"/>
    
    
  19.  
    <!-- Old syntax: -->
      <include ctrl:node="Home"/>
    
    <!-- New syntax: -->
      <include node="${ctrl:parsePage(uix,'Home')}"/>
    
    
  20.  
    <!-- Old syntax: -->
      <text text="Untitled" data:text="text@userdata"/>
    
    <!-- New syntax: -->
      <text text="${ui:defaulting(userdata.text,'Untitled')}"/>