UIX Developer's Guide |
![]() Contents |
![]() Previous |
![]() Next |
This chapter introduces the UIX Controller, which provides the means to group web pages together and control page flow. You'll learn to use UIX Controller to handle events and send pages back to a browser. In short, you can use UIX Controller to tie all of the pieces of UIX together into an application.
This chapter contains the following sections:
What is UIX Controller? UIX Controller is a web application framework based on the Model-View-Controller (MVC) design pattern. You've already been introduced to our preferred View layer - UIX. You've also seen the generic abstraction UIX provides for a Model layer - data binding - which can glue any model code into UIX. UIX Controller is the Controller layer, where events from the View modify the Model, and data from the Model controls the flow from one View to another. UIX Controller groups individual web pages together to form an application, and exports interfaces that allow the programmer to control the page flow.
Figure 5.1: A Web Application
Figure 5.1: A Web Application gives a schematic of a simple application. Here, 5 pages are grouped together to form the web application. The arrows represent links from one page to another. This can easily be done using HTML hyperlinks; why do we need to use UIX Controller? Often, when a user clicks on a link, the next page to display depends on data entered by the user or state on the server. For example, if the user has entered invalid data, the user should be redirected to an error page. It might also be necessary to process the data entered by the user before going to the next step. For example, in a shopping cart application the data the user has entered must be saved on the server before continuing. This is where UIX Controller comes in (See Figure 5.2: UIX Controller Page Flow). UIX Controller sits on the server examining each request. Depending on the data the user has entered, server-side state (such as a database), and the results returned from developer-provided handlers, UIX Controller can determine which page to display next (performing operations on the data in the process).
Figure 5.2: UIX Controller Page Flow
UIX Controller is written in Java and is designed to work with Java
Servlets. It may be used with its own standalone UIXServlet
, or with some other servlet
(including JSPs).
The technologies used to create each HTML page are independent of UIX Controller. UIX Controller can use JSPs to create HTML, or serve raw HTML files. UIX Controller can even serve pages that aren't HTML - GIF images, for example. But UIX Controller is especially designed to support and integrate with UIX Components and uiXML. It can automatically parse and cache UIX files, and make the UIX Components calls to turn UIX Components and uiXMLdocuments into HTML. In this chapter, UIX Controller will be used in conjunction with UIX Components to render UIX pages.
The following is a simple UIX Controller and uiXML page. All UIX Controller pages must start with a
page
element (note that this element is in the
UIX Controller namespace). This page
element has a content
child element. UIX Components and uiXML elements are added as
children to this UIX Controller content
element (note the
change in the default namespace from UIX Controller to 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>
And just for completeness here is an example with data binding:-
<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>
Strictly speaking, UIX Controller pages do not have to begin with the page
element; they may begin with UIX Components
elements. However, in order to make full use of the UIX Controller page flow system the
page
elements need to be used.
If you would like to run these examples in your own installation of UIX,
simply create some "filename.uix" file, add the contents to it, and place the
file in your servlet engine's web applications directory (e.g., "demos") under
the desired subfolder. For instance, if the servlet engine was OC4J and your
file was placed in:
/servletEngine/demos/somepath/filename.uix
, you
can then view the file by pointing your browser to http://myHostName:8888/somepath/filename.uix
.
Conventionally, static HTML pages are linked together with hardcoded HTML links. This means that the page flow is static - clicking on a link always sends you to the same HTML page. However, in most web applications it is necessary to dynamically choose a page to go to when a link is activated or a form is submitted. This decision may be made depending on the data submitted by the user or the state of the the server. While making this decision, state on the server might change.
UIX Controller makes it easy to indicate which links (or form submissions) require special processing (as described above). All client requests are received by UIX Controller, and by default UIX Controller serves up the page identified by the client request. However, UIX Controller recognizes a special signal known as an "event." Any link (or form submission) can be encoded to trigger an event. When a user clicks on such a link, UIX Controller executes application specific code to handle the event (maybe modifying server-side state in the process), and uses the results to determine which page to render in response to the event.
Events are the means by which applications interact with UIX Controller to modify state depending on user input. All server-side state modifications in the UIX Controller framework are performed by event handlers. Note that UIX Controller events occur on the server-side and should not be confused with client-side Javascript events.
A UIX Controller event has a name and zero or more parameters. An event name is a string
and a parameter is a simple name and value pair (just like a regular form
parameter), which associates the string name with the string value. The Java
interface that encapsulates a UIX Controller event is PageEvent
. The event name is returned by the getName()
method (in this class) and parameter values
can be obtained by calling the getParameter(String
key)
method (where key
is the parameter
name). The following section describes how events can be generated.
UIX Controller event URLs look like a form submission with an "event" form
parameter. But for the XML interface, we've provided a convenient "ctrl:event"
attribute that gives you a
consistent syntax for encoding events onto UIX elements. Therefore,
an event can be constructed as follows:
<!-- 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>
The above UIX creates a page with a form with two form elements. The first is
a messageTextInput
element, and the other is a submitButton
. Notice that the form
element has no destination attribute. UIX Controller
automatically uses the current page as the destination if none is set. When
the submit button is pushed by the user the following URL is generated by the
browser:
http://hostname:port/pageName?event=StoreName&txt1=YourName
UIX Controller will interpret this request as a triggered event. The event has name
"StoreName" and has one parameter with name "txt1" and value "YourName" (or
whatever the user typed into the text field). A later section will talk
about how to process this generated event on the server, but before that let's
see a few more ways to generate events. You do not need to use a SubmitButtonBean
to generate events. You can do the
following:
<form name="form1">
<contents>
<messageTextInput name="txt1" prompt="Enter Name" text="YourName" />
<formValue ctrl:event="StoreName" />
</contents>
</form>
The above generates a hidden form element which encodes an event named "StoreName". When this form is submitted an event will still be generated. The advantage of this method of firing events is that no matter what process is used to submit the form, the event will still be generated. This means that the form can be submitted by different submit buttons, by JavaScript or by other UIX Components beans, and still have the desired behaviour of triggering an event.
You don't have to use form elements to trigger an event. Any UIX
element that supports an URL "destination"
attribute also supports UIX Controller events. For example:
<link text="Trigger Event Foo"
ctrl:event="Foo"
destination="page?param1=value1&param2=value2" />
Clicking on the above link triggers an event with the name "Foo" and with two event parameters; the first has name "param1" and value "value1", and the second has name "param2" with value "value2". Also note that in XML certain special characters need to be encoded; in the above example the '&' character had to be encoded as '&'.
Incidentally, you might try to encode events directly, as in the following example. This is not recommended; it breaks the abstraction of page events, and will bypass some encoding mechanisms.
<!-- ******* Do not try to fire events like this!! ********* -->
<formValue name="event" value="StoreName" />
Instead, use the following form:
<formValue ctrl:event="StoreName" />
Note that certain UIX Components elements generate events; for example, NavigationBarBean
generates events with name
"goto", TotalRowBean
generates "total"
events, SortableHeaderBean
generates
"sort" events, and AddTableRowBean
generates "addRows" events. Of course, these can be handled without
using UIX Controller, but they all integrate well with the UIX Controller framework.
Now that we know how to generate events let's learn how to handle them inside UIX Controller. First we'll learn about registering event handlers from UIX; then, we'll learn about writing event handlers in Java.
An event handler is a piece of code that processes events. UIX Controller needs to know which event handler to call to process a specific event. This is done by registering each event handler with UIX Controller. The following UIX file gives an example of how this is done in 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>
The above code introduces a new child element of the UIX Controller page
element, the handlers
element. All handlers for the page are listed as children of this
element. Currently, in UIX Controller there is only one type of handler and that is
event handler.
Event handlers are defined by using the UIX Controller event
element. This takes one attribute "name", which specifies which UIX Controller event the
handler is capable of handling. In the above example the handler is
registering to handle all "StoreName" events occuring on this page.
Inside the event
element, we specify the
handler code by adding an event handler element. One such element is
the UIX Controller method
element. This element
takes the fully qualified Java class name and a method name and
calls this method (using the Java Reflection API) to handle the
respective event. In the above example the method MyClass.handleStoreNameEvent(...)
will be called
to handle any "StoreName" events occuring on this page.
Another way to reference event handler code is to use the UIX Controller instance
element. In the following example the
method MyClass.getStoreNameEventHandler
is
used to produce an event handler (instead of handling the event
directly). The event handler that is returned by this method is used
to actually handle the event (this will be discussed in more detail in
the next section). Incidentally, the method
attribute may be left out; UIX Controller will use
the default method name sharedInstance
, or
call a default (no-argument) constructor as a last resort.
<handlers>
<event name="StoreName">
<instance class="MyClass" method="getStoreNameEventHandler" />
</event>
</handlers>
The null
UIX Controller element is useful for
demonstration purposes where events are generated, but the event
handling code hasn't been written yet. If UIX Controller cannot find an event
handler for an event it receives, UIX Controller throws an UnhandledEventException
to warn the developer of
the mistake. The null
event handler can
be used in this case to route the user back to the same page without
any server-side processing. Use it as in the following example:
<handlers>
<event name="someEvent">
<null/>
</event>
</handlers>
UIX is fully extensible and can be enhanced with new elements to the uiXML to support registering event handlers other than these. See Extending UIX for more information about extending UIX.
At this point let's see other ways to register event handlers. The
following event handler is registered under "*". This event handler will
be called whenever an event is triggered on the page for an
event name that has no explicit handler.
This type of event handler is also known as the
default event handler. In the following example the UIX Controller null
handler is registered as the default
handler. Any events that do not have corresponding handlers will be
handled (and consequently ignored) by null
(this hides UnhandledEventExceptions
, but
masks the coding error).
<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
<event name="*">
<null/>
</event>
</handlers>
</page>
Event handlers can also be registered under the name "null". This
keyword is special and causes the event handler to be registered as a
null event handler (not to be confused with the UIX Controller null
element). Null event handlers are called
when no events are triggered on a page (ie: no event signal is encoded
on the URL of the page).
<page xmlns="http://xmlns.oracle.com/uix/controller" >
<handlers>
<event name="null">
...
</event>
</handlers>
</page>
When an event is generated, the component that triggered the event is
typically identified by the "source" event parameter. Different components set
the "source" parameter in different ways; for example, the table
's name
, and the hideShow
's id
attributes (we
apologize for the inconsistency) become the "source" parameters for all events
generated by those components. Please see the documentation for each component
to determine how it sets the "source" parameter.
It is possible to register event handlers for events generated by specific components by using this "source" parameter. In the following example, a handler is registered to handle the "goto" event, generated by some "table1" component:
<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>
As the above example illustrates, an event handler registered by both name and source takes precedence over one which is registered by name alone.
Note that you can register multiple event handlers under different event names and register the same event handler to handle different events, as in the following example:
<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>
It is possible to register a single event handler for multiple events from
multiple sources by using space-separated lists for the event name and
source. In the following example, The 'handleHGridEvent' method
handler is registered to handle four events
('expand', 'focus', 'expandAll' and 'collapseAll') for two components -
'hgrid1' and 'hgrid2':
<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>
You can also register a "global" EventHandler - one that will process an event
with a given name, no matter which page it is fired from. Since these don't
belong to any one page, they can't be registered from inside a page's UIX.
Instead, they're registered with Java code (see oracle.cabo.servlet.AbstractPageBroker
). In fact, all
these registration mechanisms are available from outside of UIX with UIX
Controller's Java API.
Now that we have seen the different ways event handlers can be registered let's see how a typical event is handled. This is discussed in the next section.
In the previous section the method MyClass.handleStoreNameEvent(...)
was introduced as an
example of an event handler for FirstPage.uix. The following is the Java source code
for the class:
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);
}
}
The first thing to note is the method signature. The UIX Controller method
element requires the following signature:
public static EventResult methodName(BajaContext context,
Page page,
PageEvent event) throws Throwable;
context
is the current BajaContext
(BajaContext
objects encapsulate all the request-specific pieces of application state, and
provide access to global services needed to write a Servlet-based application)
. page
is the page in which the event occurred
(Page
objects are explained in a later section),
and event
encapsulates the event that was fired
(the example demonstrates how to access the event name and properties by
calling getName()
and getParameter(String)
methods on PageEvent
).
The method must return an EventResult
which is
used by the current PageFlowEngine
(which will be
discussed in a latter section) to determine which page to display next. The
method may throw a java.lang.Throwable
; UIX Controller will
catch the Throwable
and redirect to an
error page.
Back to MyClass.handleStoreNameEvent
; this method
starts by obtaining the name entered by the user (recall that this
was in a text input form element with name "txt1"), and saves this user name
on the HttpSession
. It then wraps up the next
Page
(in the page flow) in an EventResult
object and returns it. The
current PageFlowEngine
will examine this result
object and render whichever Page
is contained in
it (page flow engines will be discussed later).
Note that the event handler uses the getParameter
method in PageEvent
to access the parameters instead of
using the same method in HttpServletRequest
. (The HttpServletRequest
can be obtained from the
current BajaContext
with the getServletRequest()
method). When accessing
parameters the PageEvent
abstraction must
be respected to make sure that file uploads work correctly, and that
parameters are properly decoded into the correct character set. The
PageEvent
abstraction also lets you
transform parameters - like adding, removing, or merging key/value
pairs - in a central location, all without touching your event handling
code.
Recall the example with the UIX Controller
instance
element. In that example the
method MyClass.getStoreNameEventHandler()
is called to produce an EventHandler
. This
static method (which is called just once when the page is parsed) must
take no arguments and has a signature as in the following code
fragment:
import oracle.cabo.servlet.event.EventHandler;
public class MyClass
{
public static EventHandler getStoreNameEventHandler()
{
...
}
}
The EventHandler
that is returned will be used to
service the event. Not surprisingly, the EventHandler
interface exports the following method
(notice the similarity with the UIX Controller method
event handler signature).
public interface EventHandler
{
public EventResult handleEvent(
BajaContext context,
Page page,
PageEvent event) throws Throwable;
}
Incidentally, UIX Controller instance
handlers execute slightly
faster than corresponding method
event handlers,
because the instance
handlers do not incur the
introspection costs of the Java Reflection API. However, the difference is
small enough that most applications shouldn't care.
Before we go into more complicated event handling it is necessary to describe
the UIX Controller Page
object. This is done in the next section.
Page
Objects
In UIX Controller, Page
objects are used to identify
resources much like a URI. Each Page has a name (obtained by the getName()
method) which uniquely
identifies a UIX Controller page within an application. Every request sent to UIX Controller is
automatically decoded to produce a Page object, which identifies the source of
the request.
Page
s can also have text properties set on
them. These properties are simple string name-value pairs. Properties
are set and retrieved with the setProperty(String
key, String value)
and getProperty(String
key)
methods. Property values don't always have to be
strings; as long as a value can be encoded as a string it can be set as a
property (see the Java API for Page
).
Page
properties can be encoded directly
onto an URL; the corresponding Page
object
produced by decoding that URL will have those properties already set
on it. This means that property state can survive a trip to the
client browser and back, and also be stored as a bookmarked
link. Because of this feature page properties can be used to maintain
user specific state without using server-side storage; the user state
is stored on the URL as properties. The following example uses events
and page properties to accumulate information about a user without
using server-side state.
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);
}
}
Recall that the method MyClass.handleStoreNameEvent(...)
was used as an event
handler in FirstPage.uix. The above code is a bit
different from the original handleStoreNameEvent(...)
method. The first thing to
note is that this new version doesn't use HttpSession
. This version does not store state on the
server-side. Instead, it encodes state on the URL using page properties.
This is achieved by creating a new Page
to
reference the next page in the page flow. The "UserName" property is set on
this Page
and the Page
is returned as an EventResult
This new Page
references NextPage.uix.
The form
element's destination
attribute is not set (in this UIX
file). Therefore, UIX Controller defaults it to reference the current Page
which has the "UserName" property
set. Hence, when this form is submitted the destination URL will have
the "UserName" property encoded on it. Also, submitting the form
triggers a "StoreAge" event which is handled by 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>
Now consider the MyClass.handleStoreAgeEvent(...)
. The user name
is accessed off the current page's properties. It is added (along with
the "Age" property) to the next page in the flow and life
continues. This is an example of using page properties to maintain
client state without using memory on the server-side. One important
drawback is that only state in text form can be maintained in this
way. Another point to make is that since property names/values are not
encrypted they shouldn't be used to store sensitive information (like
passwords or credit card information). Also, URLs are limited in
length - Internet Explorer 5 supports about 2000 bytes per URL, and
others may support as few as 255 bytes per URL. That places a strong
limit on how many page properties can be used at a time.
The above example uses an encoded Page
URL as the destination of a form submission. It is sometimes necessary in UIX to use Page
URLs as the destinations of other elements, for
example, links. This can be done by using the ctrl:destination
attribute, as in the following example:
<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>
Similarly, you can set the source
attribute of an
element to a Page
URL by using ctrl:source
as in the following code fragment:
<frame ctrl:source="Tree" name="TreeFrame" />
Unfortunately, at this time there is no way to encode page properties onto an URL inside of UIX alone. To obtain an URL with properties encoded in it, it is necessary to first data bind the attribute and use Java to encode the URL inside the data provider.
The encoding of a Page
into an URL is
obtained by using a PageEncoder
. The
current PageEncoder
can be accessed by
calling getPageEncoder()
on the current
BajaContext
. Consider the following
example:
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;
}
};
}
The above code implements a UIX Components data provider. Its basic function is to
encode the given key
as a certain property on a
Page
and return that Page
's encoding. Notice the method call BajaRenderingContext.getBajaContext(...)
. If UIX Components is
running in a UIX Controller environment this method can be used to get at the current
BajaContext
(Similarly BajaRenderingContext.getPage(...)
can be used to get at
the current Page
).
The following UIX code demonstrates how the above data provider is used to
compute destinations for links. The first link is encoded with the Oracle bug
database product code for UIX Components, while the second is encoded with the code
for JEWT. The
destination page in this example ("Products.uix") will be able to access the
correct product ID from the Page
object and render
the appropriate data.
<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>
Page properties have some excellent advantages for storing state. Because they're placed directly inside of URLs, you don't have to maintain any state on the server. You also don't have to worry about users hitting the Back and Next buttons, because the URL changes as the user hits those buttons.
However, the same reasons for these benefits are also the reasons for
limitations of page properties. As discussed earlier, Page properties have
to be Strings, should not contain sensitive information, and can run
into limits on the length of URLs. You can solve all of these
problems simply by putting values into an HttpSession
. However, you then run into the
classic Back/Next problem: when the user hits the Back button in a web
browser, the server's state doesn't unwind at all. The user may think
he's undone some actions, but the server doesn't behave accordingly.
The Page State
API addresses all of these
issues.
Instead of storing values directly on the Page
object or inside an HttpSession
, you'll instead store these values
inside a State
object, then attach that
State
object to the Page
. The UIX Controller will take care of
managing that State
object and will encode
a short ID string into the URL so that the UIX Controller can relocate
the State object.
State
objects are created and managed by a
StateManager
. To get the current StateManager
, call BajaContext.getStateManager()
. Then, use getNewState()
to get the State
object:
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
objects come in two forms:
mutable and immutable. When State
objects
are initially created off the state manager, they are returned as
MutableState
objects, which let you set
values before hooking it up to a Page
.
Then, on a subsequent request, you can get the State
object from the page and retrieve values -
but not change values. If you do need to change values in an
immutable State
object, you'll want to use
the StateUtils.cloneState()
function:
// 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);
Let's put the State
API to use by
changing our handleStoreNameEvent()
code:
/** 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);
}
Applications should always gracefully
handle the absence of a State
object. For
reasons to be explained shortly, a State
object may become unavailable if the user hits the "Back" button too
many times.
It may be useful to understand the implementation of this
architecture. If you're not interested in how State
objects are implemented, feel free to skip
ahead. StateManagers
are, by default,
stored in an HttpSession
. This means that
they do require stateful behavior on the server. It also means that
State objects cannot be shared between users, because each user has a
separate StateManager
and separate IDs.
This also implies that, from a security standpoint, ensuring that
HttpSession
IDs cannot be spoofed is
sufficient to ensure that State
IDs cannot
be spoofed.
The default StateManager
implementation
does not maintain all State
objects ever
created for a user. If it did, the longer a user was logged in, the
more memory that user would require on the server. Instead, it
maintains a fixed length, first-in-first-out queue of State
objects. By default, this queue is 20
states long: this means that when the twenty-first State
object is created for a given user, the
first will be discarded. In practice, this means that a user can hit
the Back button up to 19 times before running into problems. States
are only placed on the queue once their
ID has been requested, so State
objects
that are created and then immediately dropped don't actually count
against this limit. The length of the queue can be adjusted by
overriding BaseBajaContext.createStateManager()
.
At this point we have learned how to use events and page properties
and state to perform operations and maintain state while moving
between the pages of an application. The next section describes the
UIX Controller PageFlowEngine
that
supports more complicated flows such as the enforcing of a login.
A PageBroker
is the UIX Controller's
top-level interface. Each UIX application will have one instance of
an object that implements the PageBroker
interface, and it will act as the major point of entry into your
applications logic. A PageBroker
is
responsible for the following operations:
Since it provides a first point of entry into your application, it also provides hooks for initialization and shutdown of the application, as well as hooks for the start and end of each request.
The default servlet (UIXServlet
)
provided by UIX can pick a PageBroker
using a servlet configuration parameter: oracle.cabo.servlet.pageBroker
. Here's a sample
configuration if you're using a "WEB-INF/web.xml" file:
<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>
While you're always free to implement PageBroker
directly, UIX provides some
useful basic implementations.
The oracle.cabo.servlet.AbstractPageBroker
class is
a useful abstract base class for anyone working with the UIX
Controller. It provides a lot of default implementation for all
pieces of a PageBroker
. For example, it
makes it trivial to support file upload; you need only override the
doUploadFile()
method, which will be
called once for each file sent to your servlet.
In addition to providing a good basis for support of all the basic
responsibilities of a PageBroker
, the
AbstractPageBroker
adds one important
abstraction to the UIX Controller, the PageDescription
. While the UIX Controller
allows complete separation of event handling and user interface as a
pure Model-View-Controller architecture, many developers find it much
more convenient to associate the event handling of a page with its
user interface, and a PageDescription
is
just this coupling.
We could say a lot more about PageBrokers
, but we'll wrap up this section by
briefly describing perhaps the most useful PageBroker
implementation provided with the UIX
Controller, UIXPageBroker
. This class
(oracle.cabo.servlet.xml.UIXPageBroker
in
full) is the glue that ties your uiXML files into the UIX
Controller. It supports finding your XML files, parsing them into
PageDescriptions
then caching the results,
and provides all the hooks you'll need to customize the processing of
UIX. If you're using uiXML with the UIX Controller, your
application will almost certainly either use UIXPageBroker
or subclass it.
Page flow engines control page access and and page flow. They are
responsible for determining which page must be rendered in response to
a client browser request. The flow engines are also responsible for
interpreting the EventResult
s produced by
event handlers. Page flow engines are pluggable; a user may plug in
his/her engine by implementing the oracle.cabo.servlet.event.PageFlowEngine
interface. This section will introduce TrivialPageFlowEngine
which is the default page
flow engine included with UIX Controller.
EventResult
object
The TrivialPageFlowEngine
interprets the object
stored in an EventResult
as a destination Page
. This will be the Page
to render in response to the event. Examples of using EventResult
in this way were presented in earlier
sections (eg: See
MyClass.handleStoreNameEvent(...)
).
The superclass (BasePageFlowEngine
) also
stores the EventResult
as a property on
the current BajaContext
(all PageFlowEngine
s should do this). Use EventResult.getEventResult(BajaContext)
to get
at this result. For example, an EventResult
can be accessed from a UIX Components data
provider using the following code:
public static DataObject getDataObject(RenderingContext context,
String namespace,
String name)
{
BajaContext bajaContext = BajaRenderingContext.getBajaContext(context);
EventResult result = EventResult.getEventResult(bajaContext);
...
}
The above example illustrates a way to send data from a UIX Controller event
handler to a UIX Components data provider. EventResult
s can also be used to directly
exchange information between a UIX Controller event handler and a UIX file. A
UIX file can access the current EventResult
by using the DataObject
with name "eventResult" in the
"http://xmlns.oracle.com/cabo/controller" namespace. Each key/value in this
DataObject
maps to the corresponding EventResult
property name/value. For example:
<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>
In the above example the "text" attribute of messageStyledText
is data bound to the "counter" property
on the current UIX Controller EventResult
. When the user
clicks on the submit button, an "inc" event is triggered and the following
event handler is called:
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;
}
}
This event handler increments the internal _counter
variable and sets it as a property on the
EventResult
. This result points to the
current Page
. This Page
identifies the same page the event was triggered on
and so the same page is displayed again; however, this time the counter has
incremented.
It's also worth noting that EventResult
objects can have generic Java Object
name/value pairs as properties (compare with Page
objects that can only have string
name/value pairs). But the properties of an EventResult
are only available until the page
finishes rendering, while the properties of a Page
object will still be available to the next
event handler.
The Page
returned in an EventResult
may be different to the Page
that triggered the event. However, it is important
to know that the client-side browser has no way of knowing that the pages have
been switched. Here is an example:
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);
}
In the above example, the URL displayed by the browser will be the URL for
sourcePage
and not the URL for nextPage
. If it is important that the URL displayed by
the browser agrees with the page rendered by the server, then use the class
oracle.cabo.servlet.util.RedirectUtils
, to force
the browser to redirect to the correct page (note that this will incur the
cost of a second round-trip request to the server). The following is an
example:
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
A very useful feature of a page flow engine is to control access to
certain pages. For example, a client may not be allowed to view
certain pages until he/she has authenticated him/herself to the
server. All client requests, with or without events, flow through the
PageFlowEngine
. So all access control can
be centralized here, which is much better than distributing this
responsibility among many pages. An implementation of this interface
can integrate with any login or access control technology, like
Oracle's single-sign-on server.
TrivialPageFlowEngine
provides a very
basic implementation of a login page flow. Calling the method setLoginPage(Page loginPage)
on TrivialPageFlowEngine
(or setting the servlet
configuration parameter oracle.cabo.servlet.loginPage
) indicates to this
engine that access control is enabled; any request from a client which
has not been authorized will cause the loginPage
to be rendered regardless of the URL
that was originally requested.
After the loginPage
has been rendered and
the client authenticates him/herself TrivialPageFlowEngine
will redirect to the page
that corresponds to the original request URL.
How does TrivialPageFlowEngine
know if a
client has been authenticated? It checks for an HttpSession
value at a certain key. If the value
exists, the corresponding client is deemed authenticated. The key that
is used to get at this value is set using the method setLoggedInKey(String)
in TrivialPageFlowEngine
(or by setting the servlet
configuration parameter oracle.cabo.servlet.loggedInKey
).
A simple example is presented here. First the appropriate servlet configuration parameters must be set. The example presented here uses a web.xml file, supported by any 2.2 Servlet API engine (e.g. Tomcat). So, the servlet configuration parameters are set in the following manner:
<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>
The above configuration sets Login.uix as the login page on TrivialPageFlowEngine
and sets the key to be the string
"MyClass.isLoggedIn". Now UIX Controller will check for this key in every HttpSession
for every client request. If the value
associated with this key is null, or no HttpSession
has been created, then the client will be
temporarily redirected to the following login page:
<!-- 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>
After entering his/her username and password the user clicks on the submit
button which promptly triggers a "login" event which is handled by the
following event handler. Notice that if the password is correct some object is
associated with the login key "MyClass.isLoggedIn" on the HttpSession
. This is an indication to the TrivialPageFlowEngine
that this client has been
authenticated, and will cause the engine to render the original page requested
by the user.
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
in UIX Controller
In order to maintain client state on the server, many applications make use of
the HttpSession
object. Servers need to know which
HttpSession
is associated with which client and the
usual way this is done is by setting a cookie on the client browser to
identify the client to the server.
URL encoding is an alternate technique of identifying a client by placing an
ID on every URL on a page, so that when the user clicks on a link the URL sent
back to the server has the ID in it helping the server identify the client and
the associated HttpSession
. This alternate
technique is used with browsers that do not support cookies (or have cookie
support disabled).
The URLEncoder
used in UIX Controller automatically encodes
servlet session IDs on URLs when the client browser does not support
cookies. UIX Controller performs a test to make sure that URL encoding is necessary; URL
encoding would be necessary only if the client browser has no cookie support
and the user application requires HttpSession
s. Therefore, in order for URL encoding to
work, the user must call getSession(true)
on javax.servlet.http.HttpServletRequest
before any
rendering takes place; ie: before the start of a render cycle (if HttpSession
is created in the middle of a render cycle
this may result in URLs rendered at the beginning of the cycle to not be
encoded).
To ensure that URL encoding works as intended the following two rules must be adhered to:
getSession(true)
on HttpServletRequest
from within data provider code. If a
session is needed call getSession(false)
and
use default values if the HttpSession
is null.
getSession(true)
. This is safe because event
handlers are called at the very beginning of a render cycle.
If you absolutely need an HttpSession
on
all pages, you can explicitly code your application to force one to be
created. The simplest technique is subclassing your PageBroker
. Their requestStarted()
method is a perfect place for
this:
public class YourPageBroker extends SomeBuiltInPageBroker
{
public void requestStarted(BajaContext context)
{
// Force the creation of an HttpSession
context.getServletRequest().getSession(true);
}
}
Because State
objects rely on an HttpSession
, it's also illegal to create State
objects while rendering. Consequently,
DataProviders
should never create State
objects. They must be created either
during event handling or immediately prior to rendering.
UIX Controller provides some convenient extensions to UIX, so that important objects pertinent to the UIX Controller framework may be accessed from within a UIX file. These extensions are grouped into two categories, Data Providers and Attributes. Note that in all of the examples in this chapter, the XML namespace prefix "ctrl" maps to the UIX Controller namespace (http://xmlns.oracle.com/uix/controller) and the prefix "data" maps to the UIX Components namespace (http://xmlns.oracle.com/uix/ui).
This section lists the data objects implemented by UIX Controller data providers. All
these data objects are in the UIX Controller namespace, and can be accessed from UIX
like this data:attribute="key@ctrl:bajaDataObject"
. The following
is an example of setting the text
of a messageTextInput
element to be the username
property of the current UIX Controller Page
.
<messageTextInput prompt="Your Name"
data:text="username@ctrl:page" />
page
Page
. Each key maps to a Page
property name and returns the corresponding value.
pageState
State
associated with the current Page
. Each key maps to a State
select key and returns the corresponding value.
eventResult
EventResult
. Each key maps to
an EventResult
property name and returns the
corresponding value. Examples of using this data object can be found in the Page Flow Engine section.
httpSession
javax.servlet.http.HttpSession
object
associated with the current user. Each key maps to an attribute name and
returns the respective value. If the session has not been created, then every
key will return null. Please note the section Using HttpSession
in UIX Controller.
servletRequest
javax.servlet.ServletRequest
. Each key maps to an
attribute name and returns the respective value. This doesn't
provide access to a ServletRequest's
parameters - it only provides access to its attributes.
servletContext
This is the current javax.servlet.ServletContext
, which is global to
an entire web application, and shared among all users. Each key maps
to an attribute name and returns the respective value.
The following is a list of UIX Controller attributes that can be used in a UIX file.
destination
destination
attribute of the element to
the URL of the respective page. For example:
<link text="Front Page" ctrl:destination="Home" /> <!-- Home.uix -->
For some UIX Components elements (globalButton
and link
) the selected
attribute
is also set, so that if the current page happens to be the destination, then
selected
will be true
.
source
destination
but for the source
attribute.
<frame ctrl:source="Home" /> <!-- Home.uix -->
event
destination
attribute of the element. If the element
happens to be a UIX Components submitButton
or formValue
then the name
and
value
attributes of this element are
encoded. Examples:
<link text="Add Item" ctrl:event="addItem" />
<button text="Remove" ctrl:event="delItem" />
<submitButton text="Search" ctrl:event="search" />
node
UINode
tree
created by parsing a UIX file. This can be used with a UIX Components include
element to include a UIX file, as in the
following example:
<include ctrl:node="TabBar" /> <!-- includes TabBar.uix -->
By now, we hope you've gotten a good idea of how the pieces of UIX Controller work, but you're probably wondering how all of these pieces fit together - who calls the page flow engine? How are these UIX pages actually getting turned into HTML, and how would UIX Controller use a JSP or Java code to create HTML? This section will help you to answer those questions, and introduce you to some of the Java APIs that make up UIX Controller.
Figure 5.3: UIX Controller Overview
Any request begins with the client browser issuing a request to the
web server (for example, Apache), which passes it to the servlet
engine (for example, Tomcat or JServ), which finally passes it to a
servlet (or JSP). The servlet recognizes that this request is a UIX Controller
request, encapsulates the request in a BajaContext
, and forwards it to a small UIX Controller
class called PageBrokerHandler
.
This handler converts the request URI into a UIX Controller Page
object and PageEvent
and forwards it to a PageBroker
. It calls the page broker twice:
once for event processing, and a second time for page rendering.
(This explicit division forces developers to separate their controller
and view code.) A subtle but important point - the event processing
phase happens even if there is no event. This makes access control
(like forwarding to a login page) consistent across all requests.
All UIX Controller applications have one PageBroker
. It's the first point of entry into
UIX Controller for each request, and all persistent UIX Controller
objects hang off of the PageBroker
. It's
the PageBroker's
responsibility to find
EventHandlers
and PageRenderers
for each page (more on that latter
item below). How it find these handlers and renderers is up to the
page broker - our UIXPageBroker
class
looks for UIX files.
For the event processing phase, the PageBroker
sends the the Page
, PageEvent
, and
the correct EventHandler
to its PageFlowEngine
to determine which Page
to render in response to the client
request. This response Page
is returned to
the PageBrokerHandler
. The PageFlowEngine
is free to ignore the event and
event handler - if, for instance, the end user shouldn't have access
to a page - or can use the handler to get an EventResult
. But it always has the final word
on what page will be rendered.
For, the rendering phase, the PageBrokerHandler
passes the Page
to be rendered to the PageBroker
. The PageBroker
find a PageRenderer
, and asks it to render the page.
How it renders is up to the specific implementation of PageRenderer
. Our implementations include
renderers that:
... but an implementation could dynamically create a GIF image, run
the results of an XSLT transform, or anything else you can imagine.
Once the PageRenderer
finishes, UIX Controller is
done, and processing is complete until the next browser request comes
along.
By now, you've been introduced to most of the pieces of a UIX Controller application, but you may still be missing the "big picture". How can a team use these pieces to put together an entire application? What roles will individual team members play, and how is the application architected? It's time to give you that big picture, the view from 30,000 feet.
A UIX Controller uiXML application can be built by three types of developers:
The user interface developers are responsible for creating the View of an application. Their output is the UIX pages that define the user interface.
The model developers are responsible for defining the Model of an application. Their output is Java classes that expose the back-end. Those classes should be free of any trace of UIX, so they can be reused for other applications.
Finally, the controller developers. These are the
developers that glue the user interface and model together. They'll
code UIX Components data providers that convert the model to DataObjects
, and code UIX Controller event handlers that
update the model and handle page flow. And they'll code some custom
data providers for the parts of pages that don't belong in the model
layer, like error handling. This is the layer where UIX Controller lives.
Of course, this three-way division is a simplification of reality. Teams will need internationalization developers and translators. Some developers will work on more than one layer - for instance, most controller developers will need to modify the UIX to support data binding. But this is a good overview of how to divide UIX development among the members of a team.