blob: dc17b5a5f1191a6f8944e05a7b40c0f1a66e1738 [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="cycle">
<title>Understanding the Request Cycle</title>
<para>
Web applications are significantly different in structure from other types of interactive applications.
Because of the stateless nature of HTTP (the underlying communication protocol between web browsers
and web servers), the server is constantly "picking up the pieces" of a conversation with the client.
</para>
<para>
This is complicated further in a high-volumes systems that utilizes load balancing and fail over.
In these cases, the server is expected to pick up a conversation started by some other server.
</para>
<para>
The Java Servlet API provides a base for managing the client - server interactions,
by providing the &HttpSession; object, which is used to store
server-side state information for a particular client.
</para>
<para>
Tapestry picks up from there, allowing the application engine, pages and components
to believe (with just a little bit of work) that they are in continuous contact
with the client web browser.
</para>
<para>
At the center of this is the request cycle. This request cycle is so fundamental under Tapestry
that a particular object represents it, and it is used throughout
the process of responding to a client request and creating an HTML response.
</para>
<para>
Each application service makes use of the request cycle in its own way.
We'll describe the three common application services (page, action and direct),
in detail.
</para>
<para>
In most cases, it is necessary to think in terms of two consecutive request cycles.
In the first request cycle, a particular page is rendered and, along the way, any number
of URLs are generated and included in the HTML. The second request cycle
is triggered by one of those service URLs.
</para>
<section id="cycle.URLs">
<title>Service URLs and query parameters</title>
<para>
All URLs generated by the framework consist of the the path to the servlet, and up to three
query parameters.
<itemizedlist>
<listitem>
<para>
<varname>service</varname>: the name of the service that will be used
to processes the request.
</para>
</listitem>
<listitem>
<para>
<varname>context</varname>: contextual information needed by the service; typically
the name of the page or component involved. Often there are several pieces of
information, separated by slashes.
</para>
</listitem>
<listitem>
<para>
<varname>sp</varname>: additional parameters that can be made available to
the component. This is used by a &DirectLink; component. If there is more than one
service parameter, then there will be multiple <varname>sp</varname>
parameters in the URL.</para>
</listitem>
</itemizedlist>
</para>
</section>
<section id="cycle.page">
<title>Page service</title>
<para>
The page service is used for basic navigation between pages in the application.
The page service is tightly tied to the &PageLink; component.
</para>
<para>
A page service stores the name of the page as the single value in the service context.
</para>
<para>
The request cycle for the page service is relatively simple.
</para>
<figure>
<title>Page Service Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Page-Service-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
The URL contains the name of the page, and the corresponding page is aquired from
the request cycle. The page is given a chance to validate that the user can
access it, it can throw &PageRedirectException; to force a render of a different page.
Otherwise, <function>setPage()</function> tells the request cycle which page will
be used to render a response, and <function>renderPage()</function> peforms
the actual render.
</para>
</section>
<section id="cycle.listeners">
<title>Action and Direct listeners</title>
<para>
The &ActionLink;, &DirectLink; and &Form; components (which make use of
the <link linkend="cycle.action">action</link> and <link linkend="cycle.direct">direct</link>
services) inform the application when they have been triggered using listeners.
</para>
<para>
A listener is an object that implements the &IActionListener; interface.
</para>
<para>
<informalexample>
<programlisting>
public void actionTriggered(IComponent component, IRequestCycle cycle)
throws RequestCycleException;
</programlisting>
</informalexample>
</para>
<para>
Prior to release 1.0.2, it was necessary to create an object to be notified
by the component; this was almost always an annonymous inner class:
<informalexample>
<programlisting>
public IActionListener getFormListener()
{
return new IActionListener()
{
public void actionTriggered(IComponent component, IRequestCycle cycle)
throws RequestCycleException
{
// perform some operation ...
}
};
}
</programlisting>
</informalexample>
</para>
<para>
Although elegant in theory, that's simply too much Java code for too little effect.
Starting with Tapestry 1.0.2, it is possible to create a
<emphasis>listener method</emphasis> instead.
</para>
<para>
A listener method takes the form:
<informalexample>
<programlisting>
public void <replaceable>method-name</replaceable>(IRequestCycle cycle)
throws RequestCycleException;
</programlisting>
</informalexample>
</para>
<note>
<para>The throws clause is optional and may be omitted. However, no other
exception may be thrown.
</para>
</note>
<para>
In reality, listener <emphasis>objects</emphasis> have not gone away. Instead, there's a mechanism
whereby a listener object is created automatically when needed.
Each component includes a property, <varname>listeners</varname>, that is a collection of
listener objects for the component, synthesized from the available public methods. The
listener objects are properties, with the names corresponding to the method names.
</para>
<tip>
<para>
The class <classname>AbstractEngine</classname> (the base class for &SimpleEngine;)
also implements a listeners property. This allows you to easily add listener methods
to your application engine.
</para>
</tip>
<para>
The earlier example is much simpler:
</para>
<informalexample>
<programlisting>
public void formSubmit(IRequestCycle cycle)
{
// perform some operation ...
}
</programlisting>
</informalexample>
<para>
However, the property path for the listener binding must be changed, from <varname>formListener</varname> to
<varname>listeners.formSubmit</varname>.
</para>
</section>
<section id="cycle.direct">
<title>Direct service</title>
<para>
The direct service is used to trigger a particular action. This service is tied to the
&DirectLink; component. The service context identifies the page and component within the page. Any parameters
specified by the &DirectLink; component's <varname>context</varname> parameter are encoded as well.
</para>
<para>
The request cycle for the direct service is more complicated that the page service.
</para>
<figure>
<title>Direct Service Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Direct-Service-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
As with the page service, the page involved has a chance validate the request.
The component is located within the page, and the page is set as the default
response page. The listener is free to override this, and can load other pages,
change their properties, or otherwise affect the state of the application.
</para>
<para>
After the listener has its chance to respond to the request, a response page
is renderred.
</para>
<note>
<title>&IDirect; vs. &DirectLink;</title>
<para>
The sequence shown above is for the &DirectLink; component, which implements the &IDirect;
interface. In some rare cases, it is desirable to have a different component
implement the &IDirect; interface instead. It will still implement the
<function>trigger()</function> method, but will respond in its own way, likely without
a listener.
</para>
</note>
<para>
This is the primary way (besides forms) in which applications respond to the user.
What's key is the component's listener, of type
&IActionListener;. This is the hook that allows
pre-defined cotheirponents from the Tapestry framework to access application specific behavior.
The page or container of the
&DirectLink; component provides the necessary listener objects using dynamic bindings.
</para>
<para>
The direct service is useful in many cases, but does have its limitations.
The state of the page when the listener is invoked is its state just prior to rendering
(in the previous request cycle). This can cause a problem when the action to be performed is reliant
on state that changes during the rendering of the page. In those cases, the
<link linkend="cycle.action">action service</link>
(and &ActionLink; or &Form; components) should be used.
</para>
<para>
The &DirectLink; component has an optional parameter named <varname>parameters</varname>.
The value for this may be a single object, an array of objects,
or a &List;. Each object is converted into a string encoding, that is
included in the URL.
When the action is triggered, the array is reconstructed (from the URL) and stored
in the &IRequestCycle;, where it is available to the listener. The type is maintained, thus if
the third parameter is of type &Integer; when the URL is generated, then the third parameter
will still be an &Integer; when the listener method is invoked.
</para>
<para>
This is a very powerful feature of Tapestry, as it allows the developer to encode dynamic page state directly
into the URL when doing so is not compatible with the action service (described in the next section).
</para>
<para>
The most common use for these service parameters is to record
an identifier for some object that is affected by the link. For
example, if the link is designed to remove an item from
the shopping cart (in an e-commerce example), the service parameters
could identify which item
to remove in terms of a primary key, or line number within the order.
</para>
</section>
<section id="cycle.action">
<title>Action service</title>
<para>
The action service is also used to trigger a particular application-specific
action using an &ActionLink; component, and its listener.
The action service may also be used by the &Form; component to process HTML form submissions.
</para>
<para>
An action service encodes the page name and component for the request. It also includes
an action id.
</para>
<para>
The request cycle for the action service is more complicated that the direct service.
This sequence assumes that the component is an
&ActionLink;, the details of handling form submissions are described in a later section.
</para>
<figure>
<title>Action Service Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Action-Service-Sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
The point of the action service is to restore the <emphasis>dynamic state</emphasis>
of the page to how it was when the &ActionLink; component rendered the link. Only then is
the listener notified.</para>
<para>
The process of restoring the page's dynamic state is called rewinding. Rewinding is used to
go beyond restoring a page's persistent state and actually restore the page's dynamic state.
Whatever state the page was in when the action URL was rendered in the previous request cycle
is restored before the &ActionLink; component's listener is invoked.
</para>
<para>
Use of the action service is convenient, but not always appropriate.
Deeply nested &Foreach; components will result in a geometric increase in
processing time to respond to actions (as well as render the HTML).
</para>
<para>
If the data on the page is not easily accessible then the action service should be avoided.
For example, if the page is generated from a long running database query.
Alternate measures, such as storing the results of the query as persistent page state should be used.
Another alternative is to use the direct service (and &DirectLink; component) instead, as
it allows the necessary context to be encoded into the URL, using service
parameters. This can be very useful when the dynamic state of the page is dependant on
expensive or unpredictably changing data (such as a database query).
</para>
<para>
For example, a product catalog could encode the primary key of the products listed as the
service parameters,
to create links to a product details page.
</para>
</section>
<section id="cycle.forms">
<title>Services and forms</title>
<para>
Processing of requests for &Form; components is a little more complicated than
for ordinary &ActionLink; components.
This is because a &Form; will wrap a number of form-related components, such as
&TextField;, &Checkbox;, &PropertySelection; and others.
</para>
<para>
In order to accept the results posted in the HTML form, each of
these components must be given a chance to respond to the request. A component responds to the
request by extracting a request parameter from the &HttpServletRequest;,
interpreting it, and assigning a value through a parameter.
</para>
<para>
As with an &ActionLink; component, a full rewind must be done,
to account for conditional portions of the page and any &Foreach; components.
</para>
<note>
<para>
Starting with Tapestry release 1.0.2, &Form;s may now use the
<link linkend="cycle.direct">direct service</link> instead
of the
<link linkend="cycle.action">action service</link>; this is configurable. Using the direct service is the
default behavior unless specified. A rewind still occurs, it simply starts directly
with the &Form; component, rather than having to "work down" to it. This can be
a performance gain if a page contains many forms.
</para>
</note>
<para>
The &Form; component doesn't terminate the rewind cycle until <emphasis>after</emphasis> all of
its wrapped components have had a chance to render. It then notifies its own listener.
</para>
<para>
The basic components, &TextArea; and &TextField;,
are quite simple. They simply move text between the
application, the HTML and the submitted request.
</para>
<para>
Individual &Checkbox; components are also simple: they
set a boolean property.
A &RadioGroup; and some &Radio; components
allow a property to be set to a value
(dependent on which radio button is selected by the user).
The &PropertySelection; component is designed to more
efficiently handle this and can produce HTML for either
a popup list or a collection of radio buttons.
</para>
<para>
Tapestry also includes the more involved component,
&ValidField;,
which is similar to the simple &TextField; component,
but provide greater validation and checking of input, and
provides the ability to visually mark fields that are required or in error.
</para>
<para>
Regardless of which service the &Form; uses, it encodes the query parameters
(which identify the service and context)
as hidden field elements, rather than encoding them into the URL. This is necessary because some
servlet containers ignore URL query parameters when using the HTTP POST request; therefore, it is necessary that all query parameters (including the ones related to the engine service), be part of the form posting ... and that means the use of hidden fields in the form.
</para>
</section>
</chapter>