Rapid Java Web Application Development with Tapestry

he J2EE world is full of development frameworks, all designed to simplify tedious low-level programming tasks and allow the programmer to get on with more interesting business-related stuff. The most well known probably is Struts, the Model-View-Controller (MVC) framework based largely on Struts Action. More recent frameworks are shifting away from the Struts approach in favor of a higher-level, more object-oriented approach that is component-based and event-driven. Among the most interesting of this new generation of frameworks are JavaServer Faces (JSF), which is backed by industry giants such as Sun, and a dynamic and innovative outsider from the Apache Jakarta project called Tapestry.

Introducing Tapestry

Tapestry is an open-source framework for object-oriented, component-based Java Web application development. Simply put, instead of dealing with the Servlet API or with Struts Actions, the Tapestry programmer stores user data with object properties and handles user actions with event-handling methods.

Another major feature of Tapestry is its use of HTML page templates. In Tapestry, each page is an HTML template containing browser-friendly HTML tags. Unlike JSP, JSTL, or JSF pages, creating Tapestry pages is relatively easy using common Web design tools, and you can preview them in a Web browser.

This article demonstrates a few of the main features of Tapestry, and shows how Tapestry 4, released in December 2005, makes things even easier than previous versions.

Setting Up Tapestry

Tapestry is built on the standard Servlet API, which means it will run on any Java servlet container or application server. You just need to set up the Tapestry servlet in your web.xml file, as illustrated here:

     Introduction to Tapestry Tutorial             app        org.apache.tapestry.ApplicationServlet        0                app        /app    

You can set the servlet name to anything you like. However, modifying the url-pattern is a bit trickier, so you should leave this as is (“/app”).

The Example Application

The best way to get a feel of Tapestry is to work through some examples. In this tutorial, you will work on a Web site designed to sell discount plane tickets for various destinations. The application uses a simple business model (see Figure 1):

  • Promotional offers are represented by the DestinationOffer class, which contains the city of destination and the price.
  • The FeatureDestinations interface provides business methods concerning a set of daily promotional offers.
  • The FeatureDestinationsImpl class is a memory-based implementation of this interface.
Click to enlarge  
Figure 1. UML Model for the Example Application

Your First Tapestry Page

Let’s start off with a simple page. Every Tapestry application has a home page called (appropriately enough) Home. Your home page will display a randomly chosen feature destination. It also will provide a link back to the same page in order to display another offer. The Home page requires two main things:

  • A page template, containing a mixture of HTML code and dynamic Tapestry components (more about components shortly)
  • A corresponding Java class, which provides data for the dynamic parts of the page

In Tapestry 3, each page also needed a page specification file. This file is an XML file describing the mapping between the page template and the Java class. Although page specifications can be useful, and even necessary in more complex pages, Tapestry 4 uses Java 5 annotations and extra tag attributes to greatly reduce the need to write one for each and every page. You won’t need to worry about them for the examples in this article.

The Page Template

Let’s start by looking at the page template:

         Tutorial: Introduction to Tapestry               

Online Travel Discounts

One of today's feature destinations:

A trip to Paris for only $199

The first thing you may notice is that the template is very close to normal HTML. In fact, you can safely display it in a Web browser (see Figure 2). Because it uses Normal HTML tags instead of JSP, JSTL, or JSF tags, a non-Java-savvy Webmaster can easily build and maintain the page templates using ordinary Web design tools. JSF pages, on the other hand, use tags that are quite alien to a traditional HTML Web designer, and that cannot be previewed in a Web browser. Java developers also generally find it more convenient to be able to preview pages without having to deploy to an application server.

Click to enlarge  
Figure 2. Previewing a Tapestry Page

Tapestry Components

The next thing you may notice are the rather strange-looking tag attributes (“jwcid” and the like). These attributes identify Tapestry components, which are responsible for generating (or “rendering”) the dynamic parts of the page (see Figure 3).

Click to enlarge  
Figure 3. Generating a Tapestry Page

Tapestry components are embedded in ordinary HTML tags, as in the following example:

