Introduction to Jakarta Tapestry

Introduction to Jakarta Tapestry

By Rob Smith, OCI Senior Software Engineer

May 2004


Introduction

Jakarta Tapestry is an open-source framework for creating Java web applications. At this point you're probably saying, "Not another framework!" And, in most cases I would agree with you. However, if you spend a little time looking at it you might find Tapestry differentiates itself from other frameworks and is worth a serious look.

Tapestry is a component-based framework that makes developing web applications very similar to developing traditional GUI applications. You write web applications using Tapestry without being concerned about the operation-centric Servlet API. To quote from the Tapestry website, "Tapestry reconceptualizes web application development in terms of objects, methods and properties instead of URLs and query parameters." Tapestry 3.0 has recently been released with a large number of improvements and new features.

Tapestry's Goals

Simplicity

Tapestry applications contain far less code than traditional Servlet applications. Most traditional Servlet applications include such boring and repetitive tasks as parsing query parameters, manipulating the HttpSession object, and building URLs. Tapestry eliminates much of the uninteresting "plumbing" code that developers in a traditional Servlet application have to concern themselves with and allows them to spend their time writing application-specific logic.

Consistency

Tapestry provides a consistent approach to developing pages for a web application. This helps eliminate the guesswork that occurs in traditional Servlet applications. Pages in a Tapestry application all work similarly because they are built with the same reusable components.

Efficiency

Tapestry applications are highly scalable and Tapestry is implemented utilizing caches and object pools to minimize processing time for each request. Tapestry applications have similar performance to traditional Servlet applications.

Feedback

Anyone that has ever developed a Servlet/JSP application has no doubt spent a lot of time looking through stack traces in the browser window only to find out that something in the web.xml file was incorrect. The Tapestry framework does an excellent job of error reporting, most notably it includes the file and line number that caused the error.

Comparison to Struts

Since Apache Struts is probably the most widely used web application framework today it is only fair to compare Tapestry to it. The following are observations from developing a few simple Tapestry applications for this article and working on a few Struts projects here and there.

Struts Advantages

Tapestry Advantages

Tapestry Architecture

The Tapestry framework is an extension built on the standard Servlet API. The framework requires J2SDK 1.2 or higher and a Servlet API 2.2 (or higher) compliant application server/Servlet container.

A Tapestry application consists of a number of uniquely named pages. Each page consists of a template and is constructed from reusable components. A template is standard HTML markup except for some additional attributes and tags that tell Tapestry which part of the page is made up of Tapestry components.

Simple Tapestry Application

To best describe the pieces needed to construct a Tapestry page we will look at the code for a Pig Latin translator application. The application will consist of a single page that takes as an input a text value, translates it to Pig Latin, and shows the user the translated value.

In a Tapestry application each page is composed of three pieces; an HTML page template, a page specification, and a Java class. Here is a screenshot of what the page will look like.

Tapestry Pig Latin Translator

The page template looks like regular HTML except for some additional attributes and tags that tell Tapestry which part of the page is made up of Tapestry components. Page templates are stored in the root context folder of the web application. By default Tapestry looks for and renders a page named "Home" on startup. Although it is possible to override this behavior it is simpler just to follow Tapestry's convention.

Home.html

  1. <html>
  2. <head>
  3. <title>Tapestry Pig Latin Translator</title>
  4. </head>
  5. <body>
  6. <h1>Pig Latin Translator</h1>
  7. <form jwcid="@Form"① listener="ognl:listeners.submit"②>
  8. <table border="1">
  9. <tr>
  10. <td>Value to Translate:</td>
  11. <td>
  12. <input type="text" jwcid="@TextField"value="ognl:inputValue"/>
  13. </td>
  14. </tr>
  15. <tr>
  16. <td>Pig Latin:</td>
  17. <td>
  18. <jwcid="@Insert"value="ognl:pigLatinValue"></jwcid>
  19. </td>
  20. </tr>
  21. </table>
  22. <input type="submit" jwcid="@Submit"value="Translate"/>
  23. </form>
  24. </body>
  25. </html>

