ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Tapestry: A Component-Centric Framework

by Hemangini Kappla
09/27/2006

Tapestry is an open source web application framework written in Java. Highly-interactive and content-rich applications can be easily developed using this framework.

Tapestry offers advantages including a high-performance coarse-grained pooling strategy, high code-reuse, line-precise error reporting, and lots more. Tapestry applications can be run on any servlet container since the apps are 100 percent container agnostic.

Adoption of the tapestry framework eliminates writing servlets and building URLs and query parameters to servlets, even though Tapestry is built on top of the servlet API. Developers can concentrate on coding the application's functionality because Tapestry takes care of the "plumbing" code (creating URLs, dispatching incoming requests, managing server-side state, and so forth).

The Tapestry Distribution

The Tapestry distribution is available for download at the Tapestry home page on the Apache site.

The Tapestry distribution consists of the following pieces, illustrated in Figure 1:

Contents of the Tapestry distribution
Figure 1. Contents of the Tapestry distribution

Tapestry has a number of additional runtime dependencies, which must be downloaded and packaged with the above JAR files in the WEB-INF/lib folder of Tapestry web applications. This can be done by the Ant build scripts for Tapestry, which will automatically download these dependencies. The build scripts require some configuration to work; details are available at the Tapestry Wiki. Alternatively, these dependencies may be downloaded from Howard Lewis Ship's quick-start directory as tapestry-libraries.tar.gz (21.7 MB). Copy the dependencies to the WEB-INF/lib folder or any shared library folder to make the JAR files available to the application that is to be developed based on the Tapestry framework. Since Tapestry is a library, this is all that needs to be done for the installation part.

The Component World of Tapestry

A Tapestry application is different from other traditional web applications based on operation-centric frameworks (e.g., Struts, PHP, etc.). A Tapestry application is a collection of web pages, and each web page is in turn constituted of components. The web pages in Tapestry are nothing but simple HTML pages (not JSPs) and are called as HTML templates. The entire web application is built from these templates and specifications, using minimal Java code.

The Tapestry framework acts as a layer between the Tapestry application and the Java Servlet Container. Tapestry uses the standard Servlet API objects and functions within the existing Servlet container. Since the application components access the Tapestry components, the developed application is totally oblivious of the Servlet API.

In Tapestry-based applications, all the elements on a web page (forms, links, test-boxes, etc.) are represented as components. At the heart of the design is the interface org.apache.tapestry.IComponent. All objects that may be used to provide dynamic content on a Tapestry-based page are represented by this class.

Figure 2 shows some of the classes and their relations. In short, everything from forms, components of forms like text areas, text fields, hidden fields, submit buttons, and links are represented as objects, methods and properties (each component having its own id). This is the unique Component Object Model (COM).

Thumbnail, click for full-size image.
Figure 2. Class diagram showing the relation between Tapestry components (Click for full-size image)

Tapestry and MVC

Model-View-Controller (MVC) is a design paradigm based on separating the user interface of an application from its domain logic. Tapestry's MVC Model 2 implementation is one of the most pure implementations of the pattern.

An Example

The simplest way to understand the Tapestry framework is to see it in action. The following is an example that demonstrates how easily applications can be developed using this framework.

By using an example of an online bank, a user can view his balance, place an order for a check book, or edit his profile. Like every application there is a home page called Home.html.

<html>
  <head>
    <title>An Online Bank </title>
  </head>
  <body>
    <h1> Welcome to MyBank>/h1>    
    <h2>Please select action:>/h2>
    <tr>
      <td> 
      <a href="" jwcid="viewBalanceLink">View Balance</a> </td>
    .
    .
    .
    </tr>
  </body>
</html>

Notice how this is simple HTML, barring one element: jwcid, which means Java web component ID and is unique to Tapestry. As mentioned earlier, every component in the Tapestry framework has its own ID. When this HTML page is being rendered, a component is created and identified by this jwcid. So, how will Tapestry determine which type of component to create? This information has to be specified in the Home.page file. Here is a snippet from Home.page:

<page-specification class="com.mybank.Home">
    <component id="viewBalanceLink" type="DirectLink">
.
.
.
</page-specification>

In this case, a component of the type DirectLink, identified by jwcid viewBalanceLink, will be created. Behind the scenes, before the page is rendered, Tapestry creates a page object of the type org.apache.tapestry.html.BasePage. The second step is to create all the components described in the .page file and associate them with the Page object. Then the Page reads the HTML file for information to render the page. When it encounters the component with the ID viewBalanceLink, the control is transferred to this object for rending the link. This component will output the resultant HTML link to the page to be rendered. In this entire process, the HTML page is used only for reference and is called the template. Here is the code snippet from Home.java:

public abstract class Home extends BasePage{
    
    public abstract AccountBalance getAccountBalancePage();
    .
    .
    .
}

The page is rendered successfully.

But why is the method abstract and where is the implementation? Well, Tapestry does that bit of work for you. When Tapestry comes across an unimplemented getter method, it creates a property for it.

So, what happens when a user clicks on this link? We've put the following code in Home.page:

<component id="viewBalanceLink" type="DirectLink">
  <binding name="listener" value="listener:onClickViewBalanceLink"/>
</component>

Here we can see a new expression: listener. When a user clicks the link, the listener that has a binding with that component will be called by Tapestry. So, a listener method has to be defined in the page's class:

public IPage onClickViewBalanceLink() {
        //do something to give a Account Balance Page
}

The effect of clicking the link in this scenario is to display the account balance page. So, we have a AccountBalance.html, AccountBalance.page, and the Page object class AccountBalance.java. But how do we define a relation between the home page and AccountBalance page? Have a look at this:

public abstract class Home extends BasePage{
    .
    .
    @InjectPage("AccountBalance")
    public abstract AccountBalance getAccountBalancePage();
    
    
    public AccountBalance onClickViewBalanceLink() {
        return getAccountBalancePage();
    .
        .
}

The most interesting change that we have made here is the annotation: @InjectPage. Annotations are a new feature of J2SE 5.0 (Tiger) and are used to provide metadata. Annotations take the form of an at sign (@), followed by the annotation name. You thwn supply data to the annotation--when data is required--in name=value pairs.

Using @InjectPage annotation, we have injected the AccountBalance page into our component. Now this page is available for our listener method to change its value, interact with a backend system or simply display the page named "AccountBalance" as the response after this listener method returns. And this is exactly what we have done!

On the account balance page, the balance is displayed using the tag:

<h2>Your account balance is: $<span jwcid="accountBalance"> </span> </h2>

Needless to say, that the accountBalance is an attribute of the AccountBalance.java class. The same result can be displayed if we include the following tag instead:

<h2>Your account balance is: 
    $<span jwcid="@Insert" value="ognl:accountBalance"> </span>
</h2>

Note that there is an Object Graph Navigational Language (OGNL) expression in the value. OGNL is a powerful open source expression language used to get and set properties of Java objects. When the Tapestry comes across ognl: it knows that what follows it is an OGNL expression and not a string constant.

Validation and Error Reporting

Consider a scenario in our banking application in which the user wants to edit his profile. On the "Edit Profile" page there are certain mandatory fields such as name and date of birth. These validations can be done with minimal coding in Tapestry. In the banking application, we have a HTML file for updating personal details (PersonalDetails.html), an XML template (PersonalDetails.page), and the Page Object class(PersonalDetails.java).

Starting by modifying the Page Object class, we add an ValidationDelegate attribute type. This attribute holds the list of error messages, in case the validation fails. But how does Tapestry know what to validate? Here's how: we update the page specification (PersonalDetails.page) to inform Tapestry that the component name is mandatory. Also, we bind the ValidationDelegate to the Form (personalDetailsForm) to record any validation errors in its components.

<component id="personalDetailsForm" type="Form">
        <binding name="delegate" value="beans.delegate"/>
            .
            .
</component>
    .
    .
        <component id="name" type="TextField">
    .
    .
    <binding name="validators" value="validators:required"/>
    .
    .
</component>

Further, we need to have a component to render the error message, after the validation fails. This object records the error and is stored in the ValidationDelegate object.

<component id="errormsg" type="Delegator">
    <binding name="delegate" value="currentFieldTracking.errorRenderer"/>
</component>

This way, no code needs to be present in the Page object class for this validation; although for customized validations we can introduce that part in the onSubmit() of the Page class.

Lastly, on the HTML page, following changes need to be made to display the recorded error messages:

<span jwcid="errormsgs">
    <span jwcid="errorOccured"><li><span jwcid="errormsg"/></li></span>
</span>

Conclusion

This article shows how simple it is to develop a web application using Tapestry. Tapestry adopts a component approach to web development, making it possible to move all the boring plumbing code out of the application and into the framework. Applications developed using the Tapestry framework naturally adapt to the Tapestry philosophy of simplicity, consistency, efficiency, and feedback.

Resources

Hemangini Kappla currently works for Mphasis, an EDS company in Bombay, India.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.