UIX開発者ガイド | ![]() 目次 |
![]() 前へ |
![]() 次へ |
このトピックでは、Webページのグループ化およびページ・フローの制御を可能にするUIX Controllerを紹介します。UIX Controllerを使用してイベントを処理し、ページをブラウザに戻す方法を説明します。つまり、UIX Controllerを使用すると、UIXのすべての要素を使用してアプリケーションを作成することができます。
ここでは、次の項目について説明します。
HttpSession
の使用方法UIX Controllerとは何でしょうか。UIX Controllerは、Model-View-Controller(MVC)デザイン・パターンに基づくWebアプリケーション・フレームワークです。優れたビュー・レイヤーであるuiXMLについてはすでに紹介しました。UIXでモデル・レイヤーのために用意されている汎用抽象化機能についても説明しました。これはデータ・バインドと呼ばれ、任意のモデル・コードをuiXMLに連結できます。UIX Controllerはコントローラ・レイヤーです。ここでビューのイベントがモデルを変更し、モデルのデータがビュー間のフローを制御します。UIX Controllerでは、個々のWebページをグループ化してアプリケーションを形成し、プログラマがページ・フローを制御するためのインタフェースを提供します。
図5-1: Webアプリケーション
図5-1: Webアプリケーションは、単純なアプリケ―ションの概要図です。ここでは、5つのページからWebアプリケーションが形成されています。矢印はページ間のリンクを表しています。これはHTMLハイパーリンクを使用して簡単に実行できます。では、UIX Controllerはどこに使用するのでしょうか。多くの場合、ユーザーがリンクをクリックして表示されるページは、ユーザーが入力したデータ、またはサーバーの状態によって変わります。たとえば、ユーザーが入力したデータが無効な場合は、エラー・ページを表示する必要があります。次のステップに進む前に、ユーザーの入力データの処理が必要な場合もあります。たとえば、ショッピング・カート・アプリケーションでは、ユーザーが次に進む前に、入力データをサーバーに保存する必要があります。ここでUIX Controllerが必要になります(図5-2: UIX Controllerのページ・フローを参照)。UIX Controllerは、サーバーに常駐して各リクエストを調べます。ユーザーが入力したデータ、サーバー側の状態(データベースなど)、および開発者提供のハンドラから返される結果に基づいて、UIX Controllerで(処理中のデータを操作して)次に表示するページを決定します。
図5-2: UIX Controllerのページ・フロー
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のすべてのページは、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
にアクセスするとファイルを表示できます。
従来、静的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&param2=value2" />
上のリンクをクリックすると、名前がFoo
で、2つのイベント・パラメータ(1つは名前がparam1
で値がvalue1
、もう1つは名前がparam2
で値がvalue2
)を持つイベントが起動されます。XMLでは、一部の特殊文字をエンコードする必要があるので注意してください。上の例では、&という文字を&とエンコードしています。
次の例のように、イベントを直接エンコードする可能性も考えられます。これはお薦めしません。ページ・イベントの抽象化が損なわれるため、一部のエンコーディング・メカニズムが実行されないからです。
<!-- ******* 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"パラメータを異なる方法で設定します。たとえば、table
のname
属性や、hideShow
のid
属性(不一致についてはお詫びします)は、これらのコンポーネントで生成されたすべてのイベントの"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
ではなく、PageEvent
でgetParameter
メソッドを使用して、パラメータにアクセスします(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
オブジェクトについて説明する必要があります。次のセクションで説明します。
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
に設定され、Page
はEventResult
として返されます。
この新しい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;
}
}
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.HttpServletRequest
でgetSession(true)
をコールする必要があります(HttpSession
がレンダリング・サイクル中に作成されると、サイクルの最初にレンダリングされたURLがエンコードされない場合があります)。
URLエンコーディングが予定どおりに機能するためには、次の2つのルールに従う必要があります。
HttpServletRequest
でgetSession(true)
をコールしないでください。セッションが必要な場合は、getSession(false)
をコールし、HttpSession
がnullの場合はデフォルト値を使用します。
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
オブジェクトを作成することも不可能です。つまり、DataProvider
でState
オブジェクトを作成することはできません。イベント処理の最中か、レンダリングの直前に作成する必要があります。
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の現在のPage
のusername
プロパティに設定する例を示します。
<messageTextInput prompt="Your Name"
data:text="username@ctrl:page" />
page
Page
です。各キーはPage
プロパティ名にマップされ、対応する値を返します。
pageState
Page
に関連付けられているState
です。各キーはState
選択キーにマップされ、対応する値を返します。
eventResult
EventResult
です。各キーはEventResult
プロパティ名にマップされ、対応する値を返します。このデータ・オブジェクトの使用例は、「ページ・フロー・エンジン」のセクションを参照してください。
httpSession
javax.servlet.http.HttpSession
オブジェクトです。各キーは属性名にマップされ、それぞれの値を返します。セッションが作成されなかった場合、すべてのキーがnullを返します。「UIX ControllerでのHttpSession
の使用方法」のセクションを参照してください。
servletRequest
javax.servlet.ServletRequest
です。各キーは属性名にマップされ、それぞれの値を返します。ServletRequest
パラメータへのアクセスは提供されません。属性へのアクセスのみが提供されます。
servletContext
javax.servlet.ServletContext
です。Webアプリケーション全体に対してグローバルであり、すべてのユーザーに共有されます。各キーは属性名にマップされ、それぞれの値を返します。
次に、uiXMLファイルで使用できるUIX Controllerの属性のリストを示します。
destination
destination
属性を各ページのURLにバインドします。次に例を示します。
<link text="Front Page" ctrl:destination="Home" /> <!-- Home.uix -->
一部のUIX Components要素(globalButton
およびlink
など)ではselected
属性も設定され、現在のページが宛先である場合には、selected
がtrue
になります。
source
source
属性である以外はdestination
と同様に機能します。
<frame ctrl:source="Home" /> <!-- Home.uix -->
event
destination
属性にこのイベントをエンコードします。要素がUIX ComponentsのsubmitButton
またはformValue
である場合、要素のname
属性およびvalue
属性がエンコードされます。次に例を示します。
<link text="Add Item" ctrl:event="addItem" />
<button text="Remove" ctrl:event="delItem" />
<submitButton text="Search" ctrl:event="search" />
node
UINode
ツリーのルートを取得するために使用されます。これは、UIX Componentsのinclude
要素とともに使用して、次の例のようにUIXファイルを組み込むことができます。
<include ctrl:node="TabBar" /> <!-- includes TabBar.uix -->
ここまでで、UIX Controllerの各部分がどのように機能するかを詳しく説明してきました。次は、これらの部分をどのように組み合せるか(誰がページ・フロー・エンジンをコールするか)について説明します。これらのuiXMLページは、実際にはどのようにHTMLに変換されるのでしょうか。また、UIX ControllerではJSPまたはJavaコードを使用してどのようにHTMLを作成するのでしょうか。このセクションではこれらの質問に回答し、UIX Controllerを構成するいくつかのJava APIを紹介します。
図5-3: 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
により、Page
、PageEvent
、および正しいEventHandler
がPageFlowEngine
に送信され、クライアント・リクエストに応じてレンダリングするPage
が決定されます。この応答Page
はPageBrokerHandler
に返されます。PageFlowEngine
では、エンド・ユーザーをあるページにアクセスさせない場合などに、イベントおよびイベント・ハンドラを無視できます。あるいは、ハンドラを使用してEventResult
を取得できます。ただし、レンダリングするページを最終的に決定するのは、常にPageFlowEngine
です。
レンダリング・フェーズの場合、PageBrokerHandler
により、レンダリングされるPage
がPageBroker
に渡されます。PageBroker
ではPageRenderer
を探して、ページをレンダリングするよう要求します。レンダリングの方法は、PageRenderer
の実装によって異なります。実装には次の処理を行うレンダラが含まれます。
... ただし、実装によっては動的にGIFイメージが作成され、XSLT変換の結果やその他の処理が実行される場合があります。PageRenderer
の終了後、次のブラウザ要求が送信されるまでは、UIX Controllerは終了し、処理が完了した状態になります。
ここまでで、UIX Controllerアプリケーションのほとんどの部分について説明してきましたが、全体図については説明していません。開発チームはこれらの各部分をどのように組み合せてアプリケーション全体を作成するのでしょうか。個々の開発者が果たす役割やアプリケーションのアーキテクチャは何でしょうか。ここで簡単に全体図について説明します。
UIX ControllerのuiXMLアプリケーションは、次の3種類の開発者によって開発されます。
ユーザー・インタフェースの開発者は、アプリケーションのビューを作成します。ユーザー・インタフェースを定義するuiXMLページが出力されます。
モデルの開発者は、アプリケーションのモデルを定義します。バックエンドを公開するJavaクラスが出力されます。これらのクラスは他のアプリケーションで再利用できるように、UIXの影響を受けていないことが必要です。
最後がコントローラの開発者です。ユーザー・インタフェースとモデルを統合します。モデルをDataObject
に変換するUIX Componentsのデータ・プロバイダをコーディングし、モデルを更新し、ページ・フローを処理するUIX Controllerのイベント・ハンドラをコーディングします。エラー処理など、モデル・レイヤーに属さないページの一部に対して、カスタム・データ・プロバイダもコーディングします。UIX Controllerがあるのがモデル・レイヤーです。
もちろん、この3種類は実際の状況を単純化したものです。開発チームには、国際化のための開発者や翻訳者も必要になります。開発者が複数のレイヤーにわたって作業することもあります。たとえば、ほとんどのコントローラ開発者はデータ・バインドをサポートするためにUIXを変更します。ただし、チームのメンバー間でUIXの開発を分割する場合には適切な分類方法です。