For the most part the page template is plain HTML with a few special attributes used by Tapestry. A benefit of this templating mechanism is that Tapestry page templates can be created and previewed in standard WYSIWYG editors. The parts of the markup that describe Tapestry components are numbered and highlighted.

The jwcid attribute used in the markup refers to the Java Web Component ID of the Tapestry component to use. In this snippet we are utilizing implicit components. Implicit components are those that are declared directly in the HTML template; the @ symbol that prefixes the jwcid attribute tells Tapestry that it is an implicit component.

In the Pig Latin Translator page template Form①, TextField③, Insert④ and Submit⑤ components are used. These are just 4 of the over 40 components provided with the Tapestry framework. In a later example we will see how to use declared components. Declared components are components that are defined in the page specification.

In the preceding HTML template, component parameters are used as well. For instance the Form① component has a listener② parameter that specifies the name of the method to invoke in the Page class when the form is submitted. The ognl: prefix used throughout the HTML template refers to the Object Graph Navigation Language (OGNL). OGNL is a powerful open source expression language used to bind component properties to Page class properties.

Next we will look at the page specification for the Pig Latin translator. The page specification is a short XML document with a .page extension that has numerous responsibilities, but its most basic is to specify the page class. Page specifications are stored in the webapp's WEB-INF folder.

Home.page

  1. <?xml version="1.0"?>
  2. <!DOCTYPE page-specification PUBLIC
  3. "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
  4. "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
  5. <page-specification class="Home">
  6. <property-specification name="inputValue" type="java.lang.String"></property>
  7. <property-specification name="pigLatinValue" type="java.lang.String"></property>
  8. </page-specification>

The page-specification root element has a class attribute that specifies the Java class to instantiate for the page. The class specified in the page specification must implement the org.apache.tapestry.IPage interface. The page specification also defines two property-specification elements that direct Tapestry to create new properties in the Page class.

The Tapestry framework provides the org.apache.tapestry.html.BasePage class, which implements the IPage interface. Page classes are stored under the WEB-INF/classes folder alongside any other class in your web application.

Home.java

  1. import org.apache.tapestry.html.BasePage;
  2. import org.apache.tapestry.IRequestCycle;
  3.  
  4. public abstract class Home extends BasePage {
  5. public abstract String getInputValue();
  6. public abstract void setInputValue(String inputValue);
  7.  
  8. public abstract String getPigLatinValue();
  9. public abstract void setPigLatinValue(String pigLatinValue);
  10.  
  11. public void submit(IRequestCycle cycle) {
  12. String inputValue = getInputValue();
  13. String pigLatinValue = new PigLatinTranslator().translate(inputValue);
  14. setPigLatinValue(pigLatinValue);
  15. }
  16. }

The first thing that you may notice is that this class is abstract. It also has abstract methods for accessing the inputValue and pigLatinValue properties. This is utilizes a feature of Tapestry that creates a subclass of the page with the necessary methods and fields at runtime. We specify two property-specification elements that correspond to the abstract methods in our Page class. Tapestry uses this information to create the subclass at runtime.

The submit method is invoked when the form is submitted. This occurred because the listener attribute of the Form component in the page template is set to ognl:listeners.submit. This means that the listener named submit is accessed from the Page's listeners property.

All pages and components inherit a property named listeners from the org.apache.tapestry.AbstractComponent class. After the submit method completes the page will be rendered with the Pig Latin value for the text the user entered to be translated.

Last but not least is the web.xml web deployment descriptor. Tapestry, like many popular web applications frameworks, consists of only one Servlet but still requires a deployment descriptor. The deployment descriptor should be stored in the WEB-INF folder.

web.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE web-app PUBLIC
  3. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">
  5. <web-app>
  6. <display-name>Tapestry Pig Latin Translator</display-name>
  7.  
  8. <servlet>
  9. <servlet-name>tapestry</servlet-name>
  10. <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
  11. <load-on-startup>1</load-on-startup>
  12. </servlet>
  13.  
  14. <servlet-mapping>
  15. <servlet-name>tapestry</servlet-name>
  16. <url-pattern>/app</url-pattern>
  17. </servlet-mapping>
  18. </web-app>