199

The jwcid attribute stands for Java Web Component ID. It identifies the dynamic component you are interested in. The component here is an Insert component, which inserts data retrieved from a property of the corresponding Java class. The value attribute indicates where the dynamic data is to be found. The ognl prefix stands for Object Graph Navigation Language, a powerful expression language that is useful for identifying properties within Java objects. In this particular case, you are fetching the price attribute nested in the featureDestination attribute of the Java class associated with this page. The contents of the tag (199) are just there for previewing purposes; it will be replaced at runtime by the dynamically generated text.

There are 50 or so built-in Tapestry components, which generally cover most Web developer needs, including loops, conditions, and date input fields. If you really need to, you can also write your own quite easily.

The Page Class

Most Tapestry pages with any significant dynamic content have a corresponding Java class, which works with the page template to fill in the dynamic parts of the page. The class should have the same name as the page, and, for convenience, extend the BasePage class (See Sidebar: Where Does Tapestry Look for Its Classes?).

A Tapestry page class typically provides the following:

  • Getters and setters for the objects used in the page
  • Methods that are called at different points in the page lifecycle
  • Methods that will be invoked by user actions on the page

The following is the class for the example Home page, which represents only the first two types of methods:

public abstract class Home extends BasePage implements PageBeginRenderListener{    public abstract DestinationOffer getFeatureDestination();    public abstract void setFeatureDestination(DestinationOffer featureDestination);    public void pageBeginRender(PageEvent event) {       setFeatureDestination(BusinessFactory    							  .getFeatureDestinations()							  .getDailyFeatureDestination());  	    }}

One of the quirks of Tapestry is that page properties are often abstract, rather than being classic JavaBean properties. Another important thing to know is that, for performance reasons, Tapestry pages are pooled and can be shared between users. So you must re-initialize any user-specific data before reusing the page. By declaring abstract getters and setters for a property, you let Tapestry handle all the nitty-gritty details of property cleanup and initialization.

Tapestry also provides a number of interfaces, which you can use to implement methods that are called at certain points in the page lifecycle. One of the most useful is the PageBeginRenderListener interface, which along with the pageBeginRender() method, initialize page properties before the page is generated. Tapestry calls the pageBeginRender() method just before the page is displayed. The example initializes the featureDestination page attribute with a value retrieved from the appropriate business class.

Working with Links and Listeners

One of the major innovations of Tapestry (and other more recent frameworks such as JSF) is that HTML links, submit buttons, and the like are mapped directly to arbitrary Java class methods. In Tapestry, navigation logic and data submission are placed in these “listener” methods or, for simpler cases, directly configured in the Tapestry component itself.

For example, suppose you want to add a button that redisplays the Home screen with a new featured destination. Defining links to other pages is an important part of any Web application, and Tapestry provides several convenient ways of doing so. The easiest is to simply create a link to another Tapestry page, using the PageLink component, as shown here:

Show another feature destination!

This will generate a correct href reference to the application Home page. One of the nice things about links in Tapestry is that you almost never have to worry about the exact form of the URL. In this case, you don’t even need to add a method to the page class.

Now suppose you want to add a “Show Details” button to your Home page. You would need to add a link to the details page and provide a parameter that indicates which offer to display. Rather than forcing you to painstakingly building the URL yourself, Tapestry uses a more object-oriented approach: invoking listener methods. To use this function, you add a new method (say, showDetails()) to the Home Java class with the following signature:

public IPage showDetails(DestinationOffer destination)

Then you call this method using the DirectLink component and indicate where the method parameters should be with the parameters attribute:

  Show Details

Tapestry 4 is very flexible about listener methods. A listener method can be just an ordinary Java method, with or without parameters or a return type.

In this case, you want the method to go to the ShowDetails page. To do this, the method must return an instance of the IPage interface, which each Tapestry page implements. Using Java 5, you can use Tapestry 4 annotations to inject an appropriate getter method to reference the target page, as follows:

    @InjectPage("ShowDetails")    public abstract ShowDetails getShowDetailsPage();        public IPage showDetails(DestinationOffer destination) {    	ShowDetails showDetailsPage = getShowDetailsPage();    	showDetailsPage.setOffer(destination);    	return showDetailsPage;    }

In a nutshell, this method will initialize the ShowDetails Page with the selected offer and then transfer control to this page.

Working with Lists

Tapestry provides an impressive number of useful components out of the box. To illustrate these standard components, suppose you now want a page that lists all your feature destinations. Each feature destination should have a link to the Details page. To do this, you would use the Tapestry For component, as illustrated here:

Destination Price

$ Show Details
Paris 199 Show Details
Rome 299

The source attribute specifies the collection or list of objects to be iterated. The value attribute specifies a placeholder variable used to store the current object value for each iteration.

Astute readers will notice two lines at the end of the table containing dummy data and a strange looking $remove$ component. The remove component allows a Web designer to add HTML tags that can be used for previewing, but which will be discarded during page rendering. This is very useful for correctly formatting and previewing large tables.

The corresponding Java class has to provide getters and setters for the attributes used in the For component. You will also need a showDetails() method for the DirectLink component, similar to the one you used in the Home page. The following is the full class:

public abstract class FeatureDestinationList extends BasePage implements PageBeginRenderListener{    public abstract List getFeatureDestinationOffers();    public abstract void setFeatureDestinationOffers(List offers);        public abstract DestinationOffer getOffer();    public abstract void setOffer(DestinationOffer offer);        public void pageBeginRender(PageEvent event) {    	setFeatureDestinationOffers(BusinessFactory    			                   .getFeatureDestinations()    			                   .getFeatureDestinationOffers());    }        @InjectPage("ShowDetails")    public abstract ShowDetails getShowDetailsPage();        public IPage showDetails(DestinationOffer destination) {    	ShowDetails showDetailsPage = getShowDetailsPage();    	showDetailsPage.setOffer(destination);    	return showDetailsPage;    }    }

Figure 4 shows the final page.

Click to enlarge  
Figure 4. A List Page

Localization

Localization in Tapestry is a remarkably simple task. Each page or component needing to be localized has its own properties file in the WEB-INF directory of your Web application and contains the messages to be displayed. To use them, you can use a special attribute of the span tag as follows:

Online Travel Discounts

The contents of the tag are for previewing purposes only; this text is discarded at runtime and replaced by the localized text.

If you need to use a formatted message, you instead use the Insert component and the special messages object accessible in every page:

      A trip to Paris for only $199  

The first parameter is the name of the message key; the following parameters are message-formatting parameters.

The final page template, complete with localization, is presented here:

        <span key="page-title">Tutorial: Introduction to Tapestry</span>       

Online Travel Discounts

One of today's feature destinations:

A trip to Paris for only $199 Show Details

Show another feature destination!

Show all your feature destinations!

As you can see, it is still quite readable and can still be previewed in a standard Web browser. You can also localize your page templates, which may be useful for pages with a lot of static text that needs to be localized. For example, Home_fr.html would be the French translation of the Home.html template (see Figure 5).

Click to enlarge  
Figure 5. Template Localization

Many More Pros Than Cons

Tapestry is a clean, efficient, and easy-to-use (dare I say “fun”?) framework. In the tradition of Hibernate and Spring, and as opposed to EJB and JSF, it is based on real user needs and experience rather than being designed to a committee-specified standard. Its programming model, although not devoid of a few quirks and idiosyncrasies, is globally clean and elegant. Its architecture (especially where session management and component pooling are concerned) is efficient and highly scaleable. It provides a large number of very useable standard components out of the box, and custom components are relatively easy to develop if necessary. And important Web-development requirements such as localization are also well integrated.

On the negative side, it is less well known than Struts or JSF, and not backed by any industry giants. Documentation is not as abundant as it is for Struts or JSF. And although the Tapestry community is active and growing fast, experienced Tapestry developers are probably relatively hard to find.

Nevertheless, Tapestry is a fine piece of architecture and is well worth consideration for your next Web development project.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: