Tapestry is a component-based web application framework, written in Java. Tapestry is more than a simple templating system; Tapestry builds on the Java Servlet API to build a platform for creating dynamic, interactive web sites. More than just another templating language, Tapestry is a real framework for building complex applications from simple, reusable components. Tapestry offloads much of the error-prone work in creating web applications into the framework itself, taking over mundane tasks such as dispatching incoming requests, constructing and interpretting URLs encoded with information, handling localization and internationalization and much more besides.
The "mantra" of Tapestry is "objects, methods and properties". That is, rather than have developers concerned about the paraphanlia of the Servlet API: requests, responses, sessions, attributes, parameters, URLs and so on, Tapestry focuses the developer on objects (including Tapestry pages and components, but also including the domain objects of the application), methods on those objects, and JavaBeans properties of those objects. That is, in a Tapestry application, the actions of the user (clicking links and submitting forms) results in changes to object properties combined with the invocation of user-supplied methods (containing application logic). Tapestry takes care of the plumbing necessary to connect these user actions with the objects.
This can take some getting used to. You don't write servlets in Tapestry, you write
s. You don't build URLs to servlets either -- you use an existing component (such as
) and configure its
parameter to invoke your listener method. What does a listener method do? It
interacts with backend systems (often, stateless session EJBs) or does other
bookkeeping related to the request and selects a new page to provide a response to
the user ... basically, the core code at the center of a servlet. In Tapestry, you
write much less code because all the boring, mechanical plumbing (creating URLs,
dispatching incoming requests, managing server-side state, and so forth) is the
responsibility of the framework.
This is not to say the Servlet API is inaccessible; it is simply not relevant to a typical Tapestry user.
This document describes many of the internals of Tapestry. It is not a tutorial, that is available as a separate document. Instead, this is a guide to some of the internals of Tapestry, and is intended for experienced developers who wish to leverage Tapestry fully.
Tapestry is currently in release 4.0, and has come a long way in the last couple of years. Tapestry's focus is still on generating dynamic HTML pages, although there's plenty of support for XHTML, WML and other types of markup as well.
Nearly all of Tapestry's API is described in terms of interfaces, with default implementations supplied. By substituting new objects with the correct interfaces, the behavior of the framework can be changed significantly. A common example is to override where page and component specifications are stored (perhaps in a database).
Finally, Tapestry boasts extremely complete JavaDoc API documentation. This document exists to supplement that documentation, to fill in gaps that may not be obvious. The JavaDoc is often the best reference.
Perhaps the hardest part of understanding Tapestry is the fact that it is component-centric not operation-centric . Most web technologies ( Struts , servlets, PHP, etc.) are operation-centric. You create servlets (or Actions, or what have you) that are invoked when a user clicks a link or submits a form. You are responsible for selecting an appropriate URL, and the name and type of any query parameters, so that you can pass along the information you need in the URL.
You are also responsible for connecting your output pages (whether they are
templates, or some other form of templating technology) to those operations.
This requires you to construct those URLs and get them into the
attribute of your <a> tag, or into the
attribute of your <form> tag.
Everything is different inside Tapestry. Tapestry applications consist of pages; pages are constructed from smaller components. Components may themselves be constructed from other components. Every page has a unique name, and every component within a page has its own unique id ... this is a component object model . Effectively, every component has an address that can easily be incorporated into a URL.
In practical terms, your don't write a servlet for the
operation. In fact, you don't even write an
component. What you do is take an existing component, such as
, and configure it. When the component renders, it will create a callback URL.
When you click that link, the callback URL (which includes the name of the page
and the id of the component within the page) will invoke a method on the
component ... and
method invokes your application-specific
Listener methods in Tapestry are very similar in intent to delegates in C#. In both cases, a method of a particular object instance is represented as an object. Calling this a "listener" or a "listener method" is a bit of a naming snafu; it should be called a "delegate" and a "delegate method" but the existing naming is too deeply entrenched to change any time soon.
You supply just the listener method ... not an entire servlet. Tapestry takes care that your listener method is invoked at the right time, under the right conditions. You don't have to think about how to build that URL, what data goes in the URL, or how to hook it up to your application-specific code--that's all handled by the framework.
Tapestry divides an application into a set of pages. Each page is assembled from Tapestry components. Components themselves may be assembled from other components ... there's no artificial depth limit.
Tapestry pages are themselves components, but are components with some special responsibilities.
All Tapestry components can be containers of other components. Tapestry pages, and most user-defined components, have a template, a special HTML file that defines the static and dynamic portions of the component, with markers to indicate where embedded components are active. Components do not have to have a template, most of the components provided with Tapestry generate their portion of response in code, not using a template.
Components may have one or more named parameters which may be set (or, more correctly, "bound") by the page or component which contains them. Unlike Java method parameters, Tapestry component parameters may be bidirectional; a component may read a parameter to obtain a value, or write a parameter to set a value.
Most components are concerned only with generating HTML. A certain subset of components deal with the flip-side of requests; handling of incoming requests. Link classes, such as PageLink and DirectLink create clickable links in the rendered page and are involved in dispatching to user-supplied code when such a link is triggered by clicking it.
Other components, Form , and the form control components ( TextField , PropertySelection , Checkbox , etc.), facilitate HTML forms. When such components render, they read properties from application objects so as to provide default values. When the forms are submitted, the components within the form read HTTP query parameters, convert the values to appropriate types and then update properties of application objects.
Tapestry has evolved its own jargon over time.
The Engine is a central object, it occupies the same semantic space in Tapestry that the HttpSession does in the Servlet API. The Engine is ultimately responsible for storing the persistent state of the application (properties that exist from one request to the next), and this is accomplished by storing the Engine into the HttpSession. This document will largely discuss the default implementation, with notes about how the default implementation may be extended or overriden, where appropriate.
Engine services are the bridge between servlets and URLs and the rest of Tapestry. Engine services are responsible for encoding URLs, providing query parameters that identify, to the framework, the exact operation that should occur when the generated URL is triggered (by the end user clicking a link or submitting a form). Services are also responsible for dispatching those incoming requests. This encapsulation of URL encoding and decoding inside a single object is key to how Tapestry components can flexibily operate without concern for how they are contained and on which page ... the services take into account page and location when formulating URLs.
The Visit object is an application-defined object that acts as a focal point for all server-side state (not associated with any single page). Individual applications define for themselves the class of the Visit object. The Visit is stored as a property of the Engine, and so is ultimately stored persistently in the HttpSession.
The Global object is also application-specific. It stores information global to the entire application, independent of any particular user or session. A common use for the Global object is to centralize logic that performs JNDI lookups of session EJBs.
Tapestry is tightly integrated with OGNL , the Object Graph Navigation Language. OGNL is a Java expression language, which is used to peek into objects and read or update their properties. OGNL is similar to, and must more powerful than, the expression language built into the JSP 2.0 standard tag library. OGNL not only support property access, it can include mathematical expressions and method invocations. It can reference static fields of public classes. It can create new objects, including lists and maps.
The simplest OGNL expressions are property names, such as
, which is equivalent to method
if the expression is being used to update a property). The "Navigation" part
comes into play when the expression is a series of property names, such as
, which is equivalent to
... though care must always be taken that the intermediate properties (
in this example) are not null.
OGNL is primarily used to allow two different objects (such as a page and a component contained by that page) to share information.
Since its initial introduction in early 2000, Tapestry has been in a constant state of evolution, driven by feedback from its community. Tapestry took a big leap forward in 2003. During that period, the Tapestry project moved from SourceForge to Jakarta . This was also the transition from release 2.3 to release 3.0 (the final 3.0 release occuring in April 2004).
Tapestry 3.0 was designed to radically change how Tapestry applications were created. It introduced a kind of RAD (Rapid Application Development) support in the form of implicit components ... a way of specifying a component's type and parameters in place in the HTML template, which is much more familiar to JSP and PHP developers.
Tapestry 3.0 also saw the introduction of line precise error reporting , in which runtime errors are related back to relevant lines in the HTML template or specification file. Further, 3.0 introduced the <property> element, and the bytecode enhancement technology behind it.
Tapestry 4.0 represents an even more radical leap beyond 3.0 by introducing a new, sophisticated infrastructure on top of the HiveMind microkernel. This new backbone provides the support necessary to meet the needs of Tapestry's much larger community ... including support for prettier URLs, integration of Tapestry and the Java Portlet API, and modularization of applications (allowing the use of folders, and thus, J2EE declarative security). In addition, a more sophisticated approach to implementing connected parameter properties has been introduced in release 4.0, and more flexibility for storing session-specific state as HTTP query parameters has been provided. For JDK 1.5 users, the XML page and component specifications can now be sidelined in favor of Java annotations.
A further future direction, in the Tapestry 5.0 timeframe (the far future), is to rethink the component object model such that the classes you write do not sub-class Tapestry base classes. Page and component Java classes will be simple POJOs (Plain Old Java Objects) and will have any Tapestry dependencies injected into them.