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

4. ADF UIXでのコントローラの使用

このトピックでは、Webページのグループ化およびページ・フローの制御を可能にするOracle ADF UIXサーブレットの概要を示します。 UIXサーブレットを使用してイベントを処理し、ページをブラウザに戻す方法を説明します。 つまり、UIXサーブレットを使用すると、UIXのすべての要素を使用してアプリケーションを作成することができます。

注意: Oracle ADF UIXの以前のリリースには、J2EE Webアプリケーションの構築用に「UIX Controller」というUIXサーブレット・テクノロジが採用され、推奨されていました。 UIX Controllerは、Model-View-Controllerデザイン・パターンに基づいて、完全な機能を備えたUIXアプリケーションを構築するためのページ・フロー・エンジンとして推奨されていました。 JDeveloper 10gでは、Strutsを優先するため、ページ・フロー・エンジンとしてのUIXサーブレットの役割は重視されていません。 イベント処理、部分ページ・ユーティリティなどのADF UIXサーブレットの他の機能は、Strutsに同等のものが存在しないためこれまでどおり推奨しています。このヘルプでは引き続き、WebアプリケーションのコントローラとしてのUIXサーブレットの役割について説明します。 今後のバージョンには、UIXテクノロジの中核機能として継続して使用されるUIXサーブレットの機能に関する、更新されたドキュメントが付属する予定です。

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

概要

UIXサーブレットとは何でしょうか。 UIXサーブレットは、Model-View-Controller(MVC)デザイン・パターンに基づくWebアプリケーション・テクノロジです。 UIXビュー・レイヤーであるUIX Componentsテクノロジについてはすでに紹介しました。 UIXでモデル・レイヤーのために用意されている汎用抽象化機能についても説明しました。これはデータ・バインディングと呼ばれ、任意のモデル・コードをUIX XMLに連結できます。 UIXサーブレットはコントローラ・レイヤーです。ここでビューのイベントがモデルを変更し、モデルのデータがビュー間のフローを制御します。 UIXサーブレットでは、個々のWebページをグループ化してアプリケーションを形成し、プログラマがページ・フローを制御するためのインタフェースを提供します。

図4-1: Webアプリケーション

単純なアプリケーションのダイアグラム

図4-1: Webアプリケーションは、単純なアプリケ―ションの概要図です。 ここでは、5つのページからWebアプリケーションが形成されています。 矢印はページ間のリンクを表しています。 これはHTMLハイパーリンクを使用して簡単に実行できます。では、UIXサーブレットはどこに使用するのでしょうか。 多くの場合、ユーザーがリンクをクリックして表示されるページは、ユーザーが入力したデータ、またはサーバーの状態によって変わります。 たとえば、ユーザーが入力したデータが無効な場合は、エラー・ページを表示する必要があります。 次のステップに進む前に、ユーザーの入力データの処理が必要な場合もあります。 たとえば、ショッピング・カート・アプリケーションでは、ユーザーが次に進む前に、入力データをサーバーに保存する必要があります。 ここでUIXサーブレットが必要になります(図4-2: UIXサーブレットのページ・フローを参照)。 UIXサーブレットは、サーバーに常駐して各リクエストを調べます。 ユーザーが入力したデータ、サーバー側の状態(データベースなど)、および開発者提供のハンドラから返される結果に基づいて、UIXサーブレットで(処理中のデータを操作して)次に表示するページが決定されます。

図4-2: UIXサーブレットのページ・フロー

UIXサーブレットのページ・フローのダイアグラム

UIXサーブレットはJavaで作成され、Javaサーブレットで動作するように設計されています。 独自のスタンドアロンUIXServlet、またはその他のサーブレット(JSPなど)で使用できます。

各HTMLページを作成するために使用するテクノロジは、UIXサーブレットとは関係ありません。 UIXサーブレットでは、JSPを使用してHTMLを作成できます。または、素のHTMLファイルも使用できます。 UIXサーブレットでは、HTML以外のページ(GIFイメージなど)も使用できます。 ただし、UIXサーブレットは、UIX ComponentsおよびUIX XMLをサポートし、統合できるように特別に設計されています。 UIXファイルの解析やキャッシュを自動的に実行したり、UIX Componentsをコールして、UIX ComponentsやUIX XMLの文書をHTMLに変換できます。 このトピックでは、UIXサーブレットをUIX Componentsと組み合せて使用し、UIXページをレンダリングします。

単純なUIXサーブレットおよびUIX XMLページ

次に、単純なUIXサーブレットおよびUIX XMLページを示します。 UIXサーブレットのすべてのページは、page要素で開始する必要があります(この要素はUIXサーブレット・ネームスペースにあることに注意してください)。 このpage要素にはcontent子要素があります。 UIX ComponentsおよびUIX XMLの要素は、UIXサーブレットのこのcontent要素に子として追加されます(デフォルトのネームスペースがUIXサーブレットからUIX Componentsに変わることに注意してください)。

<page xmlns="http://xmlns.oracle.com/uix/controller"
      expressionLanguage="el">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="UIX Components Header Bean">
  <contents>
   <stackLayout>
    <contents>
      UIX Components Stuff
     <link text="This is a link" destination="www.cnn.com"/>
    </contents>
   </stackLayout>
  </contents>
  </header>
</content>
</page>

補足として、データ・バインディングを含む例も次に示します。

<page xmlns="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="UIX Components Header Bean">
  <contents>
   <dataScope>
    <contents>
     <link text="${uix.data.dat1.text1}" destination="${uix.data.dat1.dest1}"/>
     <link text="${uix.data.dat1.text2}" destination="${uix.data.dat1.dest2}"/>
    </contents>
    <provider>
     <data name="dat1">
      <inline text1="Oracle" dest1="http://www.oracle.com"
              text2="Cnn" dest2="http://cnn.com" />
     </data>
    </provider>
   </dataScope>
  </contents>
  </header>
</content>
</page>

厳密には、UIXサーブレットのページはpage要素で始まる必要はありません。UIX Componentsの要素で開始することもできます。 ただし、UIXサーブレットのページ・フロー・システムを最大限に活用するには、page要素を使用する必要があります。

例の実行

UIXでこれらの例を実行する場合は、filename.uixなどのファイルを作成し、コンテンツを追加して、サーブレット・エンジンの所定のサブフォルダにあるWebアプリケーション・ディレクトリ(demosなど)にそのファイルを入れます。 たとえば、サーブレット・エンジンがOC4Jで /servletEngine/demos/somepath/filename.uixにファイルを入れた場合、ブラウザでhttp://myHostName:8888/somepath/filename.uixにアクセスするとファイルを表示できます。

UIXサーブレットのイベントの概要

従来、静的HTMLページのリンクは、ハードコードされたHTMLリンクに対して設定されていました。 つまりページ・フローが静的であり、1つのリンクをクリックすると常に同一のHTMLページが表示されます。 しかし、ほとんどのWebアプリケーションでは、リンクがアクティブになった際、またはフォームが送信された際に、表示するページを動的に選択する必要があります。 この選択は、ユーザーが送信したデータ、またはサーバーの状態に基づいて行われます。 この選択時に、サーバーの状態が変わる場合があります。

UIXサーブレットを使用すると、特殊な処理(前述を参照)を必要とするリンク(またはフォーム送信)を簡単に指定できます。 すべてのクライアント・リクエストはUIXサーブレットによって受信され、デフォルトで、クライアント・リクエストで指定されたページがUIXサーブレットによって提供されます。 ただし、UIXサーブレットでは、イベントと呼ばれる特殊な信号を認識します。 リンク(またはフォーム送信)は、イベントを起動するようにエンコードできます。 ユーザーがそのようなリンクをクリックすると、UIXサーブレットでは、アプリケーション固有のコードを実行してイベントを処理し(処理中のサーバー側の状態も変更され)、結果を使用して、イベントに応答してレンダリングするページを決定します。

イベントは、アプリケーションがUIXサーブレットと対話して、ユーザーの入力に基づいて状態を変更するための手段です。 UIXサーブレット・フレームワークでのサーバー側の状態の変更はすべて、イベント・ハンドラによって実行されます。 UIXサーブレットのイベントはサーバー側で発生します。クライアント側のJavaScriptイベントとは異なりますので注意してください。

