Illustrating Tapestry
-Harish Krishnaswamy
31 May 2003
Summary
"Yet another web framework?" one may wonder, while in fact
Tapestry is a revolutionary framework that beckons the web development
community. In one (big!) sentence, Tapestry is a flexible,
comprehensive, easy-to-use open source Java web framework that
introduces component-based-development to the presentation tier of web
applications and marks a clear separation of technologies employed in
its development. This document explores this statement from a
bird's-eye view.
Definitions
Before we delve into that statement, let's quickly define a few
terms for clarity and completeness of this document.
-
Component
-
a component is a "black box" with well-defined interfaces
that serves a specific purpose and can be used as a building block
to compose other components and applications via a plug-and-play
mechanism.
-
Component Definition
-
a component definition is the blueprint of the component (like a
Class in OO paradigm) that defines the component's behavior. A
component definition may include static configuration files (XML,
.properties files etc.) that help in defining the behavior of the
component, in addition to its code itself.
-
Component Declaration
-
a component declaration is the usage of a component in the
definition of another component. The defining component is known as
container or parent component and the used component is known as
contained or child component. It's like an attribute
declaration in a Class in OO paradigm.
-
Component Instance
-
a single copy of a component definition (like an Object in OO
paradigm) created from a component declaration.
Overview
Since the advent of Servlets and JSPs, one of the biggest problems that
has plagued the Java web development community is the separation of
presentation and content. There has been various attempts to solve this
problem. Some use a template approach that introduces a whole new set
of tags intermixed with some HTML and even some programming constructs.
While other solutions take it to the other extreme where everything is
done in code. Tapestry's unique solution to the problem is to keep
the presentation in HTML (or other standard markup language), define
the content in Java and tie the two together with an XML specification;
and a grouping of these three pieces makes a Tapestry component which
is the logical unit of a Tapestry application.
A Tapestry component, technically referred to as JWC (Java Web
Component), is a web component that can be composed, instantiated,
configured and aggregated to compose other components. A text field
shown in a browser via a Tapestry application is an instance of a JWC.
And so is the web page itself (a specialized JWC, technically referred
to as "Page", as you will see later in this document), which
may be composed by aggregating instances of other non-Page JWCs.
Tapestry comes with a toolbox ("library") of JWCs that can be
used right off the shelf. A JWC need not necessarily have to be a web
interface component; it can be a control component like the
Foreach
component in Tapestry's core library. In addition,
Tapestry framework provides a strong foundation for the developer to
build custom JWCs (beyond the scope of this document) without the
painful, error prone, grunt, plumbing work that is inevitable in
building a web application. A Tapestry application's presentation
tier is built using these JWCs.
Illustration 1
In order to understand what makes a JWC and see how the separation of
technologies is achieved, among other things, let's open up the
"black box" and take a peek at the internals of the JWC. Each
JWC is typically made of three parts – an HTML template, an XML
specification and one or more Java classes, although a simple JWC can
be made by providing just the template. In this illustration let's
use all three parts (a purist's approach) to show its industrial
strength and then we'll see how it can be simplified for the casual
developer, in our next illustration.
For the purpose of illustration, let's consider a simple one-page
Tapestry application that greets the user as shown below.

Figure 1. Home.java
Now that we know a page is also a JWC (specialized), let's build
the Page component (Home Page in our example) of our Welcome
application using the core library components readily available.
Page Template
<html>
<head>
<title>Welcome to Tapestry!</title>
</head>
<body>
Hello <span jwcid="user">User Name</span>! Welcome to Tapestry!
</body>
</html>
Listing 1. Home.html
The template is the part where the static portions (plain text/HTML) of
the web page needs to be provided, that will be rendered by Tapestry
unaltered. In addition to providing the static portions of the page,
the template is also used to provide placeholder tags for the dynamic
portions of the page. The placeholder tags are XML-like (no scripting
here!); in other words every open tag should have a matching close tag.
The placeholder tags are identified by a unique user-provided id
specified using a special markup tag attribute,
jwcid
, as
shown in the
<span>
tag in our example template. It
is the
jwcid
attribute of the placeholder tag that is of
importance to Tapestry; the placeholder tag itself is irrelevant, it
could have as well been
<xyz>
in our example. But
the
<span>
tag was chosen instead, so the page can
be previewed properly outside the context of a Tapestry application.
The
jwcid
attribute instructs Tapestry that an instance of
a JWC, declared in the specification below, is to be created and
plugged in place of the placeholder tag. The placeholder tag can have
attributes other than
jwcid
. How Tapestry interprets and
treats these attributes is dependent on the specification of the
component that will be plugged in. Some components (like the one we
have used in our Home Page) instruct the framework to silently filter
out the irrelevant attributes of the placeholder tag and its enclosing
body when it is rendered. Thus our
user
JWC, when
rendered, will not render the text "
User Name
"
enclosed by the placeholder tag. Please see "Tapestry Component
Reference" under "Documentation" on
Tapestry's home page for more information
on the core component specifications. In any case, the newly created
instance of JWC is assigned the user-specified id (
user
in
our example) that may be used later to reference this JWC. The
template, as one can see, is for the most part plain HTML except for
the XML-like placeholder tags. This allows a page designer with no
programming knowledge, a more natural undertaker of the task to build
and maintain web pages, to easily take over the responsibility of
building and maintaining the template through the life of the
application.
The naming convention for templates is the name of the component with a
.html
extension. And thus the template for our Home Page
component is named Home.html
. Templates are typically
placed under the web application context. So, if Welcome
is the context of our Welcome application, Home.html
will
typically be placed under webapps/Welcome
.
Page Specification
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<page-specification class="demo.Home">
<component id="user" type="Insert">
<binding name="value" expression="userName"/>
</component>
<context-asset name="$template" path="Home.html"/>
</page-specification>
Listing 2. Home.page
The specification is the part that drives a component and ties its
pieces together. It specifies, among a lot of things, the class that
defines the behavior of the component under construction (called
container or parent component – Home Page in our example), the
template for the container component and the components to be plugged
in place of the template's placeholder tags (called contained or
child components). The defining class (also known as component class
– demo.Home
in our example) of the container
component is specified using the class
attribute of the
specification root element – page-specification
.
The container component's template is specified using the
context-asset
element. A component can have any number of types
of assets (images, stylesheets etc.) associated with it and the
template is treated as one such type. The template asset is specified
using the special name $template
. The contained components
are declared using the component
element. Components thus
declared are known as an "Explicit" or "Declared"
components. The id
attribute of the component
element is like an Object reference in OO paradigm, and is the same as
the jwcid
attribute of the placeholder tag in the
template. Thus the framework knows which component needs to be plugged
into which placeholder tag. The type of component to be instantiated
and plugged in is defined by the type
attribute of the
component
tag. The component we use in our example is the
Insert
JWC that comes with Tapestry's core library.
As mentioned before, every component serves a specific purpose and so
is our Insert
JWC. The Insert
JWC, like the
name suggests, inserts text (the user name in our example) in place of
the placeholder tag in the template. The text to be inserted is
provided to the JWC via a parameter defined by the component.
Parameters are the gateway to configure components in Tapestry.
Component parameters are like method parameters, the difference being
that component parameters can be two-way; i.e., component parameters
can pass in a value and also spit out a value. Tapestry allows four
different kinds of parameters - in, form, custom
and
auto
. in
parameters can only pass in a value
to the component, while the other kinds can pass values in and out of a
component. The value passed in to a component is stored in the JWC as a
JavaBeans property (known as parameter-property) in the component
class. The value passed out of a component is updated in the variable
that was the source of the data that was passed in. This variable is
usually an attribute of the container component class, although it can
be any variable accessible from the container component class. In
addition to the parameter property, the JWC houses another JavaBeans
property called the binding-property. Every JWC parameter-property is
accompanied by a binding-property. The name of the binding-property is
the name of the parameter-property + the literal suffix
Binding
. The purpose of the binding-property, like the name
suggests, is to bind a foreign object (unknown at the time of component
definition) to the JWC parameter-property. This allows a JWC to be
decoupled from the source of data for the parameter-property. This
decoupling is achieved with the help of another incredible open source
project – Object Graph Navigation Library, OGNL. The component
class will use the parameter-property, via the JavaBeans-style
get
method, as needed in defining the component's behavior.
The get<ParameterProperty>
method in turn, will use
the get<ParameterProperty>Binding
method, which
will retrieve the bound object at runtime, using OGNL, and make it
available to the component class as shown in Figure 2. The
parameter-property is specified in the JWC specification at the time of
component definition and the binding-property is automatically created
and provided by the framework at runtime. The actual binding of the
foreign object, though, is specified at the time of component
declaration in the container component's specification as shown in
our Page specification (Listing 2).
Figure 2. Parameter mechanics
The binding is specified in the component declaration using the
binding
element, nested within the
component
element. The
binding
element has two attributes –
name
and
expression
. Attribute
name
specifies the name of the parameter and the
expression
attribute specifies the foreign object to bind. The
foreign object is any object that can be accessed from within the
container component class via a JavaBeans-style
get
method, or an object that is the result of an OGNL expression. The
target foreign object can be a very distant object in the object graph
and yet be accessed from right within the
expression
attribute. This object graph navigation is achieved via OGNL. Please
see
Resources for information on OGNL
expressions.
The Insert
JWC (user
in our example) defines
a parameter called value
. This parameter is bound to the
foreign object (userName
in our example) via the
binding
element as shown in the example Page specification
(Home.page
). At runtime, the user
instance
JWC reads the value of userName
attribute of
demo.Home
instance and updates the value
parameter
using OGNL. The user
instance then uses the parameter
value and inserts it into the template.
Page components are special JWCs, as already stated. Page components
are root level components (can never be contained components) with
special attributes and responsibilities. Page components cannot be used
as building blocks to compose other components; that said, parameters
are useless in Page components and so the framework forbids Pages from
having parameters.
The naming convention for specifications is the name of the component
with an extension of .page
(for Page components) or
.jwc
(for non-Page components). And thus the specification for
our Page component is named Home.page
. Specifications are
typically placed under the WEB-INF
folder of the
application context. So, if Welcome
is the context of our
Welcome application, Home.page
will typically be placed
under webapps/Welcome/WEB-INF
.
Page Class
package demo;
import org.apache.tapestry.html.BasePage;
public class Home extends BasePage {
private String userName = "Jack";
public String getUserName() {
return this.userName;
}
}
Listing 3. Home.java
The last piece of our Page component is the component class –
demo.Home
. The component class is a JavaBeans class that
typically houses the properties that supply data to the contained
components. In other words, these properties typically act as foreign
objects that will be bound to the parameters of the contained
components. In our example, the Page component class
(demo.Home
) houses the userName
property that
supplies the name Jack
to user
, the contained
Insert
JWC, via the getUserName
method. Note
that the contained Insert
JWC does not care where the
name comes from. For all it cares, the getUserName
method
can get the name from a directory service, a database or a legacy
system.
In addition to providing data, the component class also provides the
behavior of the component via listener and lifecycle methods. The
framework provides a base component class each for JWC
(BaseComponent
) and Page (BasePage
)
components that can be extended by applications. These base component
classes provide the default behavior for the necessary listener and
lifecycle methods thus keeping the extended classes short and simple
like our example Page component class (demo.Home
).
The naming convention for the component class is the name of the
component itself. And thus our component class is named
Home
. Component classes are typically placed in the
WEB-INF/classes
folder under the context of the web application
like any other Java web application. So, if Welcome
is the
context of our Welcome application, Home.class
will
typically to be placed under
webapps/Welcome/WEB-INF/classes
.
Application Specification
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application name="Tapestry Illustration 1">
<page name="Home" specification-path="Home.page"/>
</application>
Listing 4. Welcome.application
The application specification to an application is like the component
specification to a component; it ties the pieces of an application
together. It specifies, among other things, the Pages that make the
application. The specification root element,
<application>
, has a name
attribute that can
be used to specify an optional descriptive name for the application as
shown in the Welcome application. In our example one-page Welcome
application, the Home Page component is declared using the
<Page>
element within the <application>
element. The <page>
element has two attributes
namely name
and specification-path
. The
name
attribute is used to specify a logical name for the
Page component, which is typically the same as the principal physical
name (Home
part of Home.page
in our example)
of the component specification. The logical name for the Page component
can be any identifier unique within an application. Although the name
Home
(exact case) has a special meaning in Tapestry. The
framework will forward all unknown requests to the Page component named
Home
. The specification-path
attribute is
used to specify the physical name and location of the Page component
specification relative to the current location (location of the
application specification file). This is the only place one has to
specify the physical path to the file; all other references to the
component are specified using the logical name thus isolating the
physical name and location changes.
The naming convention for the application specification is the
application context name with an extension of application
.
And thus the specification for our Welcome application is named
Welcome.application
. As mentioned before, specifications are
typically placed under the WEB-INF
folder of the
application context. So, if Welcome
is the context of our
Welcome application, Welcome.application
will typically be
placed under webapps/Welcome/WEB-INF
.
web.xml
<?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 Welcome Application</display-name>
<servlet>
<servlet-name>Welcome</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Welcome</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>
Listing 5. web.xml
Tapestry applications have a single entry point servlet,
ApplicationServlet
, that acts as a gateway/gatekeeper. A request
that gets delegated to a Tapestry application is sucked in by the
framework's ApplicationServlet
, processed, packaged
and finally handed off to the application, in the form of Objects
instead of the cryptic request parameters, for further processing.
org.apache.tapestry.ApplicationServlet
is the framework
provided default servlet that may be extended for special needs (a very
rare situation). The application servlet is by convention (not a
requirement) mapped to the /app
URL pattern within the
application context. The AppilicationServlet
is specified
in the web.xml
as all Servlets in a Java web application.
The web.xml
file is placed, per Java specifications,
under the WEB-INF
folder of the application context. Now
if we ran our Welcome example application within Tomcat, running at
port 8080, within context Welcome
, the request URL
http://localhost:8080/Welcome/app
will take us to the Home Page
of our Welcome application that we just built.
Illustration 2
If you thought the previous illustration was a lot of work to show a
simple welcome page, don't fret; this illustration is for you.
Here, we'll use the same Insert
JWC as in Listing
1, but we'll use it to show the user's visitng time
instead. Before we continue, let me boost your spirits by showing you
the code that is needed for this illustration.
Page Template
<html>
<head>
<title>Welcome to Tapestry!</title>
</head>
<body>
Hello Jack! Welcome to Tapestry!
Your visiting time is <span jwcid="now@Insert" value="ognl:new java.util.Date()">Current time</span>
</body>
</html>
Listing 6. Home.html
That's all we need for this illustration, of course other than the
web.xml
which we can reuse from the previous illustration.
You don't need the page specification or the JavaBeans component
class; neither do you need the application specification. The framework
has default values defined for all these parts; we'll talk about
these a little later, but for now let's focus our attention on the
template.
The template, if you notice, looks pretty much the same as in
Listing 1 except for the placeholder tag. The placeholder tag now
has the component type (Insert
) embedded into the
jwcid
attribute value using the @
symbol. This is
just another notation for declaring a component. A component thus
declared is known as an Implicit component. now
is the
unique ID for the component and may be omitted if you don't have
the need to reference this component in your code. If the ID is
omitted, the component is called an "Anonymous Implicit"
component, and the framework would provide an arbitrary ID to such
components for internal use. An Implicit component has to be declared
and configured in the placeholder tag in its entirety thus avoiding the
need for a separate specification. Parameters of Implicit JWCs are
bound to their values right in the placeholder tag in the form of
attribute-value pairs. So the value
parameter of the
Insert
JWC, in our example, is bound to an instance of
java.util.Date
, as shown in Listing 6, using OGNL.
Yes, OGNL expressions are perfectly acceptable for attribute values in
templates. Any attribute value prefixed with ognl:
is
considered an OGNL expression. This type of binding is identical, in
meaning, to the binding specified in the Page specification from our
previous illustration.
The component declaration was only one part of our Page specification,
the other parts being the Page class declaration
(demo.Home
) and the template declaration
($template
asset). The framework by default searches for
the template by the name of the component with a .html
extension, so we can skip the $template
declaration. We
also do without the Page class, and thus the Page specification itself,
with the help of the framework default Page class,
org.apache.tapestry.html.BasePage
. The missing application
specification is also owed to the framework's default behavior.
Knowing that the framework will forward all unknown requests to a Page
component named Home
, we skip the application
specification too. Another point worth mentioning here is that the
framework has the ability to search and locate pages and templates not
explicitly specified in the specification. The framework performs this
search in a methodical fashion, the details of which can be found in
the JavaDocs of
org.apache.tapestry.resolver.PageSpecificationResolver
and
org.apache.tapestry.engine.DefaultTemplateSource
classes.
Now, if we place our template (Home.html
) under the
application context Welcome
, and place the
web.xml
file under the WEB-INF
folder, and ran our
Welcome example application within Tomcat, running at port 8080, the
request URL http://localhost:8080/Welcome/app
will take
us to the Home Page of our Welcome application that we just built.
Illustration 3
In the last two illustrations we have seen that Pages are composed by
aggregating other JWCs. We have also seen that there are two different
ways of declaring a component - Explicit and Implicit declaration. An
explicitly delcared component in Tapestry can be configured partly in
the template and partly in the specification. This allows UI specific
parameters to be configured in the template and content related
parameters to be configured in the specification. Also, a component can
declare some components explicitly in the specifiction and other
components implicitly in the template. This allows simpler light-weight
components to be declared in the template while other highly
configurable components to be declared in the specification thus
avoiding the clutter in the templates. Here, we will mix and match
these approaches for a more pragmatic illustration.
In this slightly enhanced version of our Welcome application, we will
accept the user's name and date-of-birth and display the
information back on a different page. The templates for the two pages
are shown in Listings 7 and 8.
Page Template
<html jwcid="@Shell" title="Welcome Page">
<body jwcid="@Body">
<form jwcid="@Form" listener="ognl:listeners.submit">
Customer Name: <input jwcid="custName" type="text"/><br/>
Date-of-Birth: <input jwcid="dob" type="text" format="MMM dd, yyyy"/>
(Month DD, YYYY)<br/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
Listing 7. CustInfo.html
<html>
<body>
Hello <span jwcid="@Insert" value="ognl:custName"/>!
Welcome to Tapestry!<br/>
Your Date-of-Birth is <span jwcid="@Insert" value="ognl:dob"/>.<br/>
<a href="#" jwcid="@PageLink" page="CustInfo">Next Customer</a>
</body>
</html>
Listing 8. Welcome.html
The components used here are all part of Tapestry's core library.
For information on these components, please refer to the component
reference. The point illustrated here is the flexibility available in
declaraing components. Note that the implicit components, Shell,
Body
and Form
, reside abreast the explicit
components, custName
and dob
, in the same
Page in perfect harmony. Also note that the format
of
date-of-birth field is configured in the template and the
value
is configured in the specification as shown in Listing
9.
Page Specification
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<page-specification class="demo.Welcome">
<property-specification name="custName" type="java.lang.String"/>
<property-specification name="dob" type="java.util.Date"/>
<component id="custName" type="TextField">
<binding name="value" expression="custName"/>
</component>
<component id="dob" type="DatePicker">
<binding name="value" expression="dob"/>
</component>
</page-specification>
Listing 9. CustInfo.page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<page-specification class="demo.Welcome">
<property-specification name="custName" type="java.lang.String"/>
<property-specification name="dob" type="java.util.Date"/>
</page-specification>
Listing 10. Welcome.page
The important thing to note here is that the component class,
demo.Welcome
, is shared between the two Page components. This is
perfectly legal and the framework takes care of providing a fresh
instance to each component thereby making it thread-safe. The new
appearance here is the property-specification
element.
This element is used to define JavaBeans-style properties in the
component class in a declarative fashion. The framework extends the
component class (demo.Welcome
) at runtime and provides the
necessary accessors and mutators for these properties using bytecode
enhancement. The component class can access these properties via
abstract methods to define its behavior as shown in Listing 11.
Page Class
package demo;
import java.util.Date;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;
public abstract class Welcome extends BasePage {
public abstract void setCustName(String custName);
public abstract void setDob(Date dob);
public abstract String getCustName();
public abstract Date getDob();
public void submit(IRequestCycle cycle) {
if (getCustName() != null
&& !getCustName().trim().equals("")
&& getDob() != null) {
Welcome welcome = (Welcome) cycle.getPage("Welcome");
welcome.setCustName(getCustName());
welcome.setDob(getDob());
cycle.activate(welcome);
}
}
}
Listing 11. Welcome.java
The submit
method is a listener method attached to the
Page's form (see template Listing 7) that will be invoked by
the framework automagically when the form is submitted. The
submit
method forwards the request to the Welcome Page
(cycle.activate
) if the user provided both the
custName
and dob
. The listener method passes the
custName
and dob
to the Welcome Page prior to
activating it. The component class can be obtained from
IRequestCycle
(cycle.getPage("Welcome")
)
by providing the component name. cycle
is the object
provided by the framework that represents the current request-response
cycle.
Application Specification
<?xml version="1.0"?>
<!DOCTYPE application PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application name="Tapestry Illustration 3">
<page name="Home" specification-path="CustInfo.page"/>
</application>
Listing 12. Welcome.application
Our Home Page (CustInfo
), not following the default naming
convention, is specified in the application specification. Reusing the
web.xml from previous illustrations and running the Welcome example
application within Tomcat, running at port 8080, with the request URL
http://localhost:8080/Welcome/app
will take us to the
CustInfo Page of our Welcome application that we just built.
The framework - a flyby
In the above illustrations, we saw how we can utilize the services
provided by the framework to build our applications elegantly and
effortlessly. Here, we will skim the framework and unveil some of the
key objects and their mechanics, that provide these services.
In the servlet world, every user request goes through a cycle of being
sent to the server, mapped to a servlet, processed in the servlet using
the query string, Request and Session objects, responded by the servlet
by manually building the markup and finally rendered in the browser.
This cycle entails a lot of convoluted, shoddy coding that frustrates
developers and maintainers. Tapestry framework is a rich API that
deconvolutes this coding and shields the developer from the shoddiness.
The API, built on top of the servlet API, provides the developer with
all the servlet functionality, and a lot more, in a much friendly way,
in the form of objects that forms the framework.
We saw in our illustrations that Pages are made of three parts - the
template, the specification and the component class, and that a Page
may be composed of other components recursively that are in turn each
made of the three parts. This means, when a request is made for a
particular page, the framework has to build a single HTML page from the
three parts of the Page component and recursively from the parts of its
contained components. This build task entails resolving, parsing and
loading the XML specification, instantiating the component class and
resolving, parsing and loading the template. The "loading"
step is where the bulk of the work happens that involves building all
contained components recursively. This build step for each contained
component entails the same steps as the build task of the Page. At the
end of the build task, we have an object, Page object, of type
org.apache.tapestry.IPage
that represents the entire Page.
BasePage
that we saw earlier is an object of this type.
The analagous object that you will have at the end of building a JWC
will be of type org.apache.tapestry.IComponent
. The making
of these objects, as we have just learned, is quite an involved,
time-consuming process, and it would be a huge performance impact to
discard it after one use. This gives rise to the concept of caching.
When a request is made for a particular page, the framework goes to a
Source object, org.apache.tapestry.engine.IPageSource
, to
get an instance of the requested Page. The Page Source object maintains
a pool of Page objects previously built and pops one out for each
request. If the pool is empty, it is responsibility of the Page Source
object to build the Page object and provide it to the framework. Once
the request is processed, the Page is returned back to the Source and
put in the pool for future use. In addition to
IPageSource
, the framework has a few other Source objects that
is used in obtaining other resources -
org.apache.tapestry.engine.ISpecificationSource
and
org.apache.tapestry.engine.ITemplateSource
are a couple of them.
Each of these Source objects caches the objects they provide in a pool
just like the Page Source. Ok, we now know that the framework goes to
the Source object to retrieve the Page, but how does the framework know
which Page was requested in the first place? Well, to answer that, we
need a bit of understanding of the wiring in a Page.
When the framework renders a Page, contained components encode
framework specific information including the Page name into the Page.
Later when the Page is submitted, this information, in the form of
request parameters, is read by the framework and parsed to retrieve the
Page name and other information used by the framework for request
processing. This responsibility of encoding and parsing of request
parameters is entrusted with the framework Service object -
org.apache.tapestry.engine.IEngineService
. All components go to
the Service object to encode or parse request parameters. The framework
comes with a predefined set of Services for different types of user
requests - enter a Tapestry application
(org.apache.tapestry.engine.HomeService
), navigate to a
Page (org.apache.tapestry.engine.PageService
), submit a
form (org.apache.tapestry.engine.DirectService
or
org.apache.tapestry.engine.ActionService
) just to name a few.
When components encode the component-specific request parameters, they
also encode the type of Service object they used to encode their
request parameters. This enables the framework to go to the right
Service object to parse the various components' request parameters.
The other responsibility of the Service object is to manage the request
processing (includes loading the Page and executing the listeners of
the component class) and response rendering (includes setting the
correct content type and rendering the Page) tasks. In addition to the
framework provided Services, the developer is empowered with the
framework API to implement any number of custom Services that one may
need. These custom Services are specified in the application
specification via the <service>
element.
The framework provides support for many other aspects of a Tapestry
application viz. localization, application state management, bytecode
enhancement and script support to name a few. All these, and other,
different aspects of the web application is managed by an aptly named
pivotal framework object, the engine -
org.apache.tapestry.IEngine
. The Engine object is responsible
for instantiating and initializing most of the objects needed for
processing the request including the Service objects, Source objects,
object pools and others. These objects that the Engine initializes are
common to all instances of the application and so are held in the
ServletContext
. Application data common to all instances
of the application is stored in a developer provided
Plain-Old-Java-Object called the Global. The Global object, like other
request-processing objects, is also managed by the Engine and stored in
the ServletContext
. The developer specifies the Global
object via the application specification property
org.apache.tapestry.global-class
. Data specific to a single
instance (user session data) will be stored in a special object called
the "Visit". The Visit is also a developer provided
Plain-Old-Java-Object that is managed by the Engine. The Engine creates
the Visit when necessary and stores it in HTTPSession
. The
developer specifies the Visit object via the application specification
property org.apache.tapestry.visit-class
.
A user request mapped to a Tapestry servlet is delegated to the Engine
for processing. The Engine handles the controller aspect and delegates
it further to the Engine Service. This chain-of-responsibility requires
that the objects needed for request processing be passed around to
every single object that has any processing responsibility. Passing
around a bunch of objects can and will be unmaintainable. To solve this
problem, the framework wraps all objects required for request
processing in a single object of type
org.apache.tapestry.IRequestCycle
. Request Cycle wraps the
HTTPServletRequest
, HTTPServletResponse
,
Engine, Engine Service and the current Page among other objects. With
all this wrapped information, the Request Cycle acts as a facade to the
request processing event.
Conclusion
We have explored, in this document, just the basics of what and how
Tapestry can solve some of the prominent challenges in Java web
development. To write everything about Tapestry requires writing a book
and that's exactly what the creator of this framework has been
doing the last several months. The book, published by Manning, is
expected to be in stores before the coming winter.
Resources
-
For a simple demonstration, download the demo application from MyWorkSpace.
-
You can find more information on Tapestry at Tapestry's Home.
Make sure to check out Tapestry Wiki there.
-
For information on OGNL expressions, please refer to the docs at OGNL.
-
To discuss ideas, issues and features join the Tapestry forum. You may
also find a lot of useful information in the forum archives at
Jakarta and
Gmane.
-
There is an Eclipse plug-in, Spindle, that provides IDE
support for Tapestry. This would be very helpful for a beginner to get
up and running in no time.
-
You may also find the Eclipse plug-ins
Jetty Launcher (for Jetty) and Sysdeo (for
Tomcat) particularly useful for debugging your web applications from
within Eclipse.