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.
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.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name> Introduction to Tapestry Tutorial</display-name>
<servlet>
<servlet-name> app</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-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").
DestinationOffer class, which contains the city of destination and the price.FeatureDestinations interface provides business methods concerning a set of daily promotional offers.FeatureDestinationsImpl class is a memory-based implementation of this interface.
![]() | |
| Figure 1. UML Model for the Example Application |
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.
<html>
<head>
<title>Tutorial: Introduction to Tapestry</title>
</head>
<body>
<h3>Online Travel Discounts</h3>
<h4>One of today's feature destinations:</h4>
<p>
A trip to
<span jwcid="@Insert" value="ognl:featureDestination.destination">Paris</span>
for only $<span jwcid="@Insert" value="ognl:featureDestination.price">199</span>
</p>
</body>
</html>
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.
![]() | |
| Figure 2. Previewing a Tapestry Page |
![]() | |
| Figure 3. Generating a Tapestry Page |
Tapestry components are embedded in ordinary HTML tags, as in the following example:
<span jwcid="@Insert" value="ognl:featureDestination.price">199</span>
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.
BasePage class (See Sidebar: Where Does Tapestry Look for Its Classes?).
A Tapestry page class typically provides the following:
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.
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:
<a href="#" jwcid="@PageLink" page="Home">Show another feature destination!</a>
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:
<a href="#" jwcid="@DirectLink"
listener="listener:showDetails"
parameters="ognl:{ featureDestination }">Show Details</a>
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.
For component, as illustrated here:
<table cellspacing="6">
<tr>
<td>Destination</td>
<td>Price</td>
<td></td>
</tr>
<tr>
<td colspan="3"><hr/></td>
</tr>
<tr jwcid="@For" source="ognl:featureDestinationOffers" value="ognl:offer" element="tr">
<td><span jwcid="@Insert" value="ognl:offer.destination"/></td>
<td>$<span jwcid="@Insert" value="ognl:offer.price"/></td>
<td>
<a href="#" jwcid="@DirectLink"
listener="listener:showDetails"
parameters="ognl:{ offer }">Show Details</a>
</td>
</tr>
<tr jwcid="$remove$">
<td>Paris</td>
<td>199</td>
<td><a href="#">Show Details</a></td>
</tr>
<tr jwcid="$remove$">
<td>Rome</td>
<td>299</td>
</tr>
</table>
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<DestinationOffer> getFeatureDestinationOffers();
public abstract void setFeatureDestinationOffers(List<DestinationOffer> 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.
![]() | |
| Figure 4. A List Page |
<span key="title">Online Travel Discounts</span>
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:
<span jwcid="@Insert"
value="ognl:messages.format('feature-destination',
featureDestination.destination,
featureDestination.price)">
A trip to Paris for only $199
</span>
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:
<html>
<head>
<title><span key="page-title">Tutorial: Introduction to Tapestry</span></title>
</head>
<body>
<h3><span key="title">Online Travel Discounts</span></h3>
<h4>
<span key="todays-feature-destination">One of today's feature destinations:
</span>
</h4>
<p>
<span jwcid="@Insert"
value="ognl:messages.format('feature-destination',
featureDestination.destination,
featureDestination.price)">
A trip to Paris for only $199
</span>
<a href="#" jwcid="@DirectLink"
listener="listener:showDetails"
parameters="ognl:{ featureDestination }">
<span key="show-details">Show Details</span>
</a>
</p>
<p>
<a href="#" jwcid="@PageLink" page="Home">
<span key="show-another">Show another feature destination!</span>
</a>
</p>
<p>
<a href="#" jwcid="@PageLink" page="FeatureDestinationList">
<span key="show-all">Show all your feature destinations!</span>
</a>
</p>
</body>
</html>
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).
![]() | |
| Figure 5. Template Localization |
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.
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |