blob: b7f390ba3d26e433f39eaf5b397c4d48faf9e93f [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="engine">
<title>Application Engines and Services</title>
<para>
The application engine is a central object whose responsibility is to run the request cycle for each
request. To do this, it manages resources such as page loaders and page recorders and provides
services to the pages and components utilized during the request cycle.
</para>
<para>
Application engines are instantiated by the application's servlet (described in the next section).
They are stored into the &HttpSession; and are persistent between request cycles.
</para>
<para>
An important behavior of the engine is to provide named engine services, which are used to create and
respond to URLs. The application engine creates and manages the request cycle and provides
robust default behavior for catching and reporting exceptions.
</para>
<para>
The application engine provides the page recorder objects used by the request cycle. By doing so,
it sets the persistence strategy for the application as a whole. For example, applications which use
or subclass &SimpleEngine; will use the simple method of
storing persistent state: in memory. Such applications may still be distributed, since the page
recorders will be serialized with the application engine (which is stored within the
&HttpSession;).
</para>
<section id="engine.servlet">
<title>Application Servlet</title>
<para>
Every Tapestry application has a single servlet, which acts
as a bridge between the servlet container and the
application engine. The application servlet is an instance of
&ApplicationServlet;.
</para>
<para>
The first thing a servlet does, upon initialization, is read the application specification.
To do this, it must know <emphasis>where</emphasis> the application specification is stored.
</para>
<para>
Specifications are stored on the classpath, which means in a JAR file, or in the
<filename>WEB-INF/classes</filename> directory of the WAR.
</para>
<para>
The servlet determines the location of the application specification from the
web deployment descriptor. A servlet initialization property, <varname>org.apache.tapestry.application-specification</varname>
provides the locations of the specificiation as a path.
</para>
<example>
<title>Web Deployment Descriptor</title>
<programlisting>
<![CDATA[
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<display-name>Tapestry Virtual Library Demo</display-name>
<servlet>
<servlet-name>vlib</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<init-param>
<param-name>org.apache.tapestry.application-specification</param-name>
<param-value>/net/sf/tapestry/vlib/Vlib.application</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<!-- The single mapping used for the Virtual Library application -->
<servlet-mapping>
<servlet-name>vlib</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
]]>
</programlisting>
</example>
<para>
The servlet's main job is to find or create the &IEngine; instance. It then delegates all the behavior
for processing the request to the application engine. Encoded in the URL will be a particular
application service; the engine delegates to the service to perform the real work of handling
the request.
</para>
<figure>
<title>ApplicationServlet Sequence</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/ApplicationServlet-sequence.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
</section>
<section id="engine.req-pages">
<title>Required Pages</title>
<para>
Each application is required to have a minimum of five pages with specific names. Tapestry
provides default implementations for four of the five, but a full-featured Tapestry application may
override any of the others to provide a consistent look-and-feel.
</para>
<table>
<title>Tapestry Pages</title>
<tgroup cols="3">
<thead>
<row>
<entry>Page Name</entry>
<entry>Required</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>Exception</entry>
<entry>Default provided, may be overridden.</entry>
<entry>
Page used to present uncaught
exceptions to the user.</entry>
</row>
<row>
<entry>Home</entry>
<entry>Must be provided by developer.</entry>
<entry>The initial page displayed when the
application is started.</entry>
</row>
<row>
<entry>Inspector</entry>
<entry>Provided, never overriden.</entry>
<entry>Inspector that allows the Tapestry
application to be interrogated on its
structure.</entry>
</row>
<row>
<entry>StaleLink</entry>
<entry>Provided</entry>
<entry>Page displayed when a StaleLinkException is thrown during
the processing of a request.</entry>
</row>
<row>
<entry>StaleSession</entry>
<entry>Provided</entry>
<entry>Page displayed when a StaleSessionException is thrown
during the processing of a request.</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Tapestry only mandates the logical name of these four pages; the actual page component used is
defined in the application specification.
</para>
<para>
The <classname>Home</classname> page is the first page viewed by a client connecting to the application.
Other than that,
there is nothing special about the page.
</para>
<para>
The initial connection to the application, where nothing is specified in the URL but the path to
the servlet, causes the home service to be invoked, which makes use of the home page.
The restart service will also redirect the user to the home page.
</para>
<para>
No default is provided for the <classname>Home</classname> page; every Tapestry application must define its
own <classname>Home</classname> page.
</para>
<para>
The Exception page is invoked whenever an uncaught exception is thrown when processing a service.
</para>
<para>
The Tapestry framework catches the exception and discards any HTML output (this is why
output is buffered in memory).
</para>
<para>
The <classname>Exception</classname> page must implement a writable JavaBeans property of type
<classname>java.lang.Throwable</classname> named <varname>exception</varname>.
The framework will invoke the accessor method
before the page is rendered.
</para>
<para>
The class &ExceptionAnalyzer; and the
&ExceptionDisplay; component are typically used to present this information.
</para>
<para>
The <classname>StaleLink</classname> page is displayed when a &StaleLinkException;
is thrown, which may occur during the processing of the request. The exception is thrown when
Tapestry determines that the state of the page (on the server) is out of synch with the client's view
of the page ... this most often happens when the user makes use of the browser's back button.
<footnote>
<para>
If desired, the application engine can override the method
<function>handleStaleLinkException()</function>. The default implementation of
this method redirects to the <classname>StaleLink</classname> page, but a custom implementation
could set up an error message on the application's <classname>Home</classname> page and
redirect there instead.
</para>
</footnote>
</para>
<para>
The default implementation informs the user of the problem ("you really shouldn't use the back
button on your browser") and uses the home service to create a link back to the
<classname>Home</classname> page.
</para>
<para>
The <classname>StaleSession</classname> page is displayed when a
<classname>org.apache.tapestry.StaleSessionException</classname>
is thrown. This exception is thrown when the component
is configured to be stateful (which is the default)
and the <classname>HttpSession</classname> doesn't exist, or is
newly created - this indicates a fresh connection to the servlet container after the old session
timed out and was discarded.
<footnote>
<para>
Likewise, the default behavior can be changed by overriding the method
<function>handleStaleSessionException()</function>.
</para>
</footnote>
</para>
<para>
The <classname>Inspector</classname> page is provided by the framework;
it allows a developer to interrogate a running
Tapestry application to determine its structure.
</para>
</section>
<section id="engine.state">
<title>Server-Side State</title>
<para>
There are two types of server side state that are supported by Tapestry: persistent page properties
and the visit object. The first (page properties) have already been discussed.
</para>
<para>
The visit object is a central repository for application state and presentation logic. The visit object
is accessible through the application engine (the engine implements a <varname>visit</varname> property).
The application engine doesn't care about the class of the visit object, or what properties it
implements.
</para>
<para>
The visit object holds central information that is needed by many pages. For example, an e-
commerce application may store the shopping cart as a property of the visit object.
</para>
<para>
When using Enterprise JavaBeans, the visit object is a good place to store remote object
references (centralizing the logic to look up home interfaces, instantiate references, etc.).
</para>
<para>
Every page implements a <varname>visit</varname> property that allows access to the visit object.
</para>
<para>
When using the &SimpleEngine; engine,
the visit object is
created the first time it is referenced. The class of the visit object is stored in the application
specification.
</para>
</section>
<section id="engine.stateless">
<title>Stateful vs. Stateless</title>
<para>
Through Tapestry release 1.0.0, an &HttpSession;
was created on the very first request cycle, and an engine was created and
stored into it.
</para>
<para>
This comes at some cost, however. Creating the session is somewhat expensive if it is
not truly needed, and causes some overhead in a clustering or failover scenario.
In fact, until some real server-side state is created; that is, until a
persistent page property is recorded or the visit object created, it isn't really
necessary to store any server-side state for a particular client.
</para>
<para>
Starting with Tapestry release 1.0.1, the framework will operate statelessly
as long as possible. When triggered (by the creation of a visit, or by a
persistent page property) an &HttpSession; will be created and the engine
stored within it and the application will continue to operate pretty much
as it does in Tapestry release 1.0.0.
</para>
<para>
While the application continues statelessly, the framework makes use of a
pool of engine instances. This is more efficient, as it reduces the number of objects
that must be created during the request cycle. However, the major reason
for running statelessly is to bypass the overhead statefulness imposes
on the application server.
</para>
<para>
Of course, if rendering the <classname>Home</classname> page of your application triggers
the creation of the &HttpSession;
<footnote>
<para>
That is, changes a persistent page property, or forces the creation
of the visit object.
</para>
</footnote>, then nothing is gained. A well
designed application will attempt to defer creation of the session so that, at least,
the <classname>Home</classname> page can be displayed without creating a session.
</para>
</section>
<section id="engine.services">
<title>Engine Services</title>
<para>
Engine services provide the structure for building a web application from individual pages and
components.
</para>
<para>
Each engine service has a unique name. Well known names exist for the basic services (page,
action, direct, etc., described in a later section).
</para>
<para>
Engine services are responsible for creating URLs (which are inserted into the response HTML)
and for later responding to those same URLs. This keeps the meaning of URLs localized. In a
typical servlet or JSP application, code in one place creates the URL for some servlet to interpret.
The servlet is in a completely different section of code. In situations where the servlet's behavior
is extended, it may be necessary to change the structure of the URL the servlet processes ... and
this requires finding every location such a URL is constructed and fixing it. This is the kind of
inflexible, ad-hoc, buggy solution Tapestry is designed to eliminate.
</para>
<para>
Most services have a relationship to a particular component. The basic services (action, direct,
page) each have a corresponding component (&ActionLink;, &DirectLink;, &PageLink;).
The following example
shows how the &PageLink; component is used to create a link between application pages.
</para>
<para>
First, an extract from the page's HTML template:
</para>
<informalexample>
<programlisting>
Click &lt;a jwcid="login"&gt;here&lt;/a&gt; to login.
</programlisting>
</informalexample>
<para>
This is combined with the a <sgmltag class="starttag">component</sgmltag> declaration in the the page's specification:
</para>
<informalexample>
<programlisting>
&lt;component id="<emphasis>login</emphasis>" type="&PageLink;"&gt;
&lt;static-binding name="page"&gt;Login&lt;/static-binding&gt;
&lt;/component&gt;
</programlisting>
</informalexample>
<para>
The <varname>login</varname> component will locate the page service, and provide 'Login'
(the name of the target
page) as a parameter. The page service will build and return an appropriate URL,
which the <varname>login</varname> component will incorporate into the <sgmltag class="starttag">a</sgmltag> hyperlink it
generates.
</para>
<para>
The resulting HTML:
</para>
<informalexample>
<programlisting>
Click &lt;a href="/<replaceable>servlet-path</replaceable>?service=page&amp;context=Login"&gt;here&lt;/a&gt; to login.</programlisting>
</informalexample>
<para>
If the user later clicks that link, the application will invoke the page service to handle the URL;
it
will extract the page name (<literal>Login</literal>) and render that page.
</para>
<para>
The other services are more or less complicated, but share the same basic trait: the service
provides the URL and later responds if the URL is triggered.
</para>
<para>
Links (&ActionLink;, &DirectLink;, etc.) and &Form;s use services in slightly different ways. Links encode
all the information directly into the URL whereas &Form;s encode most of the information as
hidden form fields.
</para>
<figure>
<title>Services and Gestures
</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/Gestures.gif" format="GIF"/>
</imageobject>
</mediaobject>
</figure>
<para>
In the first part, a service generates a &Gesture; and then extracts the full URL from it, for use
as the <varname>href</varname> attribute of the <sgmltag class="starttag">a</sgmltag> tag.
</para>
<para>
In the second part, a service is used to access the servlet path (which becomes the
<varname>action</varname> attribute of the <sgmltag class="starttag">form</sgmltag> element).
The query parameters are individually extracted and encoded as hidden fields in the form.
</para>
</section>
<section id="engine.logging">
<title>Logging</title>
<para>
Tapestry makes use of the Apache group's
&Log4J; package to perform logging. This is an easy, fast,
powerful framework for adding logging to any Java application. Using &Log4J;, any number of
<emphasis>loggers</emphasis> can be created, and a logging level for each logger assigned.
Tapestry uses the complete class name as the logger for each class.
</para>
<para>
The &ApplicationServlet; class includes a method, <function>setupLogging()</function>,
to help initialize &Log4J;, allowing the default configuration to be overridden using command line
parameters.
</para>
<para>
The <link linkend="inspector">Tapestry Inspector</link> includes a Logging tab that allows the logging configuration to be
dynamically changed. The logging level for any logger can be assigned, and new loggers
can be created.
</para>
<para>
What this means is that, using the Inspector, it is possible to control exactly what logging output is
produced, dynamically, while the application is still running. The Tapestry Inspector is easily
added to any Tapestry application.
</para>
</section>
<section id="engine.private-assets">
<title>Private Assets</title>
<para>
The application engine is responsible for making private assets, assets that are stored
on the Java classpath,
visible when necessary to client web browser.
</para>
<para>
This takes two forms:
</para>
<itemizedlist>
<listitem>
<para>Dynamic download of asset data via the application servlet.</para>
</listitem>
<listitem>
<para>Dynamic copying of asset data into the web server's virtual file system.
</para>
</listitem>
</itemizedlist>
<para>
The first form is the default behavior; each private asset requires an additional round trip through the
application server and application engine to retrieve the stream of bytes which make up the asset. This is
fine during development, but less than ideal at deployment, since it places an extra burden on the
servlet container, stealing valuable cycles away from the main aspects of servicing end users.
</para>
<para>
The second form is better during deployment. The bytestreams are copied out of the classpath to a specific directory,
one that is mapped into the web server's virtual file system. Once it is so copied, the
access to the asset is completely static, as with any other image file or HTML page.
</para>
<para>
To enable dynamic copying, it is necessary to inform the framework about what file system directory
to copy the assets to, and what virtual file system directory that maps to. This is accomplished
using a pair of JVM system properties:
</para>
<variablelist>
<title>JVM System Properties</title>
<varlistentry>
<term>
<varname>org.apache.tapestry.asset.dir</varname>
</term>
<listitem>
<para>
The complete pathname of a directory to which private
assets may be copied by the asset externalizer.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<varname>org.apache.tapestry.asset.URL</varname>
</term>
<listitem>
<para>
The URL corresponding to the external asset directory.
</para>
</listitem>
</varlistentry>
</variablelist>
</section>
</chapter>