Although the Pig Latin translator application is very simple it should give you a basic understanding of the three pieces that make up a page in a Tapestry application. It also demonstrates how little code is needed to create a page for a Tapestry application.

Form Input Validation

Tapestry provides components for validating user input. The validation subsystem is centered on the ValidField component. We will make use of the ValidField component ahead in the login application. The ValidField component helps provide useful feedback to the user in terms of client-side validation and visual indications of errors in the form.

Localization

Localization of a Tapestry application is relatively simple. Tapestry allows localization of text and images. To localize page content one can either add a properties file for each page that contains localized messages or provide a localized version of the template. The approach of using a message properties file for each page is easier to manage and maintain than a huge application wide message properties file. It may be useful to create localized versions of the entire page template if pages differ across locales in more than just language, for example having different layouts or composed of different components. We'll utilize Tapestry's localization ahead in the login application.

Creating Components

Tapestry ships with more than 40 components ready for you to use when building your apps. If you are interested in learning more about the components that come with Tapestry, a good reference is the Tapestry Component Reference. To see some of the Tapestry components at work visit the Tapestry Component Workbench. If you find yourself in need of a component that Tapestry does not provide you create it yourself. Creating your own Tapestry components is similar to creating pages in a Tapestry application. A typical Tapestry component consists of a component specification XML document, an HTML component template and a Java class that implements the org.apache.tapestry.IComponent interface. This topic is a little beyond the scope of this article, but if you are interested in learning how to create your own components there is a good article Designing Tapestry Mega-Components written by the lead developer of Tapestry and the author of Tapestry In Action , Howard Lewis Ship.

Tapestry Login Application

You have seen a few basic Tapestry features in the Pig Latin translator application. Rather than overwhelm you with a very complex application trying to demonstrate all of Tapestry's features I feel it will be more useful to walk through a simple application to get a feel for working with Tapestry. This application will demonstrate how Tapestry handles page navigation, localization, and validation amongst other things.

Here is a screenshot of the Home page followed by its page template.

Tapestry-login-home

Home.html

  1. <html>
  2. <head>
  3. <title>Welcome to the Tapestry Login Application</title>
  4. </head>
  5.  
  6. <body>
  7. <h1>Welcome to the Tapestry Login Application</h1>
  8. <span jwcid="@PageLink"① page="Login">Login</span>
  9. </body>
  10. </html>

The Home page template is standard HTML with the exception of the jwcid attribute that defines the use of the Tapestry PageLink① component. The PageLink component creates a hyperlink to the Login page in the application. Since the Home page does not have any dynamic behavior it does not require a page specification or page class.

Here is a screenshot of the Login page followed by its page template.

login

Login.html

  1. <html>
  2. <head>
  3. <title>
  4. <span key="title">①Login</span>
  5. </title>
  6. </head>
  7.  
  8. <body jwcid="@Body">
  9. <span jwcid="@Conditional" condition="ognl:beans.delegate.hasErrors">
  10. <div style="color: red">
  11. <span jwcid="@Delegator" delegate="ognl:beans.delegate.firstError">
  12. Error Message
  13. </span>
  14. </div>
  15. </span>
  16. <p style="font-weight: bold" >
  17. <span key="hint">Hint: Your password is your username spelled backwards.</span>
  18. </p>
  19.  
  20. <form jwcid="@Form" listener="ognl:listeners.login" delegate="ognl:beans.delegate">
  21. <table>
  22. <tr>
  23. <td align="right">
  24. <span jwcid="@FieldLabel" field="ognl:components.inputUsername"⑥>
  25. Username:
  26. </span>
  27. </td>
  28. <td>
  29. <input type="text" jwcid="inputUsername"value="simpson_h" size="30"/>
  30. </td>
  31. </tr>
  32.  
  33. <tr>
  34. <td align="right">
  35. <span jwcid="@FieldLabel" field="ognl:components.inputPassword">
  36. Password:
  37. </span>
  38. </td>
  39. <td>
  40. <input type="text" jwcid="inputPassword" hidden="true" value="" size="30"/>
  41. </td>
  42. </tr>
  43.  
  44. <tr>
  45. <td colspan="2" align="center">
  46. <input type="submit" jwcid="@Submit" value="message:login"/>
  47. </td>
  48. </tr>
  49. </table>
  50. </form>
  51. </body>
  52. </html>

For the most part this page template is plain HTML. We see how to use Tapestry's localization feature from the page template by using a span element with a key attribute that maps to a property in the Login.properties file①. The Body component② is declared because it is required for client-side JavaScript validation to work.

Setting the delegate attribute of the Form component⑤ enables form validation. The delegate attribute is the name of the org.apache.tapestry.valid.IValidationDelegate implementation defined in the page specification (in the next section). If a validation error occurs we use the Conditional component③ to determine if the delegate has any errors and if it does the first error will be displayed to the user④. The Conditional component will render its body if the ognl expression ognl:beans.delegate.hasErrors is true. All pages and components inherit a property named beans from the AbstractComponent class. The beans property is an instance of org.apache.tapestry.IBeanProvider that is able to retrieve beans defined in the page specification by name.

The FieldLabel component⑥ is used to display the label for the inputUsername ValidField component. The FieldLabel component also coordinates with the Form's validation delegate to visually indicate fields that contain errors.

The inputUsername component⑦ is an example of a declared component. A declared component is a component that is declared in the page specification. Both the inputUsername and inputPassword components are declared and have associated FieldLabel components to display their displayName properties.

Here are the message properties for the Login Page. The Login.properties file resides in the WEB-INF folder with the Page specification.

Login.properties

  1. title = Login to the Application
  2. hint = Hint: Your password is your username spelled backwards.
  3. login = Login
  4. username = Username:
  5. password = Password:
  6. invalidpassword = Invalid Password

Here is the page specification for the Login page.

Login.page

  1. <?xml version="1.0"?>
  2. <!DOCTYPE page-specification PUBLIC
  3. "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
  4. "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
  5.  
  6. <page-specification class="com.ociweb.tapestry.Login">
  7. <bean name="delegate" class="org.apache.tapestry.valid.ValidationDelegate"></bean>
  8.  
  9. <bean name="requiredValidator"
  10. class="org.apache.tapestry.valid.StringValidator">
  11. <set-property name="required" expression="true"></set>
  12. <set-property name="clientScriptingEnabled" expression="true"></set>
  13. </bean>
  14.  
  15. <property-specification name="username" type="java.lang.String"></property>
  16. <property-specification name="password" type="java.lang.String"></property>
  17.  
  18. <component id="inputUsername" type="ValidField">
  19. <message-binding name="displayName" key="username"></message>
  20. <binding name="validator" expression="beans.requiredValidator"></binding>
  21. <binding name="value" expression="username"></binding>
  22. </component>
  23.  
  24. <component id="inputPassword" type="ValidField">
  25. <message-binding name="displayName" key="password"></message>
  26. <binding name="validator" expression="beans.requiredValidator"></binding>
  27. <binding name="value" expression="password"></binding>
  28. </component>
  29.  
  30. </page-specification>

The class attribute on the page-specification element and the two property-specification elements should look familiar from the Pig Latin translator application.

The first new thing you will notice is the bean element① that sets the bean for the name "delegate" to an instance of the org.apache.tapestry.valid.ValidationDelegate. The Form component in the HTML page template had its delegate parameter set to ognl:beans.delegate that refers to this org.apache.tapestry.valid.ValidationDelegate instance.

The bean element② sets the bean for the name "requiredValidator" to a new instance of the org.apache.tapestry.valid.StringValidator class for use in validation. The required property for the bean is set to true to indicate the field being validated is required. The clientScriptingEnabled property is set to true to turn on client-side JavaScript validation. The requiredValidator bean is used to validate the contents of the inputUsername and inputPassword components.

The component-specification for the inputUsername③ gives life to the declared component in the HTML page template as a ValidField component. The ValidField is a specialized version of the TextField Component that is used in the Tapestry validation subsystem. The message-binding element is used to set the displayName parameter of the inputUsername component by retrieving the value for the "username" key from the Login.properties file④. The validator parameter is set to the requiredValidator bean declared in the page specification⑤. The value parameter is bound to the username property in our page class⑥. The component-specification for the inputPassword component⑦ is nearly the same as the specification of the inputUsername component with the exception of the key used to retrieve the displayName property and the page property to which it is bound.

By using the ValidField components and providing the Form a ValidationDelegate we have enabled input validation for the Login Form. In addition to server side validation, Tapestry will provide client-side validation for ValidField components. Here is a screenshot of the JavaScript error the user gets if they submit the form without filling in the Username field.

login, no username

Here is a screenshot of the JavaScript error the user gets if they submit the form without filling in the Password field.

login, no password

Here is the page class for the Login Page.

Login.java

  1. package com.ociweb.tapestry;
  2.  
  3. import org.apache.tapestry.html.BasePage;
  4. import org.apache.tapestry.IRequestCycle;
  5. import org.apache.tapestry.valid.ValidationConstraint;
  6. import org.apache.tapestry.valid.IValidationDelegate;
  7.  
  8. public abstract class Login extends BasePage {
  9. public abstract String getUsername();
  10. public abstract void setUsername(String username);
  11.  
  12. public abstract String getPassword();
  13. public abstract void setPassword(String password);
  14.  
  15. public void login(IRequestCycle cycle) {
  16. String username = getUsername();
  17. String password = getPassword();
  18. StringBuffer sb = new StringBuffer(username);
  19. String validPassword = sb.reverse().toString();
  20. if (password.equals(validPassword)) {
  21. cycle.activate("Success");
  22. } else {
  23. String errorMessage = getMessage("invalidpassword");
  24. IValidationDelegate validationDelegate =
  25. (IValidationDelegate) getBeans().getBean("delegate");
  26. validationDelegate.record(errorMessage,
  27. ValidationConstraint.CONSISTENCY);
  28. }
  29. }
  30. }

Just like in the Pig Latin translator app, our Page class is abstract and we have abstract methods for the two properties we defined in our page specification. Tapestry will create a subclass of our Page class at runtime and create the username and password properties. The login method simply verifies that the password entered by the user is the inverse of the username they entered. If the password is valid, the user will be forwarded to the Success page①.

If the password is incorrect, we look up the value for the "invalidPassword" key from the Login.properties② file by invoking the getMessage() method inherited from the org.apache.tapestry.AbstractComponent class. We need to record the invalid password error to our Page's validation delegate that was defined in our Page specification. We retrieve the validationDelegate from the Page's beans property using the name that we gave it in the Page specification③. Lastly we invoke the record method on the org.apache.tapestry.valid.IValidationDelegate to add the error message that will be displayed to the user④. Here is a screenshot of the Login page if the user enters an invalid password.

login-invalid password

Here is the HTML page template for the Success Page. The Success page only contains HTML markup so a page specification and page class are not required.

Success.html

  1. <html>
  2. <head>
  3. <title>Successful Login</title>
  4. </head>
  5. <body>
  6. <p>
  7. Congratulations! You have successfully logged on.
  8. </p>
  9. </body>
  10. </html>

Summary

I hope that this article has demonstrated how simple, yet elegant, it is to use the Tapestry framework to build web applications. Tapestry differs greatly from the majority of other popular web application frameworks because it allows you to develop web applications in a component-based manner rather than an operation centric manner. If this article has piqued your interest, I suggest that you download it and use it to build a simple web application of your own. It only takes a simple application to begin appreciating the merits of this framework. If you are considering using Tapestry on your next project then I would definitely suggest purchasing the book Tapestry In Action. I own a copy of the book and have been pleased with it.

References

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret