blob: ebacc8cb0720390b6a2b14788fcd63ce0d46de52 [file] [log] [blame]
<!-- $Id$ -->
<!--
Copyright 2004 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<chapter id="pages">
<title>Tapestry Pages</title>
<para>
Pages are specialized versions of components. As components, they have a specification,
embedded components, assets and an HTML template.
</para>
<para>
Pages do not have parameters, because they are the outermost component in the component
hierarchy.
</para>
<para>
All components, however deep their nesting, have a page property that points back to the page
they are ultimately embedded within. Pages have an engine property that points to the engine
they are currently attached to.
</para>
<para>
Pages participate in a pooling mechanism, so that a single instance of a page component can be
used by multiple sessions of the same web application. Even when a large number of client
sessions are active, it is rare for more than a handful to be actively processing requests in the
application server. This pooling mechanism minimizes the number of instances of a page that
must exist concurrently on the server. There are some implications to this design that are
discussed in the following sections.
</para>
<para>
Pages may have persistent state, properties specific to a particular user
that persist between request cycles. These properties live only as long as the
&HttpSession;. There is some complexity here, because the page state is
entirely <emphasis>seperate</emphasis> from any instance of the page. Remember that
on subsequent requests, a different page from the page pool may be used
to service the request ... in fact, in a clustering environment, the request
may be serviced by an entirely different server. Tapestry
efficiently and transparently hides these details; when any portion of an application
requests a page, it receives an instance of the page with all persistent page properties
set the the values previously stored for the user.
</para>
<para>
In fact, any
component may have persistent state, and use the page as means for recording that state.
</para>
<para>
The engine is a session persistent object. The implementation of this varies from application
server to application server, but the basic idea is that the
&HttpSession; is serialized after each
request and stored in a file or database. It may then be removed from memory. When a
subsequent request for the same session arrives, it is restored from the persistent storage.
</para>
<para>
In a clustering server application, consequtive requests for the same session may be serviced by
different servers within the cluster. Serializing and deserializing the
&HttpSession; is the mechanism
by which the servers are kept synchronized. Persistent page properties are stored as part
of the engine, and so they continue to be available, even after the engine has moved
from one server to another.
</para>
<para>
The visit object is a property of the engine object, so it is serialized and de-serialized with the
engine.
</para>
<para>
Pages are <emphasis>not</emphasis> session persistent. They exist only within the memory of the Java VM in which they
are first created. Pages and components don't need to implement the
<classname>java.io.Serializable</classname>
interface; they will never be serialized.
</para>
<para>
The application engine can always instantiate a new page instance and restore its previously
recorded state (the recorded state information is serialized with the engine).
</para>
<section id="pages.state">
<title>Page State</title>
<para>
Pages, and the components on them, have state. State is considered the set of values for the
properties of the page.
</para>
<para>
In Tapestry, the lifespan of each property is very important. There are three lifespans:
</para>
<itemizedlist>
<listitem>
<para>
Persistent. Changes the property are recorded and persist between request cycles.
Persistent properties are restored when the page is next loaded. Persistent
properties are specific to an individual user.
</para>
</listitem>
<listitem>
<para>
Transient. The property is set before the page is rendered and will be reset
(to its default value) at the end of the current request cycle.
</para>
</listitem>
<listitem>
<para>
Dynamic. The property changes even while the page is rendered, but
(like transient) the property is
reset at the end of the current request cycle.
</para>
</listitem>
</itemizedlist>
<para>
Persistent properties are things like the user's name, the product being displayed in an
e-commerce application, etc. Transient properties are more commonly things needed just once,
such as an error message. Dynamic properties are intimately tied to the rendering process ... for
example, to display a list of items in an order, it may be necessary to have a dynamic property take
the value of each line item in sequence, as part of a loop.
</para>
</section>
<section id="pages.persistent-state">
<title>Persistent Page State</title>
<para>
The Tapestry framework is responsible for tracking changes to page state during the request cycle,
and storing that state between request cycles. Ultimately, this is the responsiblility of the
application engine. This is accomplished through page recorder objects. As a page's persistent
state changes, it notifies its page recorder, providing the name of the property and the new value.
</para>
<para>
This information is stored persistently between request cycles. In a later request cycle, the page
recorder combines this information with a page instance to rollback the state of the page.
</para>
<para>
Pages are blind as to how their state is stored. The basic implementation of Tapestry simply
stores the page state information in memory (and serializes it with the engine, in the
&HttpSession;),
but future options may include storing the data in flat files, relational databases or even as cookies
in the client browser.
</para>
<para>
Some minor burden is placed on the developer to support persistent state.
The mutator method of every persistent property must include a line of code that notifies the
observer of the change.
</para>
<para>
For example, consider a page that has a persistent property for storing an email address. It would
implement the normal accessor and mutator methods:
</para>
<informalexample>
<programlisting>
private String emailAddress;
public String getEmailAddress()
{
return emailAddress;
}
public void setEmailAddress(String value)
{
emailAddress = value;
<emphasis>fireObservedChange("emailAddress", value);</emphasis>
}</programlisting>
</informalexample>
<para>
The mutator method does slightly more than change the private instance variable; it must also
notify the observer of the change, by invoking the method
<function>fireObservedChange()</function>,
which is
implemented by the class
&AbstractComponent;. This method is
overloaded; implementations are provided for every type of scalar value, and for
<classname>java.lang.Object</classname>.
</para>
<para>
The value itself must be serializable (scalar values are converted to wrapper classes, which are
serializable).
</para>
<para>
The page designer must provide some additional code to manage the lifecycle of the page and its
persistent properties. This is necessary to support the "shell game" that allows a page instance to
be separate from its persistent state, and is best explained by example. Let's pretend that the user
can select a personal preference for the color scheme of a page. The default color is blue.
</para>
<para>
The first user, Suzanne, reaches the page first. Disliking the blue color scheme, she uses a form
on the page to select a green color scheme. The instance variable of the page is changed to green,
and the page recorder inside Suzanne's session records that the persistent value for the color
property is green.
</para>
<para>
When Suzanne revisits the page, an arbitrary instance of the page is taken from the pool. The page
recorder changes the color of the page to green and Suzanne sees a green page.
</para>
<para>
However, if Nancy visits the same page for the first time, what is the color? Her page recorder
will not note any particular selection for the page color property. She'll get whatever was left in
the page's instance variable ... green if she gets the instance last used to display the page for
Suzanne, or some other color if some other user recently hit the same page.
</para>
<para>
This may seem relatively minor when the persistent page state is just the background color.
However, in a real application the persistent page state information may include user login
information, credit card data, the contents of a shopping cart or whatever.
The way to deal with this properly is for each page with persistent state to override the method
<function>detach()</function>. The implementation should reset any instance variables on the page to their initial
(freshly allocated) values.
</para>
<para>
In our example, when Suzanne is done with the page, its <function>detach()</function> method will reset the page
color property back to blue before releasing it into the pool. When Nancy hits the page for the
first time, the page retrieved from the pool with have the expected blue property.
</para>
<para>
In other words, it is the responsibility of the developer to ensure that,
as a page is returned to the pool, its state is exactly the same
as a freshly created page.
</para>
<para>
In our earlier email address example, the following additional code must be implemented by the
page:
</para>
<informalexample>
<programlisting>
public void detach()
{
emailAddress = null;
super.detach();
}</programlisting>
</informalexample>
<para>
All properties, dynamic, transient and persistent, should be reset inside the
<function>detach()</function> method.
</para>
<para>
Individual components on a page may also have dynamic, transient or persistent properties. If so,
they should implement the &PageDetachListener; interface and implement the
<function>pageDetached()</function> method and clear
out such properties, just as a page does in
<function>detach()</function>.
</para>
</section>
<section id="pages.ejb-props">
<title>EJB Page Properties</title>
<para>
Tapestry make a single, special case for one particular type of persistent page property: references
to Enterprise JavaBeans.
</para>
<para>
The page recorders check to see if a page property is type
<classname>javax.ejb.EJBObject</classname>. If so, they don't
store the object itself (<classname>EJBObjects</classname> are not directly serializable),
instead they get the <classname>Handle</classname> for the object
and store that instead (<classname>Handle</classname>s are serializable).
</para>
<para>
When the page is next accessed, the <classname>Handle</classname> is converted back into an
<classname>EJBObject</classname> before assigning
it to the page property.
</para>
<para>
A side effect of this is that you may not have a <classname>Handle</classname> as a
persistant page property; the page
recorders don't have a way to differentiate a <classname>Handle</classname> from an
<classname>EJBObject</classname> converted to a <classname>Handle</classname>
and always assume the latter.
</para>
</section>
<section id="pages.dynamic-state">
<title>Dynamic Page State</title>
<para>
The properties of a page and components on the page can change during the rendering process.
These are changes to the page's dynamic state.
</para>
<para>
The majority of components in an application use their bindings to pull data from the page (or
from business objects reachable from the page).
</para>
<para>
A small number of components, notably the &Foreach; component, work the other way; pushing
data back to the page (or some other component).
</para>
<para>
The &Foreach; component is used to loop over
a set of items. It has one parameter from which it
reads the list of items. A second parameter is used to write each item back to a property of its
container.
</para>
<para>
For example, in our shopping cart example, we may use a &Foreach; to run
through the list of line
items in the shopping cart. Each line item identifies the product, cost and quantity.
</para>
<example>
<title>HTML template for Shopping Cart</title>
<programlisting><![CDATA[
<h1>Context of shopping cart for
<span jwcid="insertUserName">John Doe</span></h1>
<table>
<tr>
<th>Product</th> <th>Qty</th> <th>Price</th>
</tr>
<span jwcid="eachItem">
<tr>
<td><span jwcid="insertProductName">Product Name</span></td>
<td><span jwcid="insertQuantity">5</span></td>
<td><span jwcid="insertPrice">$1.50</span></td>
<td><a jwcid="remove">remove</a></td>
</tr>
</span>
</table>]]></programlisting>
</example>
<para>
This example shows a reasonable template, including sample static values used
when previewing the HTML layout (they are removed by Tapestry at runtime). Some
areas have been glossed over, such as allowing quantities to be changed.
</para>
<para>
Component <varname>eachItem</varname> is our &Foreach;.
It will render its body (all the text and components it wraps) several times,
depending on the number of line items in the cart. On each pass it:
</para>
<itemizedlist>
<listitem>
<para>Gets the next value from the source</para>
</listitem>
<listitem>
<para>Updates the value into some property of its container</para>
</listitem>
<listitem>
<para>Renders its body</para>
</listitem>
</itemizedlist>
<para>
This continues until there are no more values in its source. Lets say this is a page that has a
<varname>lineItem</varname> property that is being updated by the
<varname>eachItem</varname> component. The <varname>insertProductName</varname>,
<varname>insertQuantity</varname> and <varname>insertPrice</varname> components use dynamic
bindings such as <literal>lineItem.productName</literal>,
<literal>lineItem.quantity</literal> and <literal>lineItem.price</literal>.
</para>
<para>
Part of the page's specification would configure these embedded components.
</para>
<example>
<title>Shopping Cart Specification (excerpt)</title>
<programlisting>
&lt;component id="eachItem" type="&Foreach;"&gt;
&lt;binding name="source" expression="items"/&gt;
&lt;binding name="value" expression="lineItem"/&gt;
&lt;/component&gt;
&lt;component id="insertProductName type="&Insert;"&gt;
&lt;binding name="value" expression="lineItem.productName"/&gt;
&lt;/component&gt;
&lt;component id="insertQuantity" type="&Insert;"&gt;
&lt;binding name="value" expression="lineItem.quantity"/&gt;
&lt;/component&gt;
&lt;component id="insertPrice" type="&Insert;"&gt;
&lt;binding name="value" expression="lineItem.price"/&gt;
&lt;/component&gt;
&lt;component id="remove" type="&ActionLink;"&gt;
&lt;binding name="listener" expression="listeners.removeItem"/&gt;
&lt;/component&gt;
</programlisting>
</example>
<para>
This is very important to the <varname>remove</varname> component. On some future request cycle, it will be
expected to remove a specific line item from the shopping cart, but how will it know which one?
</para>
<para>
This is at the heart of the <link linkend="cycle.action">action service</link>. One aspect of the
&IRequestCycle;'s functionality is to
dole out a sequence of action ids that are used for this purpose (they are also involved in forms
and form elements). As the &ActionLink; component renders itself,
it allocates the next action id from
the request cycle. Regardless of what path through the page's component hierarchy the rendering
takes, the numbers are doled out in sequence. This includes conditional blocks and loops such as
the &Foreach;.
</para>
<para>
The steps taken to render an HTML response are very deterministic. If it were possible to
'rewind the clock' and restore all the involved objects back to the same state (the same values for
their instance variables) that they were just before the rendering took place, the end result would
be the same. The exact same HTML response would be created.
</para>
<para>
This is similar to the way in which compiling a program from source code results in the same object
code. Because the inputs are the same, the results will be identical.
</para>
<para>
This fact is exploited by the action service to respond to the URL. In fact, the state of the page
and components <emphasis>is</emphasis> rolled back and the rendering processes fired again (with output discarded).
The &ActionLink; component can compare the action id against the target action id encoded
within the URL. When a match is found, the &ActionLink; component can count on the state of the
page and all components on the page to be in the exact same state they were in when the page
was previously rendered.
</para>
<para>
A small effort is required of the developer to always ensure that this rewind operation works. In
cases where this can't be guaranteed (for instance, if the source of this dynamic data is a stock
ticker or unpredictable database query) then other options must be used, including the use of
the &ListEdit; component.
</para>
<para>
In our example, the <varname>remove</varname> component would trigger some application specific code
implemented in its containing page that removes the current <varname>lineItem</varname> from the shopping cart.
</para>
<para>
The application is responsible for providing a
<link linkend="cycle.listeners">listener method</link>, a method which is invoked
when the link is triggered.
</para>
<example>
<title>Listener method for remove component</title>
<programlisting>
public void removeItem(IRequestCycle cycle)
{
getCart().remove(lineItem);
}
</programlisting>
</example>
<para>
This method is only invoked after all the page state is rewound;
especially relevant is the <varname>lineItem</varname> property.
The listener gets the shopping cart and removes the current line item from it.
This whole rewinding process has ensured that <varname>lineItem</varname> is the correct value, even though the remove
component was rendered several times on the page (because it was wrapped by the &Foreach;
component).
</para>
<note>
<title>Listener Methods vs. Listener Objects</title>
<para>
<link linkend="cycle.listeners">Listener methods</link> were introduced in Tapestry 1.0.2. Prior to that, it was necessary
to create a listener object, typically as an inner class, to be notified when
the link or form was triggered. This worked against the basic goal of Tapestry: to
eliminate or simplify coding. In reality, the listener objects are still there,
they are created automatically and use Java reflection to invoke the
correct listener method.
</para>
</note>
<para>
An equivalent JavaServer Pages application would have needed to define a servlet for removing
items from the cart, and would have had to encode in the URL some identifier for the item to be
removed. The servlet would have to pick apart the URL to find the cart item identifier, locate the
shopping cart object (probably stored in the &HttpSession;)
and the particular item and invoke
the <function>remove()</function> method directly. Finally, it would forward to the JSP that would produce the
updated page.
</para>
<para>
The page containing the shopping cart would need to have special knowledge of the cart
modifying servlet; its servlet prefix and the structure of the URL (that is, how the item to remove
is identified). This creates a tight coupling between any page that wants to display the shopping
cart and the servlet used to modify the shopping cart. If the shopping cart servlet is modified
such that the URL it expects changes structure, all pages referencing the servlet will be broken.
</para>
<para>
Tapestry eliminates all of these issues, reducing the issue of manipulating the shopping cart down
to the single, small listener method.
</para>
</section>
<section id="pages.stale-links">
<title>Stale Links and the Browser Back Button</title>
<para>
The fact that web browsers have a "back" button is infuriating to application developers. What
right does the user have to dictate the order of navigation through the application? Whose
application is this anyway?
</para>
<para>
In a truly stateless application, the browser back button is not a great hardship, because each page
carrys within itself (as cookies, hidden form fields and encoded URLs) all the state necessary to
process the page.
</para>
<para>
Tapestry applications can be more stateful, which is a blessing and a curse. The blessing is that
the Tapestry application, running on the server, can maintain state in terms of business objects,
data from databases, Enterprise JavaBeans and more. The curse is that a user hitting the back
button on the browser loses synchronization with that state.
</para>
<para>
Let's use an e-commerce example. A user is browsing a list of available cameras from a product
catalog. The user clicks on a Minolta camera and is presented with pictures, prices and details
about the Minolta camera.</para>
<para>
Part of the page lists similar or related items. The user clicks on the name of a similar Nikon
camera and is shown the pictures, prices and details of the Nikon camera.
The user then hits the
browser back button, returning to the page showing the Minolta camera, and clicks
the "add to shopping cart" button. Web browsers have no way of informing the server that the user
has employed the back button.
</para>
<para>
Once the user clicks the link, the server replies with a response showing
the contents of the shopping cart ... but what has been added to the cart, the Minolta or the
Nikon? It depends on how the Tapestry application has been structured.
</para>
<para>
Presumably, the application has a single page, named <classname>ProductDetails</classname>, that shows the pictures,
prices and details of any product. The <classname>ProductDetails</classname> page will
have a persistent property named
product, of type <classname>Product</classname>. <classname>Product</classname> is a business
class that contains all that pricing and detail
information.
</para>
<para>
The question is, how is the add to shopping cart link implemented? If its logic is to add whatever the
current value of the product property is (i.e., by using an &ActionLink;
component or part of a form) then it will
add the Nikon camera, since that's the current product (the most recent one displayed
to the user, as far as the server is concerned &horbar; it has no way to know the user hit
the back button and was staring at the Minolta when the link was clicked). This is the natural approach, since it
doesn't take into account the possiblility that the user worked backwards to a prior page.
</para>
<para>
On the other hand, if a &DirectLink; component is used, it can encode into the
URL the primary key of
the Minolta product, and that will be the product added to the shopping cart, regardless of the
current value of the product property.
</para>
<para>
HTML Forms, controlled by the &Form; component, are also
susceptible to these issues related to the browser back button. Still, there are techniques to make
even forms safe. Borrowing an idea from more traditional JavaServer Pages development, a
hidden field can be included in the form to sychronize the form and the application ... for
example, including the primary key of the Minolta or Nikon product. Tapestry includes a
&Hidden; component used for just this purpose.
</para>
<para>
Finally, the &ListEdit; component exists to help. It works like a &Foreach;, but encodes the
number and value of the items it iterates as hidden form fields.
</para>
</section>
<section id="pages.pooling">
<title>Page Loading and Pooling</title>
<para>
The process of loading a page (instantiating the page and its components) can be somewhat
expensive. It involves reading the page's specification as well as the specification of all embedded
components within the page. It also involves locating, reading and parsing the HTML templates
of all components. Component bindings must be created and assigned.
</para>
<para>
All of this takes time ... not much time on an unloaded server but potentially longer than is
acceptable on a busy site.</para>
<para>
It would certainly be wasteful to create these pages just to discard them at the end of the request
cycle.
</para>
<para>
Instead, pages are used during a request cycle, and then stored in a pool for later re-use. In
practice, this means that a relatively small number of page objects can be shared, even when there
are a large number of clients (a single pool is shared by all clients). The maximum number of
instances of any one page is determined by the maximum number of clients that simultaneously
process a request that involves that page.
</para>
<figure>
<title>Page Lifecycle</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Lifecycle.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
As the page is retrieved from the pool, all of its persistent page properties are set. Thus
the page is <emphasis>equivalent</emphasis> to the page last used by the application, even
if it is not the same instance. This includes any state (that is, the settings of any instance variables)
that are particular to the client.
</para>
<para>
This process is managed by the &IRequestCycle;. When asked for a page, it checks whether the page has
been accessed yet for this request. If not, the page must be obtained from the page loader and properly
attached and configured.
</para>
<figure>
<title>Page Loading Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Load-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
The page loader maintains a pool of pages, or can construct a new page instance as needed. The
&IPageRecorder; for the page tracks the persistant page properties and can reset the properties
of the page instance to values appropriate to the current session.
</para>
<para>
A page is taken out of the pool only long enough to process a request for a client that involves it.
A page is involved in a request if it contains the component identified in the service URL, or if
application code involves the page explicitly (for instance, uses the page to render the HTML
response). In either case, as soon as the response HTML stream is sent back to the client, any
pages used during the request cycle are released back to the pool.
</para>
<para>
This means that pages are out of the pool only for short periods of time. The duration of any
single request should be very short, a matter of a second or two. If, during that window, a second
request arrives (from a different client) that involves the same page, a new instance will be created.
Unless and until that happens, a single instance will be used and re-used by all clients, regardless of
the number of clients.
</para>
<para>Pages stay in the pool until culled, at which point the garbage collector will release the memory used by the page (and all the components embedded in it). The default behavior is to cull unused pages after approximately ten minutes.</para>
</section>
<section id="pages.localization">
<title>Page Localization</title>
<para>
When a page is first instantiated, its locale is set to match the locale of the
engine it is loaded into.
</para>
<para>
This page locale is read-only; it is set when the page is first created and never changes.
</para>
<para>
Any component or asset on the page that needs to be locale-specific (for instance, to load the
correct HTML template) will reference the page's locale.
</para>
<para>
As noted previously, pages are not discarded; they are pooled for later reuse. When an engine
gets an existing page from the pool, it always matches its locale against the pooled page's locale.
Thus a page and its engine will always agree on locale, with one exception: if the engine
locale is changed during the request cycle.
</para>
<para>
When the engine locale changes, any pages loaded in the current request cycle will reflect the
prior locale. On subsequent request cycles, new pages will be loaded (or retrieved from the pool)
with locales matching the engine's new locale.
</para>
<para>
Tapestry does not currently have a mechanism for unloading a page in the same request cycle it
was loaded (except at the end of the request cycle, when all pages are returned to the pool). If an
application includes the ability to change locale, it should change to a new page after the locale
change occurs.
</para>
<para>
Changing locale may have other, odd effects. If part of a page's persistent state is localized and
the application locale is changed, then on a subsequent request cycle, the old localized state will be
loaded into the new page (with the new locale). This may also affect any components on the page
that have persistent state (though components with persistent state are quite rare).
</para>
<para>
In general, however, page localization is as easy as component localization and is usually not much
of a consideration when designing web applications with Tapestry.
</para>
</section>
<section id="pages.buffering">
<title>Page Buffering</title>
<para>
The HTML response generated by a page during rendering is buffered. Eight kilobytes of 8-bit
ASCII HTML is allowed to accumulate before any HTML output is actually sent back to the
client web browser.
</para>
<para>
If a Java exception is thrown during the page rendering process, any buffered output is discarded,
and the application-defined exception page is used to report the exception to the user.
</para>
<para>
If a page generates a large amount of HTML (larger than the 8KB buffer)
and then throws an exception, the exception page is
still used to report the exception, however the page finally viewed in the client browser will be
"ugly", because part of the failed page's HTML will appear, then the complete HTML of the
exception page.
</para>
<para>
In practice, virtually all Tapestry pages will use a
&Body; component wrapping the majority of the page (it takes
the place of the normal <sgmltag class="starttag">body</sgmltag> element), and a &Body; component
buffers the output of all components in its body. This buffering is necessary so that the
&Body; component can write out various JavaScript handlers before the main body of HTML is written
(this is often related to the use of the &Rollover; and &Script; components).
</para>
<para>
In any case, whenever a &Body;
component is used, an exception thrown during the rendering of the page will cause all the
HTML buffered by the &Body; component to be cleanly discarded, allowing for a clean
presentation of the exception page.
</para>
</section>
<section id="pages.events">
<title>Page Events</title>
<para>
Each page has a lifecycle; it is created and attached to an engine. It will render itself. It is placed in a pool for later reuse. Later, it comes out of the pool and is attached to a new engine to
start the process again.
There are cases where objects, especially the components embedded somewhere within the page,
need to know about this lifecycle.
</para>
<para>
&IPage; can produce a number of events related to its lifecycle. &PageRenderListener; is a listener interface for determining when the page starts and finishes rendering (this includes rewind renders related to the
&ActionLink; component).
</para>
<figure>
<title>Page Render Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Render-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
The call to <function>commitPageChanges()</function> is very important. It is not possible to make any
changes to persistant page properties after this method is invoked; doing so will throw an exception.
</para>
<figure>
<title>Page Rewind Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Rewind-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
Page rewinds, which are related to the &Form; and &ActionLink; components, also perform a render operation
in order to restore dynamic state on the page. The &PageRenderListener; events are still fired. The
event listeners can invoke <function>isRewinding()</function> on &IRequestCycle; to determine
whether this is a normal render, or for rewind purposes.
</para>
<para>
The &PageDetachListener; interface is used by objects that wish to know when the page is detached from the
application, prior to be stored into the page pool (for later reuse). This is used by any components
that maintain any independent state.
</para>
<figure>
<title>Page Detach Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Detach-Sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
This cleanup occurs at the end of the request, after a response has been sent to the client web browser.
</para>
<para>
The engine knows when the &HttpSession; has been invalidated because the container will
invoke <function>valueUnbound()</function>. It loads and rolls back each page, then invokes
<function>cleanupPage()</function> to allow the page to gracefully cleanup any held resources.
</para>
<para>
Components that implement one of these interfaces usually override the method
<function>finishLoad()</function> (from &AbstractComponent;) to register themselves with the page.
</para>
</section>
</chapter>