UIXサーブレットのイベントには名前と0以上のパラメータが付いています。 イベント名は文字列です。パラメータは単純な名前と値のペア(通常の形式のパラメータと同様)で、文字列名と文字列値を関連付けます。 UIXサーブレット・イベントをカプセル化するJavaインタフェースはPageEventです。 イベント名は(このクラスの)getName()メソッドによって返され、パラメータ値はgetParameter(String key)メソッド(keyはパラメータ名)をコールして取得できます。 次のセクションでは、イベントの生成方法について説明します。

イベントの生成

UIXサーブレットのイベントのURLは、eventフォーム・パラメータを使用したフォーム送信に似ています。 通常、イベントを構築するには、次のとおりsubmitButtonを使用します。

<!-- This is pageName.uix ---->
<page xmlns="http://xmlns.oracle.com/uix/controller"
     xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="Enter Your Name">
  <contents>
   <form name="form1">
    <contents>
     <messageTextInput name="txt1" prompt="Enter Name" text="YourName" />
     <submitButton event="StoreName" text="Submit" />
    </contents>
   </form>
  </contents>
  </header>
</content>
</page>

上のUIX XMLで作成されるページには、2つのフォーム要素を持つフォームが使用されています。 1つはmessageTextInput要素で、もう1つはsubmitButtonです。 form要素にdestination属性がないことに注意してください。 宛先の設定がない場合、UIXサーブレットでは現在のページが自動的に使用されます。 ユーザーが送信ボタンを押すと、ブラウザによって次のURLが生成されます。

http://hostname:port/pageName?event=StoreName&txt1=YourName

UIXサーブレットではこのリクエストを、起動されたイベントと解釈します。 イベントの名前はStoreNameで、名前がtxt1、値がYourName(ユーザーがテキスト・フィールドに入力した任意の値)のパラメータが1つあります。 生成されたイベントのサーバーでの処理方法については後述しますが、その前に、イベントを生成するその他の方法を説明します。

イベントを起動するためにsubmitButton要素を使用する必要はありません。 URLの"destination"属性をサポートするすべてのUIX要素では、UIXサーブレットのイベントもサポートされます。 次に例を示します。

<link text="Trigger Event Foo"
      destination="${ctrl:eventUrl(uix,'Foo')}" />

上のリンクをクリックすると、名前がFooであるイベントが起動されます。 eventUrl関数については後述します。

次の例のように、イベントを直接エンコードする可能性も考えられます。 これはお薦めしません。ページ・イベントの抽象化が損なわれるため、一部のエンコーディング・メカニズムが実行されないからです。

<!-- ******* Do not try to fire events like this!!  ********* -->
<formValue name="event" value="StoreName" />

かわりに、次の形式を使用します(encodeParameter関数については後のセクションを参照)。

<formValue name="${ui:encodeParameter(uix,'event')}"
           value="StoreName" />

UIX Componentsの特定の要素によってイベントが生成されることに注意してください。たとえば、NavigationBarBeanではgotoという名前のイベント、TotalRowBeanではtotalイベント、SortableHeaderBeanではsortイベント、そしてAddTableRowBeanではaddRowsイベントが生成されます。 これらはUIXサーブレットを使用せずに処理できますが、UIXサーブレット・フレームワークにこれらすべてを適切に統合できます。

イベントの生成方法を理解したところで、UIXサーブレットでのイベントの処理方法に進みます。 まずUIXのイベント・ハンドラの登録について説明し、次に、Javaでのイベント・ハンドラの作成について説明します。

イベント・ハンドラの登録

イベント・ハンドラはイベントを処理するコード部分です。 UIXサーブレットでは、特定のイベントを処理するためにどのイベント・ハンドラをコールするかを認識している必要があります。 このために、各イベント・ハンドラをUIXサーブレットに登録します。 次のUIX XMLファイルは、UIXでの登録例を示しています。

<!-- ********* This is FirstPage.uix ********-->
<page xmlns="http://xmlns.oracle.com/uix/controller"
     xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="Enter Your Name">
  <contents>
   <form name="form1">
    <contents>
     <messageTextInput name="txt1" prompt="Enter Name" text="YourName" />
     <submitButton event="StoreName" text="Submit" />
    </contents>
   </form>
  </contents>
 </header>
</content>
<handlers>
 <event name="StoreName">
  <method class="MyClass" method="handleStoreNameEvent" />
 </event>
</handlers>
</page>

このコードでは、UIXサーブレットのpage要素の新しい子要素、handlers要素が使用されています。 このページのすべてのハンドラは、この要素の子としてリストされます。 現在、UIXサーブレットのハンドラは、イベント・ハンドラの1種類のみです。

イベント・ハンドラは、UIXサーブレットのevent要素を使用して定義されます。 event要素にはname属性があり、この属性にはハンドラで処理できるUIXサーブレット・イベントを指定します。 上の例では、このページで発生するすべてのStoreNameイベントを処理するようにハンドラを登録しています。

event要素の内部に、イベント・ハンドラ要素を追加してハンドラ・コードを指定します。 そのような要素の1つが、UIXサーブレットのmethod要素です。 この要素では、完全修飾されたJavaクラス名とメソッド名を使用してこのメソッドをコールし(Java Reflection APIを使用)、それぞれのイベントを処理します。 上の例では、メソッドMyClass.handleStoreNameEvent(...)は、このページで発生するすべてのStoreNameイベントを処理するためにコールされます。

イベント・ハンドラ・コードを参照するもう1つの方法は、UIXサーブレットのinstance要素を使用することです。 次の例では、メソッドMyClass.getStoreNameEventHandlerを使用して、(イベントを直接処理するのではなく)イベント・ハンドラを生成しています。 このメソッドによって返されるイベント・ハンドラが、実際にイベントを処理するために使用されます(詳細は次のセクションを参照してください)。 method属性は省略される場合があります。この場合、UIXサーブレットではデフォルトのメソッド名sharedInstanceを使用するか、最終的にはデフォルトの(引数なし)コンストラクタをコールします。

<handlers>
<event name="StoreName">
 <instance class="MyClass" method="getStoreNameEventHandler" />
</event>
</handlers>

UIXサーブレットのnull要素は、イベントは生成されるがイベント処理コードが記述されていないことを示す場合に便利です。 UIXサーブレットで、受信したイベントのイベント・ハンドラを検出できない場合、UIXサーブレットではUnhandledEventExceptionをスローして、開発者にエラーを警告します。 このような場合、nullイベント・ハンドラを使用して、サーバー側で処理することなくユーザーは該当ページを見つけることができます。 次の例のように使用します。

<handlers>
<event name="someEvent">
 <null/>
</event>
</handlers>

UIXは完全に拡張可能です。また、UIX XMLに新しい要素を追加して、前述したもの以外のイベント・ハンドラの登録をサポートすることもできます。 UIXの拡張の詳細は、「ADF UIXの拡張」を参照してください。

ここでは、イベント・ハンドラを登録するその他の方法について説明します。 次のイベント・ハンドラは"*"の下に登録されます。 このイベント・ハンドラは、明示的ハンドラを持たないイベント名で、イベントがページで起動されるたびにコールされます。 このタイプのイベント・ハンドラは、デフォルト・イベント・ハンドラとも呼ばれます。 次の例では、UIXサーブレットのnullハンドラがデフォルト・ハンドラとして登録されます。 対応するハンドラがないすべてのイベントが、nullによって処理(つまり無視)されます(UnhandledEventExceptionは隠されますが、コーディング・エラーもマスクされてしまいます)。

<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
 <event name="*">
  <null/>
 </event>
</handlers>
</page>

イベント・ハンドラは、イベント名nullの下に登録することもできます。 このキーワードは特別であり、イベント・ハンドラがnullイベント・ハンドラとして登録されます(UIXサーブレットのnull要素とは異なります)。 nullイベント・ハンドラがコールされるのは、ページでイベントが起動されなかった(つまり、ページのURLにイベント信号がエンコードされなかった)場合です。

<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
 <event name="null">
  ...
 </event>
</handlers>
</page>

イベントが生成されるとき、イベントを起動したコンポーネントは通常、sourceイベント・パラメータで識別されます。 コンポーネントによってsourceパラメータを異なる方法で設定します。たとえば、tablename属性や、hideShowid属性(不一致についてはお詫びします)は、これらのコンポーネントで生成されたすべてのイベントのsourceパラメータとなります。 sourceパラメータの設定方法については各コンポーネントのドキュメントを参照してください。

特定のコンポーネントで生成されたイベントのイベント・ハンドラを、このsourceパラメータを使用して登録することが可能です。 次の例では、table1コンポーネントで生成されたgotoイベントを処理するように、ハンドラが登録されています。

<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
 <event name="goto" source="table1">
   <!-- handle the "goto" event generated by "table1" -->
   ...
 </event>
 <event name="goto">
   <!-- handle all other "goto" events -->
   ...
 </event>
</handlers>
</page>

前述の例が示すとおり、名前およびソースの両方で登録されたイベント・ハンドラは、名前のみで登録されたイベント・ハンドラより優先順位が高くなります。

次の例のように、複数のイベント・ハンドラを異なるイベント名の下に登録したり、同一のイベント・ハンドラで異なるイベントを処理するように登録できます。

<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
 <event name="goto" source="searchResults">
  <!-- This handler performs the record navigation for the 'searchResults'
        table -->
  <method class="MyClass" method="navigateResults" />
 </event>
 <event name="goto" source="navBar">
  <!-- This handler responds to the event generated by a navigationBar -->
  <method class="MyClass" method="navigatePageFlow" />
 </event>
 <event name="total">
  <method class="MyClass" method="submitHandler" />
 </event>
 <event name="submit">
  <method class="MyClass" method="submitHandler" />
 </event>
</handlers>
</page>

複数のソースにおける複数のイベントの単一イベント・ハンドラを、イベント名およびソースの空白区切りリストを使用して登録することが可能です。 次の例では、hgrid1およびhgrid2の2つのコンポーネントにおける4つのイベント(expand、focus、expandAllおよびcollapseAll)を処理するように、handleHGridEvent methodハンドラが登録されています。

<handlers>
<event name="expand focus expandAll collapseAll"
        source="hgrid1 hgrid2">
 <!-- Handle the HGrid events for 'hgrid1' and 'hgrid2'
            components  -->
 <method class="MyClass" method="handleHGridEvent" />
</event>
</handlers>

グローバルなイベント・ハンドラを登録することもできます。これは、どのページから起動されたかにかかわらず、指定の名前のイベントを処理します。 これらは特定のページに所属しないため、ページのUIX XMLから登録することができません。 かわりにJavaコードを使用して登録します(oracle.cabo.servlet.AbstractPageBrokerを参照してください)。 これらの登録メカニズムは、UIXサーブレットのJava APIを使用して、すべてUIX XMLの外側から使用できます。

イベント・ハンドラを登録する様々な方法を見てきました。次は、一般的なイベントの処理方法について説明します。 説明は次のセクションにあります。

イベントの処理

前のセクションでは、メソッドMyClass.handleStoreNameEvent(...)を、FirstPage.uixのイベント・ハンドラの例として紹介しました。 次に、このクラスのJavaソース・コードを示します。

import javax.servlet.http.HttpSession;
import oracle.cabo.servlet.Page;
import oracle.cabo.servlet.BajaContext;
import oracle.cabo.servlet.event.PageEvent;
import oracle.cabo.servlet.event.EventResult;

public class MyClass
{
  /** This is handleStoreNameEvent version 1 */
  public static EventResult handleStoreNameEvent(BajaContext context,
                                                 Page page,
                                                 PageEvent event)
  {
    String userName = event.getParameter("txt1");
    HttpSession session = context.getServletRequest().getSession(true);
    session.putValue("User Name", userName);
    Page nextPage = new Page("NextPage");
    return new EventResult(nextPage);
  }
}

最初に注意する必要があるのはメソッドのシグネチャです。 UIXサーブレットのmethod要素には次のシグネチャが必要です。

  public static EventResult methodName(BajaContext context,
                                       Page page,
                                       PageEvent event) throws Throwable;

contextは現在のBajaContextです(BajaContextオブジェクトでは、リクエストごとのアプリケーション状態のデータすべてをカプセル化し、サーブレット・ベースのアプリケーションを作成するために必要なグローバル・サービスへのアクセスを提供します)。pageはイベントが発生したページです(Pageオブジェクトについては後のセクションを参照)。eventでは、起動されたイベントをカプセル化します(例では、PageEventについてgetParameter(String)メソッドをコールして、イベント・パラメータへのアクセス方法を示しています)。

メソッドでは、EventResultを返す必要があります。これは、現在のPageFlowEngine(後のセクションを参照)によって使用され、次に表示するページが決定されます。 メソッドでjava.lang.Throwableをスローする場合があります。UIXサーブレットではThrowableを捕捉し、エラー・ページにリダイレクトします。

MyClass.handleStoreNameEventに戻ります。このメソッドは、ユーザーが入力した名前(txt1というテキスト入力フォーム要素)を取得することによって開始し、このユーザー名をHttpSessionに保存します。 その後で、次のPage(ページ・フロー内)をEventResultオブジェクトでラップし、それを返します。 現在のPageFlowEngineがこの結果オブジェクトを調べ、含まれているPageをレンダリングします(ページ・フロー・エンジンについては後述します)。

イベント・ハンドラでは、HttpServletRequestではなく、PageEventgetParameterメソッドを使用して、パラメータにアクセスします。 (HttpServletRequestは、getServletRequest()メソッドを使用して、現在のBajaContextから取得できます。) パラメータにアクセスする際は、PageEventを抽象オブジェクトを利用することで、UIXによるファイルのアップロードが正常に機能し、パラメータが適切に正しいキャラクタ・セットにデコードされることが保証されます。 また、PageEvent抽象オブジェクトによって、キーと値のペアの追加、削除、またはマージなど、パラメータの変換を1つの場所で集中的に実行できます。この際、イベント処理コードを編集する必要はありません。

UIXサーブレットのinstance要素の例を思い出してください。 この例では、メソッドMyClass.getStoreNameEventHandler()をコールして、EventHandlerを生成しています。 この(ページの解析時に1回のみコールされる)staticメソッドでは引数を使用できません。また、次のコード例と同じシグネチャがあります。

import oracle.cabo.servlet.event.EventHandler;

public class MyClass
{
  public static EventHandler getStoreNameEventHandler()
  {
   ...
  }
}

返されるEventHandlerは、イベントを処理するために使用されます。 また、EventHandlerインタフェースによって次のメソッドが公開されます(UIXサーブレットのmethodイベント・ハンドラのシグネチャと似ています)。

public interface EventHandler
{
  public EventResult handleEvent(
    BajaContext   context,
    Page          page,
    PageEvent     event) throws Throwable;
}

UIXサーブレットのinstanceハンドラの方が、対応するmethodイベント・ハンドラよりも若干速く実行される場合があります。これは、instanceハンドラがJava Reflection APIのイントロスペクション・コストの影響を受けないためです。 ただし、差はごくわずかであり、ほとんどのアプリケーションでは認識されません。

さらに複雑なイベント処理に進む前に、UIXサーブレットのPageオブジェクトについて説明する必要があります。 次のセクションで説明します。

UIXサーブレットのPageオブジェクト

UIXサーブレットでは、URIのようにリソースを識別するためにPageオブジェクトが使用されます。 各Pageには(getName()メソッドによって取得される)名前があり、アプリケーション内のUIXサーブレットのページを一意に識別します。 UIXサーブレットに送信されるすべてのリクエストが自動的にデコードされ、リクエストのソースを識別するPageオブジェクトが作成されます。

Pageにはテキスト・プロパティも設定できます。 これらのプロパティは、単純な文字列の名前と値のペアです。 プロパティの設定と取得は、setProperty(String key, String value)メソッドおよびgetProperty(String key)メソッドで行われます。 プロパティ値は必ずしも文字列である必要はありません。文字列としてエンコードできる値であれば、プロパティとして設定できます(PageのJava APIを参照)。

ページ・プロパティによる状態の保持

PageプロパティはURLに直接エンコードできます。そのURLのデコードによって生成される、対応するPageオブジェクトには、それらのプロパティがすでに設定されています。 つまり、プロパティの状態は、クライアント・ブラウザとの送受信の前後も維持され、ブックマーク済リンクとしても保存されます。 この機能により、ページ・プロパティを使用して、サーバー側の記憶域を使用せずにユーザー固有の状態を保持できます。ユーザーの状態はプロパティとしてURLに保存されます。 次の例では、イベントとページのプロパティを使用して、サーバー側の状態を使用せずにユーザーの情報を蓄積します。

import oracle.cabo.servlet.Page;
import oracle.cabo.servlet.BajaContext;
import oracle.cabo.servlet.event.PageEvent;
import oracle.cabo.servlet.event.EventResult;

public class MyClass
{
  /** This is handleStoreNameEvent version 2 */
  public static EventResult handleStoreNameEvent(BajaContext context,
                                                 Page page,
                                                 PageEvent event)
  {
    String userName = event.getParameter("txt1");
    Page nextPage = new Page("NextPage");
    nextPage.setProperty("UserName", userName);
    return new EventResult(nextPage);
  }

  public static EventResult handleStoreAgeEvent(BajaContext context,
                                                Page page,
                                                PageEvent event)
  {
    String userName = page.getProperty("UserName");
    String age = event.getParameter("age");
    Page nextPage = new Page("FinalPage");
    nextPage.setProperty("UserName", userName);
    nextPage.setProperty("Age", age);
    return new EventResult(nextPage);
  }
}

メソッドMyClass.handleStoreNameEvent(...)がイベント・ハンドラとしてFirstPage.uixで使用されたことに注意してください。 上のコードは、最初のhandleStoreNameEvent(...)メソッドとは少し異なります。 まず、新しいコードではHttpSessionが使用されていません。 このコードでは、状態がサーバー側に格納されません。 かわりに、ページ・プロパティを使用してURLに状態をエンコードします。

これは、ページ・フロー内の次のページを参照する新しいPageを作成することにより行われます。 UserNameプロパティは、このPageに設定され、PageEventResultとして返されます。

この新しいPageではNextPage.uixを参照します。form要素のdestination属性は(このUIX XMLファイルには)設定されていません。 このため、UIXサーブレットでは、UserNameプロパティが設定される現在のPageをデフォルトで参照します。 これによって、このフォームの送信時、宛先URLにはUserNameプロパティがエンコードされます。 また、このフォームを送信すると、StoreAgeイベントが起動され、MyClass.handleStoreAgeEvent(...)によって処理されます。

<!-- ********* This is NextPage.uix ******** -->
<page xmlns="http://xmlns.oracle.com/uix/controller"
     xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="Enter Your Data">
  <contents>
   <form name="form1" >
    <contents>
     <messageTextInput name="age" prompt="Enter your age" />
     <submitButton event="StoreAge" text="Submit" />
    </contents>
   </form>
  </contents>
  </header>
</content>
<handlers>
 <event name="StoreAge">
  <method class="MyClass" method="handleStoreAgeEvent" />
 </event>
</handlers>
</page>

ここで、MyClass.handleStoreAgeEvent(...)について考えてみます。 ユーザー名は、現在のページのプロパティから入手されます。 これは(Ageプロパティとともに)フローの次のページに追加され、そのまま継続します。 これは、ページ・プロパティを使用して、サーバー側のメモリーを使用せずにクライアントの状態を保持する例です。 1つの大きな短所は、この方法で保持できる状態はテキスト形式の状態のみであることです。 もう1つの短所は、プロパティの名前と値が暗号化されないため、機密情報(パスワードやクレジット・カード情報など)の格納に使用できないことです。 また、URLは長さが制限されています。Internet Explorer 5は各URLを約2000バイトまでサポートしていますが、他のブラウザはわずか255バイトしかサポートしていない場合があります。 このため、一度に使用できるページ・プロパティの数は厳しく制限されます。

上の例では、エンコードされたPage URLがフォーム送信の宛先として使用されています。 UIX XMLでは、その他の要素(リンクなど)の宛先としてPage URLを使用することが必要な場合があります。 この場合、次の例のようにctrl:pageUrl関数を使用します。

<page xmlns="http://xmlns.oracle.com/uix/controller"
     xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
 <ctrl:content xmlns="http://xmlns.oracle.com/uix/ui" >
  <link text="Go to the next page"
        destination="${ctrl:pageUrl(uix,'NextPage')}" />
 </ctrl:content>
</page>

同様に、次のコード例のように、要素のsource属性をPage URLに設定できます。

<frame source="${ctrl:pageUrl(uix,'Tree')}" name="TreeFrame" />

いくつかのページ・プロパティとページ・パラメータの組合せを含むイベントを設定する場合、<ctrl:pageURL> BoundValue要素を使用する必要があります。 次に例を示します。


<link text="Approve and go to next" >
 <boundAttribute name="destination">
  <ctrl:pageURL event="approve">
   <ctrl:properties>
    <ctrl:property key="uid" value="1234"/>
    <ctrl:property key="gid" value="643"/>
   </ctrl:properties>
   <ctrl:parameters>
    <ctrl:parameter key="nextStep=" value="shipping"/>
   </ctrl:parameters>
  </ctrl:pageURL>
 </boundAttribute>
</link>

Javaを使用して、データ・プロバイダ内にURLをエンコードすることもできます。

PageのURLエンコーディングは、PageEncoderを使用して取得できます。 現在のPageEncoderにアクセスするには、現在のBajaContextについてgetPageEncoder()をコールします。 次の例について考えてみます。

import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.data.DataObject;
import oracle.cabo.servlet.Page;
import oracle.cabo.servlet.BajaContext;
import oracle.cabo.servlet.ui.BajaRenderingContext;

public class MyDataProvider
{
  public static DataObject getProductIDEncoder(RenderingContext context,
                                               String namespace,
                                               String name)
  {
    return _DATA_OBJECT;
  }

  private static final DataObject _DATA_OBJECT = new DataObject()
  {
    public Object selectValue(RenderingContext context, Object key)
    {
      BajaContext bajaContext = BajaRenderingContext.getBajaContext(context);
      Page page = new Page("Products"); // refers to Products.uix
      if (key != null)
        page.setProperty("productID", key.toString());
      String encoding = bajaContext.getPageEncoder().encodePageURL(page);
      return encoding;
    }
  };
}

上のコードでは、UIX Componentsのデータ・プロバイダが実装されています。 その基本機能は、指定のkeyを特定のプロパティとしてPageにエンコードし、そのPageのエンコーディングを返すことです。 このメソッドでBajaRenderingContext.getBajaContext(...)をコールしていることに注意してください。 UIX Componentsを使用して構築したページをUIXサーブレット環境で実行している場合、このメソッドを使用して、現在のBajaContextを取得できます(同様に、BajaRenderingContext.getPage(...)を使用して現在のPageを取得できます)。

次のUIX XMLコードでは、前述のデータ・プロバイダを使用してリンクの宛先を求める方法を示します。 最初のリンクには、オラクル社内でのUIX Componentsの製品コードがエンコードされ、2番目のリンクにはJEWTの製品コードがエンコードされます。 この例の宛先ページ(Products.uix)は、Pageオブジェクトの正確な製品IDにアクセスでき、適切なデータをレンダリングします。

<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
 <link text="Goto UIX Components product page"
       destination="${uix.data.productID.768}" />
 <link text="Goto JEWT product page"
       destination="${uix.data.productID.927" />
</contents>
<provider>
 <data name="productID">
  <method class="MyDataProvider" method="getProductIDEncoder" />
 </data>
</provider>
</dataScope>

ページ状態による状態の保持

ページ・プロパティには状態を保存するための優れた面があります。 これはURL内部で直接指定されるため、サーバー上のどの状態を保持する必要もありません。 また、ユーザーが「戻る」および「進む(次)」ボタンをクリックするとそれに応じてURLも変わるため、それらのクリックについて懸念する必要もありません。

ただし、これらの利点は同時にページ・プロパティの制限事項でもあります。 前述したように、ページ・プロパティは文字列である必要があり、重要な情報は保存しない方が賢明で、またURLの長さの制限という問題もあります。 これらの問題はすべて、値をHttpSessionに入力することだけで解決できます。 ただし、その場合、後で従来の「戻る」/「進む(次)」の問題に直面します。ユーザーがWebブラウザで「戻る」ボタンをクリックしても、サーバーの状態は戻ることはありません。 なんらかの処理を元に戻したとユーザーは考えますが、サーバーの状態はそのように機能していないのです。 ページのState APIによってこれらの問題をすべて処理できます。

値を直接PageオブジェクトやHttpSession内に入力するかわりに、Stateオブジェクト内に入力した後、そのStateオブジェクトをPageにアタッチします。 UIXサーブレットがこのStateオブジェクトを管理し、短いID文字列をURLにエンコードして、UIXサーブレットがStateオブジェクトを再配置できるようにしています。

StateオブジェクトはStateManagerによって作成および管理されます。 現在のStateManagerを取得するには、BajaContext.getStateManager()をコールします。 その次に、getNewState()を使用してStateオブジェクトを取得します。

  BajaContext context = ...;
  // Pass "true" to force the StateManager to be created
  StateManager stateManager = context.getStateManager(true);

  // Create a mutable State object.
  MutableState state = stateManager.getNewState();

  // Store some state - any Object, not just Strings
  state.putValue(someKey, someValue);

  // And attach the State
  page.setState(state);

Stateオブジェクトには、可変および不変の2つの形態があります。 Stateオブジェクトが最初にStateManager以外で作成されると、Stateオブジェクトとして返され、これによってPageにアタッチする前に値を設定できます。 その次に続くリクエストで、ページからStateオブジェクトを取り出し、値を取得できますが、値を変更することはできません。 不変のStateオブジェクトの値を変更する必要がない場合は、StateUtils.cloneState()関数を使用することもできます。


  // We want to modify a pre-existing State object;  clone it
  // into a MutableState object
  State oldState = page.getState();
  MutableState newState = StateUtils.cloneState(stateManager, oldState);
  state.putValue(someKey, someValue);
  page.setState(newState);

handleStoreNameEvent()コードを変更して、State APIを使用するようにします。

  /** This is handleStoreNameEvent version 3 */
  public static EventResult handleStoreNameEvent(BajaContext context,
                                                 Page page,
                                                 PageEvent event)
  {
    String userName = event.getParameter("txt1");
    Page nextPage = new Page("NextPage");

    // Store the user name inside a State object instead of
    // as a page property
    StateManager stateManager = context.getStateManager(true);
    MutableState state = stateManager.getNewState();
    state.putValue("UserName", userName);
    nextPage.setState(state);

    return new EventResult(nextPage);
  } 

アプリケーションは通常、Stateオブジェクトの欠落を正常に処理します。 後述する理由のため、ユーザーが「戻る」ボタンをクリックしすぎるとStateオブジェクトが使用できなくなる可能性があります。

このアーキテクチャの実装を理解しておくと役に立つ場合があります。 Stateオブジェクトの実装方法に関心がない場合はスキップしてください。 StateManagersはデフォルトでHttpSessionに保存されています。 これは、サーバー上でステートフルな動作が必ず必要なことを意味しています。 また、ユーザー間でStateオブジェクトを共有できないことも意味しており、これは各ユーザーが個別のStateManagerと個別のIDを持っているためです。 またセキュリティ面では、HttpSession IDをスプーフィング(偽造)できないように設定することは、State IDもスプーフィング不可能になることを意味しています。

デフォルトのStateManager実装では、ユーザーに対して作成されたすべてのStateオブジェクトが保持されるわけではありません。 保持していると、ユーザーのログイン時間が長くなればなるほど、より多くのメモリーがサーバー上で必要になるという結果になります。 かわりに、固定された長さの、Stateオブジェクトの先入れ先出しキューが保持されます。 デフォルトでは、このキューは20個の状態を保持できます。つまり、21番目のStateオブジェクトが指定したユーザーに対して作成されると、1番目のオブジェクトは削除されます。 実作業では、問題が発生する前に、ユーザーは「戻る」ボタンを19回までクリックできることを意味しています。 Stateは、そのIDが要求されたもののみがキューに入るため、作成された後すぐに削除されたStateオブジェクトはこの制限には含まれません。 キューの長さはBaseBajaContext.createStateManager()をオーバーライドすることで調整できます。

ここまでで、アプリケーションのページ間を移動しながら、イベントやページのプロパティおよび状態を使用して、操作を実行したり、状態を保持する方法を学びました。 次のセクションでは、UIXサーブレットのPageFlowEngineについて説明します。これは、ログインの実行などさらに複雑なフローをサポートします。

ページ・ブローカ

PageBrokerはUIXサーブレットの最上位インタフェースです。 各UIXアプリケーションにはPageBrokerインタフェースを実装するオブジェクトのインスタンスが1つあり、それは、アプリケーション・ロジックへの主なエントリ・ポイントとして動作します。 PageBrokerは次の操作を処理します。

アプリケーションの最初のエントリ・ポイントとなるため、各リクエストの開始および終了方法のみでなく、アプリケーションの初期化および停止方法も提供されます。

UIXで提供されたデフォルト・サーブレット(UIXServlet)は、サーブレット構成パラメータのoracle.cabo.servlet.pageBrokerを使用してPageBrokerを取得します。 次に、WEB-INF/web.xmlファイルを使用する場合の構成例を示します。

<servlet>
 <servlet-name>uix</servlet-name>
  <!-- We'll use the default UIX servlet -->
 <servlet-class>oracle.cabo.servlet.UIXServlet</servlet-class>
  <!-- And we'll use a custom page broker -->
 <init-param>
    <param-name>oracle.cabo.servlet.pageBroker</param-name>
    <param-value>yourPackage.YourPageBroker</param-value>
 </init-param>
</servlet>

PageBrokerはいつでも直接実装できますが、UIXには有用な基本実装も用意されています。

oracle.cabo.servlet.AbstractPageBrokerクラスは、UIXサーブレットを使用するユーザーのための有用な抽象ベース・クラスです。 PageBroker全体に対する多数のデフォルト実装が提供されます。 たとえば、これを使用するとファイル・アップロードのサポートが容易になります。必要なのはdoUploadFile()メソッドのオーバーライドのみで、このメソッドは、サーブレットに送られる各ファイルごとに1回コールされます。

PageBrokerの基礎的な役割をすべてサポートする有用なベースの提供に加えて、AbstractPageBrokerは重要な抽象の1つ、PageDescriptionをUIXサーブレットに追加します。 UIXサーブレットではイベント処理とユーザー・インタフェースを、純粋なModel-View-Controllerアーキテクチャとして完全に分離できますが、多くの開発者にとっては、ページのイベント処理とそのユーザー・インタフェースを関連付けた方が便利であり、PageDescriptionはこの結合を担います。

PageBrokersの機能はこの他にもありますが、このセクションでは最後に、UIXサーブレットに用意されている、おそらく最も有用なPageBroker実装であるUIXPageBrokerについて簡単に説明します。 このクラス(oracle.cabo.servlet.xml.UIXPageBrokerが正式名)は、UIX XMLファイルをUIXサーブレットに結び付ける役割を担当します。 XMLファイルを探し、それをPageDescriptionに解析してから結果をキャッシュすることをサポートし、UIXの処理のカスタマイズに必要なすべてのツールを提供します。 UIXサーブレットでUIX XMLを使用していると、アプリケーションではほとんどの場合、UIXPageBrokerが使用されるか、それがサブクラス化されます。

ページ・フロー・エンジン

ページ・フロー・エンジンは、ページ・アクセスおよびページ・フローを制御します。 ここで、クライアント・ブラウザのリクエストに対してどのページをレンダリングする必要があるかを決定します。 フロー・エンジンでは、イベント・ハンドラによって生成されるEventResultの解釈も行います。

ページ・フロー・エンジンは交換可能です。ユーザーは、oracle.cabo.servlet.event.PageFlowEngineインタフェースを実装することによってエンジンをプラグインできます。 また、次の要素をアプリケーションのweb.xmlファイルに追加する必要があります。

<init-param>
  <param-name>oracle.cabo.servlet.event.PageFlowEngine</param-name>
  <param-value>mypackage1.mypageFlowEngine</param-value>
</init-param>	

mypackage1はエンジンのパッケージ名で、mypageFlowEngineはエンジンのクラス名です。

このセクションでは、UIXサーブレットに含まれているデフォルトのページ・フロー・エンジン、TrivialPageFlowEngineを紹介します。

EventResultオブジェクト

TrivialPageFlowEngineでは、EventResultに格納されているオブジェクトを宛先のPageとして解釈します。 このPageがイベントに応答してレンダリングされます。 EventResultのこのような使用例は、前のセクションで示しています(MyClass.handleStoreNameEvent(...)を参照)。

スーパークラス(BasePageFlowEngine)でも、プロパティとしてEventResultが現在のBajaContextに格納されます(すべてのPageFlowEngineでこれを実行する必要があります)。 EventResult.getEventResult(BajaContext)を使用してこの結果を取得します。 たとえば、EventResultには、次のコードを使用してUIX Componentsのデータ・プロバイダからアクセスできます。

public static DataObject getDataObject(RenderingContext context,
                                       String namespace,
                                       String name)
{
  BajaContext bajaContext = BajaRenderingContext.getBajaContext(context);
  EventResult result = EventResult.getEventResult(bajaContext);
  ...
}

上の例では、UIXサーブレットのイベント・ハンドラからUIX Componentsのデータ・プロバイダにデータを送信する方法を示しています。 EventResultを使用して、UIXサーブレットのイベント・ハンドラとUIXファイルの間で情報を直接交換することもできます。 UIXファイルは、暗黙変数であるuix.eventResultを使用して、現在のEventResultにアクセスできます。 このMapの各キーと値は、対応するEventResultプロパティの名前と値にマップされます。 次に例を示します。

<page xmlns="http://xmlns.oracle.com/uix/controller"
     xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="Update The Counter">
  <contents>
   <form name="form1">
    <contents>
     <messageStyledText prompt="Counter is at"
                        text="${uix.eventResult.counter}" />
     <submitButton event="inc"
                   text="Increment Counter" />
    </contents>
   </form>
  </contents>
  </header>
</content>
<handlers>
 <event name="inc">
  <method class="MyClass" method="doIncCounter" />
 </event>
</handlers>
</page>

上の例では、messageStyledTexttext属性は、UIXサーブレットの現在のEventResultcounterプロパティにデータ・バインドされています。 ユーザーが送信ボタンをクリックすると、incイベントが起動され、次のイベント・ハンドラがコールされます。

import javax.servlet.http.HttpSession;
import oracle.cabo.servlet.Page;
import oracle.cabo.servlet.BajaContext;
import oracle.cabo.servlet.event.PageEvent;
import oracle.cabo.servlet.event.EventResult;

public class MyClass
{
  private static int _counter=0;

  // Multiple threads may call this method and mutate _counter.
  // Therefore, this method must be synchronized.
  public static synchronized EventResult doIncCounter(BajaContext context,
                                                      Page page,
                                                      PageEvent event)
  {
    _counter++;
    EventResult result = new EventResult(page);
    result.setProperty("counter", new Integer(_counter));
    return result;
  }
}

このイベント・ハンドラは、内部の_counter変数を増分し、それをEventResultのプロパティとして設定します。 この結果は現在のPageを指します。 このPageでは、イベントの起動と同じページを指定するため、そのページが再び表示されます。ただし、今回はカウンタが増分されています。

EventResultオブジェクトでは、プロパティとして汎用Java Objectの名前と値のペアを指定できます(Pageオブジェクトでは、文字列の名前と値のペアしか指定できません)。 ただし、EventResultのプロパティを使用できるのは、ページがレンダリングを終了するまでの間だけです。一方、Pageオブジェクトのプロパティは、次のイベント・ハンドラでも使用できます。

リダイレクション

EventResultで返されるPageは、イベントを起動したPageと異なる場合があります。 ただし、クライアント側のブラウザでは、ページが切り替えられていることを知る手段がないことに注意してください。 次に例を示します。

public static EventResult handleEvent(BajaContext context,
                                      Page sourcePage,
                                      PageEvent event)
{
  // Render NextPage.uix in response to this event
  Page nextPage = new Page("NextPage");
  return new EventResult(nextPage);
}

この例では、ブラウザに表示されるURLはsourcePageのURLで、nextPageのURLではありません。 ブラウザに表示されるURLが、サーバーでレンダリングされたページと一致することが重要な場合は、クラスoracle.cabo.servlet.util.RedirectUtilsを使用して、ブラウザが正しいページをリダイレクトするようにします(これによってサーバーへの2回目のリクエストというコストが発生することに注意してください)。 次に例を示します。

public static EventResult handleEvent(BajaContext context,
                                      Page sourcePage,
                                      PageEvent event)
{
  Page nextPage = new Page("NextPage");
  // Force the browser to redirect to NextPage.uix in response to this
  // event
  Page redirectPage = RedirectUtils.getRedirectPage(context, nextPage);
  return new EventResult(redirectPage);
}

TrivialPageFlowEngineによるログイン・ページ

ページ・フロー・エンジンの非常に便利な機能は、特定のページへのアクセス制御です。 たとえば、クライアントがサーバーに認証されないかぎり、特定のページの表示が許可されない場合があります。 すべてのクライアント・リクエストは、イベントの有無にかかわらず、PageFlowEngineを経由します。 このため、すべてのアクセス制御をここで集中管理して、多数のページに処理を分散する場合よりも性能を向上できます。 このインタフェースの実装は、OracleのSingle Sign-On Serverなどのログインまたはアクセス制御テクノロジと統合できます。

TrivialPageFlowEngineでは、ログイン・ページ・フローの非常に基本的な実装が提供されます。 TrivialPageFlowEngineでメソッドsetLoginPage(Page loginPage)をコールすると(またはサーブレット構成パラメータoracle.cabo.servlet.loginPageを設定すると)、アクセス制御が使用可能になったことがこのエンジンに通知されます。認証されていないクライアントからのリクエストの場合、最初に要求されたURLに関係なくloginPageがレンダリングされます。

loginPageがレンダリングされ、クライアントが認証を受けると、TrivialPageFlowEngineは最初のリクエストURLに対応するページにリダイレクトします。

TrivialPageFlowEngineでは、クライアントが認証されているかどうかをどのように認識するのでしょうか。 これは、特定のキーのHttpSession値を調べることで行います。 値が存在する場合、対応するクライアントは認証されているとみなされます。 この値の取得に使用されるキーは、TrivialPageFlowEngineでメソッドsetLoggedInKey(String)を使用して(またはサーブレット構成パラメータoracle.cabo.servlet.loggedInKeyを設定して)設定されます。

単純な例を次に示します。 最初に、適切なサーブレット構成パラメータを設定する必要があります。 ここに示す例では、バージョン2.2の任意のサーブレットAPIエンジン(Tomcatなど)でサポートされるweb.xmlファイルが使用されています。 そのため、サーブレット構成パラメータは次のように設定されます。

<init-param>
  <param-name>oracle.cabo.servlet.loginPage</param-name>
  <param-value>Login</param-value>  <!-- corresponds to Login.uix -->
</init-param>

<init-param>
  <param-name>oracle.cabo.servlet.loggedInKey</param-name>
  <param-value>MyClass.isLoggedIn</param-value>
</init-param>

上の構成では、Login.uixがログイン・ページとしてTrivialPageFlowEngineに設定され、キーは文字列MyClass.isLoggedInに設定されています。 ここでUIXサーブレットでは、すべてのクライアント・リクエストについてすべてのHttpSessionでこのキーを調べます。 このキーに関連する値がnullの場合、またはHttpSessionが作成されていない場合、クライアントは一時的に次のログイン・ページにリダイレクトされます。

<!-- This is Login.uix ---->
<page xmlns="http://xmlns.oracle.com/uix/controller"
        xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
 <header xmlns="http://xmlns.oracle.com/uix/ui"
          text="Login">
  <contents>
   <!-- method=post is used here so that the password
             is not displayed in the URL -->
   <form name="form1" method="post">
    <contents>
     <messageTextInput name="username" prompt="Enter Name" />
     <messageTextInput name="password" prompt="Enter Password"
                   secret="true" />
     <submitButton event="login" text="Login" />
    </contents>
   </form>
  </contents>
  </header>
</content>
</page>

ユーザーがユーザー名とパスワードを入力してから送信ボタンをクリックすると、loginイベントがただちに起動され、次のイベント・ハンドラによって処理されます。 パスワードが正しい場合、なんらかのオブジェクトがHttpSessionHttpSessionのログイン・キーMyClass.isLoggedInに関連付けられます。 これによって、このクライアントが認証されていることがTrivialPageFlowEngineに通知され、ユーザーが最初にリクエストしたページがエンジンによってレンダリングされます。

import javax.servlet.http.HttpSession;
import oracle.cabo.servlet.Page;
import oracle.cabo.servlet.BajaContext;
import oracle.cabo.servlet.event.PageEvent;
import oracle.cabo.servlet.event.EventResult;

public class MyClass
{
  public static EventResult handleLogin(BajaContext context,
                                        Page page,
                                        PageEvent event)
  {
    String userName = event.getParameter("username");
    String password = event.getParameter("password");
    if (password.equals(getPasswordForUser(userName)))
    {
      HttpSession session = context.getServletRequest().getSession(true);
      session.putValue("MyClass.isLoggedIn",
                  new Object());  /** this will be some object that holds
                                      all of this user's server-side state */
    }
    return null;
  }
}

UIXサーブレットでのHttpSessionの使用方法

サーバー上でクライアントの状態を保持するために、多数のアプリケーションでHttpSessionオブジェクトを利用します。 サーバーでは、どのHttpSessionがどのクライアントに関連付けられているかを認識する必要があります。一般的な方法としては、Cookieをクライアント・ブラウザに設定し、サーバーでクライアントを認識できるようにします。

URLエンコーディングは、ページのすべてのURLにIDを組み込むことでクライアントを識別するもう1つの方法です。この場合、ユーザーがリンクをクリックすると、IDを含むURLがサーバーに送信され、クライアントおよび関連付けられたHttpSessionをサーバーで識別できるようになります。 この方法は、Cookieをサポートしないブラウザ(またはCookieのサポートが無効になっているブラウザ)で使用されます。

クライアント・ブラウザでCookieがサポートされない場合、UIXサーブレットで使用されるURLEncoderにより、サーブレット・セッションIDがURLに自動的にエンコードされます。 UIXサーブレットではテストを実行して、URLエンコーディングが必要であることを確認します。URLエンコーディングが必要になるのは、クライアント・ブラウザでCookieをサポートせず、ユーザー・アプリケーションでHttpSessionを必要とする場合のみです。このため、URLエンコーディングが機能するためには、レンダリングが行われる前、つまりレンダリング・サイクルが開始される前に、ユーザーがjavax.servlet.http.HttpServletRequestgetSession(true)をコールする必要があります(HttpSessionがレンダリング・サイクル中に作成されると、サイクルの最初にレンダリングされたURLがエンコードされない場合があります)。

URLエンコーディングが予定どおりに機能するためには、次の2つのルールに従う必要があります。

  1. データ・プロバイダではセッションを作成しないでください。 データ・プロバイダ・コードは、レンダリング・サイクル中にコールされます。このため、データ・プロバイダ・コード内では、HttpServletRequestgetSession(true)をコールしないでください。 セッションが必要な場合は、getSession(false)をコールし、HttpSessionがnullの場合はデフォルト値を使用します。
  2. イベント・ハンドラ・コードでは、getSession(true)をコールして(必要に応じて)セッションを作成できます。 イベント・ハンドラはレンダリング・サイクルの一番始めにコールされるため、この方法は安全です。

すべてのページでHttpSessionが必要な場合、HttpSessionが必ず1つ作成されるように明示的にアプリケーションをコーディングできます。 最も単純な方法は、PageBrokerをサブクラス化することです。 この場合は、requestStarted()メソッドが最適です。

  public class YourPageBroker extends SomeBuiltInPageBroker
  {
    public void requestStarted(BajaContext context)
    {
      // Force the creation of an HttpSession
      context.getServletRequest().getSession(true);
    }
  }

StateオブジェクトはHttpSessionに依存するため、レンダリング中にStateオブジェクトを作成することも不可能です。 つまり、DataProvidersStateオブジェクトを作成することはできません。 イベント処理の最中か、レンダリングの直前に作成する必要があります。

UIXサーブレットによるUIX XMLの拡張機能

UIXサーブレットによって、UIXの便利な拡張機能が提供されているため、UIXサーブレット・フレームワークに関連する重要なオブジェクトに、UIX XMLファイルからアクセスする場合があります。 このような拡張機能は、暗黙変数および関数という2つのカテゴリに分けられます。 このトピックのすべての例では、XMLネームスペースの接頭辞ctrlはUIXサーブレットのネームスペース(http://xmlns.oracle.com/uix/controller)に対応し、接頭辞uiはUIX Componentsのネームスペース(http://xmlns.oracle.com/uix/ui)に対応しています。

暗黙変数

このセクションでは、UIXサーブレットのデータ・プロバイダによって実装される暗黙変数を示します。 次に、messageTextInput要素のtextを、UIXサーブレットの現在のPageusernameプロパティに設定する例を示します。

<messageTextInput prompt="Your Name"
   text="uix.pageProp.username" />

関数

UIXファイルで使用可能なUIXサーブレットの関数を次に示します。

UIXサーブレットを使用したファイルのアップロード

UIXサーブレットでは、ファイルのアップロードもサポートされます。 サーブレットの初心者には意外かもしれませんが、サーブレットはGET HTTPリクエストおよびPOST HTTPリクエストを効率よく自動的に処理しますが、ファイルのアップロードは処理しません。 Servlet APIでは、ファイルのアップロードは各開発者が構成する必要があります。 ただし、ユーザーがmultipart/form-dataリクエスト形式を解析する必要はありません。これは自動的に処理されます。

UIからは、簡単にファイルのアップロードを起動できます。 次の2つの手順を実行するのみです。

  1. <form>で、usesFileUploadtrueに設定します。
  2. そのフォーム内に<fileUpload>要素または<messageFileUpload>要素を追加します。

多くの開発者にとってファイルのアップロードを理解する際の最大の問題は、ファイルのアップロードのパラメータが、アップロードするファイルと混合してしまうことです。 パラメータを抽出する唯一の方法は、リクエスト全体を読むことですが、これを行うにはファイルのアップロード時にそのファイルを処理する必要があります。 これはある重要な点を意味します。ファイルのアップロードは、イベント・ハンドラでは処理できないということです。 PageEventが作成され、すべてのパラメータが処理されると、アップロードされたファイルがすでに渡されたことを意味します。 このため、ファイルのアップロードの処理には、FileUploadManagerという異なる方法を使用する必要があります。

UIX PageBrokerにはFileUploadManagerが1つのみ存在します。 次のいずれかのものを使用して、マネージャを登録できます。

FileUploadManagerはサブクラス化できますが、大部分のUIX開発者は、BaseFileUploadManagerをサブクラス化します。 このサブクラスでは、次に示すとおり、単一のメソッドをオーバーライドする必要があるのみです。


  protected abstract String doUploadFile(
    BajaContext       context,
    Page              page,
    MultipartFormItem item) throws IOException;

UIXは、ファイルごとにdoUploadFile()を1回コールします。 MultipartFormItemインタフェースは、ファイルの読込みおよびファイル名とMIMEタイプの検出のためのメソッドを提供します。 doUploadFileの戻り値は、すべてのファイルのアップロードの完了時に作成されるPageEventにマージされます。 このパラメータの名前は、(MultipartFormItem.getFilename()と混同されないように)MultipartFormItem.getName()の値になり、<fileUpload>要素のnameから導出されます。

例:

簡単な例として、ファイルを常に共通ディレクトリにアップロードするFileUploadManager実装を示します。 このディレクトリ自体が、サーブレット構成パラメータを使用して構成されています(このパラメータを指定しない場合、ファイルが一時ディレクトリに格納されます)。 各ファイルの書込み後に、そのファイルのフルパスおよびファイル名が返されます。これによって、EventHandlerで、書込み直後のファイルを検出できます。


    package yourPackage;

    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;

    import javax.servlet.ServletConfig;

    import oracle.cabo.servlet.BajaContext;
    import oracle.cabo.servlet.Page;

    import oracle.cabo.servlet.io.BaseFileUploadManager;

    import oracle.cabo.share.util.MultipartFormItem;

    public class YourFileUploadManager extends BaseFileUploadManager
    {
      public void init(
  ServletConfig config)
      {
  // Get the directory for an initialization parameter
  _directory = config.getInitParameter("UploadDirectory");
      }

      protected String doUploadFile(
  BajaContext       context,
  Page              page,
  MultipartFormItem item) throws IOException
      {
  File         file;
  if ((_directory != null) && !"".equals(_directory))
  {
    file = new File(_directory, item.getFilename());
  }
  else
  {
    // If we haven't been told where to save the file,
    // save it as a temporary file.
    file = File.createTempFile(item.getFilename(), null, null);
  }

  // Write out the file
  OutputStream stream = new BufferedOutputStream(
         new FileOutputStream(file));
  item.writeFile(stream);
  stream.close();

  // Return the file name, so that the file can be found after
  //
  return file.getName();
      }

      private String _directory;
    }

これは単なる例であるため、いくつかの機能が省略されています。 たとえばこの例では、実行後にクリーンアップが行われず、アップロードされるファイルのサイズにも制限がありません。

UIXサーブレットを使用していない場合、またはファイルのアップロードを独自に処理する場合、oracle.cabo.share.util.MultipartFormHandlerクラスを使用する必要があります。 その場合、UIXサーブレット・アーキテクチャに簡単に接続することはできなくなりますが、解析コードおよびMultipartFormItem APIはそのまま使用できます。

BC4Jを使用している場合、UIXには、interMediaをサポートするFileUploadManagerが含まれています。詳細は、「ADF UIXでのメディアの使用」のトピックを参照してください。

UIXサーブレット: 各部分の組合せ方法

ここまでで、UIXサーブレットの各部分がどのように機能するかを詳しく説明してきました。次は、これらの部分をどのように組み合せるか(誰がページ・フロー・エンジンをコールするか)について説明します。 これらのUIX XMLページは、実際にはどのようにHTMLに変換されるのでしょうか。また、UIXサーブレットではJSPまたはJavaコードを使用してどのようにHTMLを作成するのでしょうか。 このセクションではこれらの質問に回答し、UIXサーブレットを構成するいくつかのJava APIを紹介します。

図4-3: UIXサーブレットの概要

UIXサーブレットの概要ダイアグラム

すべてのリクエストは、クライアント・ブラウザでリクエストをWebサーバー(Apacheなど)に発行することで開始されます。リクエストはWebサーバーによりサーブレット・エンジン(TomcatまたはJServなど)に渡され、そこから最終的にサーブレット(またはJSP)に渡されます。 サーブレットは、このリクエストがUIXサーブレットのリクエストであることを認識し、リクエストをBajaContextにカプセル化して、PageBrokerHandlerというUIXサーブレットの小さいクラスに転送します。

このハンドラにより、リクエストURIがUIRサーブレットのPageオブジェクトおよびPageEventに変換され、PageBrokerに転送されます。 これによって、イベント処理とページ・レンダリングのためにページ・ブローカが2回コールされます。 (このように明示的に分かれているため、開発者はコントローラとビュー・コードを分割する必要があります。) 細かい点ですが、イベントがない場合にもイベント処理フェーズは発生します。 これにより、アクセス制御(ログイン・ページへの転送など)の一貫性がすべてのリクエストについて保たれます。

すべてのUIXサーブレット・アプリケーションには1つのPageBrokerがあります。 これは、各リクエストのUIXサーブレットへの最初のエントリ・ポイントであり、UIXサーブレットのすべての永続オブジェクトはPageBrokerを経由します。 各ページのEventHandlerおよびPageRenderer(後者は後述を参照)は、PageBroker によって検出されます。 ハンドラおよびレンダラの検出方法は、ページ・ブローカによって異なります。UIXPageBrokerクラスはUIX XMLファイルを検索します。

イベント処理フェーズでは、PageBrokerにより、PagePageEvent、および正しいEventHandlerPageFlowEngineに送信され、クライアント・リクエストに応じてレンダリングするPageが決定されます。 この応答PagePageBrokerHandlerに返されます。 PageFlowEngineでは、エンド・ユーザーをあるページにアクセスさせない場合などに、イベントおよびイベント・ハンドラを無視できます。あるいは、ハンドラを使用してEventResultを取得できます。 ただし、レンダリングするページを最終的に決定するのは、常にPageFlowEngineです。

レンダリング・フェーズの場合、PageBrokerHandlerにより、レンダリングされるPagePageBrokerに渡されます。 PageBrokerではPageRendererを探して、ページをレンダリングするよう要求します。 レンダリングの方法は、PageRendererの実装によって異なります。 実装には次の処理を行うレンダラが含まれます。

  1. UIX XMLをHTMLにレンダリング
  2. UIX ComponentsのUINodeをHTMLにレンダリング
  3. ローカルのJSPまたはサーブレットに転送
  4. 任意のURLにリダイレクト
  5. 静的ファイルをアップロード

... ただし、実装によっては動的にGIFイメージが作成され、XSLT変換の結果やその他の処理が実行される場合があります。 PageRendererの終了後、次のブラウザ要求が送信されるまでは、UIXサーブレットは終了し、処理が完了した状態になります。

UIXサーブレットによるUIX XMLアプリケーションの作成方法

ここまでで、UIXサーブレット・アプリケーションのほとんどの部分について説明してきましたが、全体図については説明していません。 開発チームはこれらの各部分をどのように組み合せてアプリケーション全体を作成するのでしょうか。 個々の開発者が果たす役割やアプリケーションのアーキテクチャは何でしょうか。 ここで簡単に全体図について説明します。

UIXサーブレットのUIX XMLアプリケーションは、次の3種類の開発者によって開発されます。

  1. ユーザー・インタフェースの開発者
  2. モデルの開発者
  3. コントローラの開発者

ユーザー・インタフェースの開発者は、アプリケーションのビューを作成します。 ユーザー・インタフェースを定義するUIX XMLページが出力されます。

モデルの開発者は、アプリケーションのモデルを定義します。 バックエンドを公開するJavaクラスが出力されます。 これらのクラスは他のアプリケーションで再利用できるように、UIXの影響を受けていないことが必要です。

最後がコントローラの開発者です。 ユーザー・インタフェースとモデルを統合します。 モデルをDataObjectに変換するUIX Componentsのデータ・プロバイダをコーディングし、モデルを更新し、ページ・フローを処理するUIXサーブレットのイベント・ハンドラをコーディングします。 エラー処理など、モデル・レイヤーに属さないページの一部に対して、カスタム・データ・プロバイダもコーディングします。 UIXサーブレットがあるのがモデル・レイヤーです。

もちろん、この3種類は実際の状況を単純化したものです。 開発チームには、国際化のための開発者や翻訳者も必要になります。 開発者が複数のレイヤーにわたって作業することもあります。たとえば、ほとんどのコントローラ開発者はデータ・バインディングをサポートするためにUIXを変更します。 ただし、チームのメンバー間でUIXの開発を分割する場合には適切な分類方法です。