別のブラウザで表示すると、JavaScriptによってこのドキュメントの表示形式が変わる場合があります。ただしドキュメントの内容に影響はありません。

UIX開発者ガイド Go to Table of Contents
目次
Go to previous page
前へ
Go to next page
次へ

5. UIX Controllerの概要

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

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

概要

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

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

Diagram of a simple application

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

図5-2: UIX Controllerのページ・フロー

Diagram of UIX Controller page flow

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

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

UIX ControllerおよびuiXMLの単純なページ

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

<page xmlns="http://xmlns.oracle.com/uix/controller">
<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"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          text="UIX Components Header Bean">
  <contents>
   <dataScope>
    <contents>
     <link data:text="text1@dat1" data:destination="dest1@dat1"/>
     <link data:text="text2@dat1" data:destination="dest2@dat1"/>
    </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 Controllerのページがpage要素で始まる必要はありません。UIX Componentsの要素で開始することもできます。ただし、UIX Controllerのページ・フロー・システムを最大限に活用するには、page要素を使用する必要があります。

例の実行

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

UIX Controllerのイベントの概要

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

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

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

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

イベントの生成

UIX ControllerのイベントのURLは、eventフォーム・パラメータを使用したフォーム送信に似ています。ただし、XMLインタフェースについては、一貫性のある構文でイベントをuiXML要素にエンコードできる、便利なctrl:event属性が用意されています。そのため、次のようにイベントを作成できます。

<!-- 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 ctrl:event="StoreName" text="Submit" />
    </contents>
   </form>
  </contents>
  </header>
</content>
</page>

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

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

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

<form name="form1">
<contents>
 <messageTextInput name="txt1" prompt="Enter Name" text="YourName" />
 <formValue ctrl:event="StoreName" />
</contents>
</form>

この例では、StoreNameという名前のイベントをエンコードする非表示フォーム要素が生成されます。このフォームが送信されたときにもイベントが生成されます。この方法でイベントを起動する利点は、どのようなプロセスでフォームを送信してもイベントが生成されるということです。つまり、JavaScriptまたは他のUIX Components Beanなど、各種の送信ボタンを使用してフォームを送信しても、イベントが起動される動作は同じです。

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

<link text="Trigger Event Foo"
      ctrl:event="Foo"
      destination="page?param1=value1&amp;param2=value2" />

上のリンクをクリックすると、名前がFooで、2つのイベント・パラメータ(1つは名前がparam1で値がvalue1、もう1つは名前がparam2で値がvalue2)を持つイベントが起動されます。XMLでは、一部の特殊文字をエンコードする必要があるので注意してください。上の例では、&という文字を&amp;とエンコードしています。

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

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

かわりに次の形式を使用してください。

<formValue ctrl:event="StoreName" />

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

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

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

イベント・ハンドラはイベントを処理するコード部分です。UIX Controllerでは、特定のイベントを処理するためにどのイベント・ハンドラをコールするかを認識している必要があります。このために、各イベント・ハンドラをUIX Controllerに登録します。次のuiXMLファイルは、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 ctrl:event="StoreName" text="Submit" />
    </contents>
   </form>
  </contents>
 </header>
</content>
<handlers>
 <event name="StoreName">
  <method class="MyClass" method="handleStoreNameEvent" />
 </event>
</handlers>
</page>

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

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

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

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

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

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

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

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

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

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

イベント・ハンドラは、イベント名nullの下に登録することもできます。このキーワードは特別であり、イベント・ハンドラがnullイベント・ハンドラとして登録されます(UIX Controllerの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>

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

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

イベントの処理

前のセクションでは、メソッド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 Controllerの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 ControllerではThrowableを捕捉し、エラー・ページにリダイレクトします。

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

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

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

import oracle.cabo.servlet.event.EventHandler;

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

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

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

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

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

UIX ControllerのPageオブジェクト

UIX Controllerでは、URIのようにリソースを識別するためにPageオブジェクトが使用されます。各Pageには(getName()メソッドによって取得される)名前があり、アプリケーション内のUIX Controllerのページを一意に識別します。UIX Controllerに送信されるすべてのリクエストが自動的にデコードされ、リクエストのソースを識別する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属性は(このuiXMLファイルには)設定されていません。このため、UIX Controllerでは、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"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          text="Enter Your Data">
  <contents>
   <form name="form1" >
    <contents>
     <messageTextInput name="age" prompt="Enter your age" />
     <submitButton ctrl: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がフォーム送信の宛先として使用されています。uiXMLでは、その他の要素(リンクなど)の宛先としてPage URLを使用することが必要な場合があります。この場合、次の例のようにctrl:destination属性を使用します。

<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" ctrl:destination="NextPage" />
</ctrl:content>
</page>

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

<frame ctrl:source="Tree" name="TreeFrame" />

しかし、現在のところuiXMLのみを使用して、ページ・プロパティをURLにエンコードすることはできません。。プロパティがエンコードされているURLを取得するには、Java APIを使用して、データ・プロバイダ内部で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 Controller環境で実行している場合、このメソッドを使用して、現在のBajaContextを取得できます(同様にBajaRenderingContext.getPage(...)を使用して、現在のPageを取得できます)。

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

<dataScope xmlns="http://xmlns.oracle.com/uix/ui"
          xmlns:data="http://xmlns.oracle.com/uix/ui" >
<contents>
 <link text="Goto UIX Components product page"
     data:destination="768@productID" />
 <link text="Goto JEWT product page"
     data:destination="927@productID" />
</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 ControllerがこのStateオブジェクトを管理し、短いID文字列をURLにエンコードして、UIX Controllerが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以外で作成されると、MutableStateオブジェクトとして返され、これによって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オブジェクトの実装方法に関心がない場合はスキップしてください。StateManagerはデフォルトでHttpSessionに保存されています。これは、サーバー上でステートフルな動作が必ず必要なことを意味しています。また、ユーザー間でStateオブジェクトを共有できないことも意味しており、これは各ユーザーが個別のStateManagerと個別のIDを持っているためです。またセキュリティ面では、HttpSession IDをスプーフィング(偽造)できないように設定することは、State IDもスプーフィング不可能になることを意味しています。

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

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

ページ・ブローカ

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

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

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

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

ページ・フロー・エンジンは、ページ・アクセスおよびページ・フローを制御します。ここで、クライアント・ブラウザのリクエストに対してどのページをレンダリングする必要があるかを決定します。フロー・エンジンでは、イベント・ハンドラによって生成されるEventResultの解釈も行います。ページ・フロー・エンジンは交換可能です。ユーザーは、oracle.cabo.servlet.event.PageFlowEngineインタフェースを実装することによってエンジンをプラグインできます。このセクションでは、UIX Controllerに含まれているデフォルトのページ・フロー・エンジン、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 Controllerのイベント・ハンドラからUIX Componentsのデータ・プロバイダにデータを送信する方法を示しています。EventResultを使用して、UIX Controllerのイベント・ハンドラとuiXMLファイルの間で情報を直接交換することもできます。uiXMLファイルは、http://xmlns.oracle.com/uix/controllerネームスペースにあるeventResultという名前のDataObjectを使用して、現在のEventResultにアクセスできます。このDataObjectの各キーと値は、対応する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"
          xmlns:data="http://xmlns.oracle.com/uix/ui"
          text="Update The Counter">
  <contents>
   <form name="form1">
    <contents>
     <messageStyledText prompt="Counter is at"
             data:text="counter@ctrl:eventResult" />
     <submitButton ctrl:event="inc"
              text="Increment Counter" />
    </contents>
   </form>
  </contents>
  </header>
</content>
<handlers>
 <event name="inc">
  <method class="MyClass" method="doIncCounter" />
 </event>
</handlers>
</page>

上の例では、messageStyledTextのtext属性は、UIX Controllerの現在のEventResultのcounterプロパティにデータ・バインドされています。ユーザーが送信ボタンをクリックすると、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 Controllerでは、すべてのクライアント・リクエストについてすべての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 ctrl:event="login" text="Login" />
    </contents>
   </form>
  </contents>
  </header>
</content>
</page>

ユーザーがユーザー名とパスワードを入力してから送信ボタンをクリックすると、loginイベントがただちに起動され、次のイベント・ハンドラによって処理されます。パスワードが正しい場合、なんらかのオブジェクトがHttpSessionのログイン・キー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 ControllerでのHttpSessionの使用方法

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

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

クライアント・ブラウザでCookieがサポートされない場合、UIX Controllerで使用されるURLEncoderにより、サーブレット・セッションIDがURLに自動的にエンコードされます。UIX Controllerではテストを実行して、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オブジェクトを作成することも不可能です。つまり、DataProviderStateオブジェクトを作成することはできません。イベント処理の最中か、レンダリングの直前に作成する必要があります。

UIX ControllerによるuiXMLの拡張機能

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

データ・プロバイダ

このセクションでは、UIX Controllerのデータ・プロバイダによって実装されるデータ・オブジェクトを示します。これらのデータ・オブジェクトはすべてUIX Controllerネームスペースにあり、UIXからdata:attribute="key@ctrl:bajaDataObject"のようにアクセスできます。次に、messageTextInput要素のtextを、UIX Controllerの現在のPageusernameプロパティに設定する例を示します。

<messageTextInput prompt="Your Name"
   data:text="username@ctrl:page" />

属性

次に、uiXMLファイルで使用できるUIX Controllerの属性のリストを示します。

UIX Controller: 各部分の組合せ方法

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

図5-3: UIX Controllerの概要

Diagram overview of UIX Controller

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

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

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

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

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

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

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

UIX ControllerによるuiXMLアプリケーションの作成方法

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

UIX ControllerのuiXMLアプリケーションは、次の3種類の開発者によって開発されます。

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

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

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

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

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