Tapestry Integration


Integrating a Tapestry Web UI layer with a Spring-provided services layer is relatively simple. This section describes how to use Tapestry and Spring together, but does assume you are already familiar with how Tapestry works and how to use the Spring application context.

We're going to show a sample of how to integrate by taking the existing Spring sample application called JPetStore, and by implementing a Tapestry page that is essentially fairly similar to the existing item detail page. The Tapestry page, however, has an input field allowing the item ID to be specified, and once the user has submitted a valid ID, the page will display the details for the specified item.

Getting Beans for Tapestry

The ItemView.java page class we're going to create is going to need access to the PetStoreFacade service object from the Spring application context, stored under the bean ID petStore. The most obvious mechanism to get at the application context is through the standard Spring static utility method WebApplicationContextUtils.getApplicationContext().

In the Tapestry page's Java code, we could just do something like this:

 WebApplicationContext appContext =          WebApplicationContextUtils.getApplicationContext(         getRequestCycle().getRequestContext().getServlet().getServletContext()); PetStoreFacade petStore = (PetStoreFacade) appContext.getBean("petStore"); 

This works but is not an ideal solution as the Java code is aware of Spring; it's not IoC in style. Instead, we rely upon the fact that Tapestry already has a mechanism to declaratively add properties to a page. In fact, especially when the properties need to be persistent, this declarative approach is the preferred approach to managing all properties on a page, so that Tapestry can properly manage their lifecycles as part of the page and component lifecycle. The automatic handling of persistent page properties is one of Tapestry's strongest points.

The Page Class

Let's examine our ItemView.java page class:

 public abstract class ItemView extends PetStoreBasePage {        public abstract Item getItem();   public abstract void setItem(Item item);        public abstract String getItemIdFilter();        public abstract PetStoreFacade getPetStore();        /**    * handle form submissions, which let an item be specified     **/   public void formSubmit(IRequestCycle cycle) {          if (getItemIdFilter() != null) {     Item item = getPetStore().getItem(getItemIdFilter());     setItem(item);     }   } } 

There's not actually very much going on at the level of Java code. We have declared three abstract properties: item, itemIdFilter, and petStore. The accompanying page definition file, ItemView.page, will also declare these all as page properties, and at runtime Tapestry actually generates a final version of the class, which implements these values and initializes them properly as per the page definition.

When the user submits the request to view an item, the item ID input box is going to come in as the itemIdFilter property. The code then uses the instance of the PetStoreFacade service, which is stored in the petStore property, to read in the item, which is then stored in the item property, to be displayed to the user. This simple implementation will not warn the user of an invalid item number. An invalid number will simply result in a null item being set, which the page template will check for.

The Page Definition

Now we need to create the page definition file, ItemView.page. First, however, we want to make it easier for any page to get at the application context. As a convenience, we extend the normal Tapestry BaseEngine class with our own implementation, which gets the context and puts it in an easier-to-access location:

 public class BaseEngine extends org.apache.tapestry.engine.BaseEngine {        public static final String APPLICATION_CONTEXT_KEY = "appContext";        // see org.apache.tapestry.engine.AbstractEngine#setupForRequest(   //         org.apache.tapestry.request.RequestContext)   protected void setupForRequest(RequestContext context) {     super.setupForRequest(context);          // insert ApplicationContext in global, if not there     Map global = (Map) getGlobal();     ApplicationContext ac = (ApplicationContext)             global.get(APPLICATION_CONTEXT_KEY);     if (ac == null) {       ac = WebApplicationContextUtils.getWebApplicationContext(               context.getServlet().getServletContext());       global.put(APPLICATION_CONTEXT_KEY, ac);     }   } } 

What the preceding code does is obtain the Spring application context and put it in the Tapestry application's Global object, where it's easier to get at from page definitions.

Now let's look at the actual page definition:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE page-specification PUBLIC     "-//Apache Software Foundation//Tapestry Specification 3.0//EN"     "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">     <page-specification          >       <property-specification name="error" type="java.lang.String"/>   <property-specification name="item"         type="org.springframework.samples.jpetstore.domain.Item" persistent="yes"/>   <property-specification name="itemIdFilter" type="java.lang.String"/>   <property-specification name="numberFormat" type="java.text.DecimalFormat">     new java.text.DecimalFormat("$#,##0.00")   </property-specification>   <property-specification name="petStore"         type="org.springframework.samples.jpetstore.domain.logic.PetStoreFacade">     global.appContext.getBean("petStore")   </property-specification>     </page-specification>

A number of properties are defined here, including the three properties previously mentioned. The other two, error and numberFormat, are used by our page's base class and by the HTML template, respectively.

The only interesting definition for our present purposes is for the petStore property, which, using the OGNL expression language, specifies an initial value for this property as global.appContext.getBean("petStore"). In this expression, the application context that was put into the Global object by the custom engine class is asked for the PetStoreFacade bean, which has the ID petStore.

The Page Template

Now let's look at the page template itself, ItemView.html:

<html jwc> <body jwc>     <h1>Item View</h1><hr/>     <span jwc/><span jwc        delegate="ognl:valDelegate"/>     <form jwc listener="ognl:listeners.formSubmit"        delegate="ognl:valDelegate">   Enter a valid item id to view its details:    <input jwc value="ognl:itemIdFilter" size="20" maxlength="20"/>   <br/>   <input type="submit" jwc value="Show Item"          style="background: #cccccc none;"/> </form>     <span jwc condition="ognl:item != null">       <table align="center" bgcolor="#008800" cellspacing="2" cellpadding="3"          border="0" width="60%">     <tr bgcolor="#FFFF88">       <td bgcolor="#FFFFFF">         <span jwc value="ognl:item.product.description" raw="true"/>       </td>     </tr>     <tr bgcolor="#FFFF88">       <td width="100%" bgcolor="#cccccc">         <b><span jwc value="ognl:item.itemId"/></b>       </td>     </tr>     <tr bgcolor="#FFFF88">       <td>         <b><font size="4">         <span jwc value="ognl:item.attribute1"/>         <span jwc value="ognl:item.attribute2"/>         <span jwc value="ognl:item.attribute3"/>         <span jwc value="ognl:item.attribute4"/>         <span jwc value="ognl:item.attribute5"/>         <span jwc value="ognl:item.product.name"/>         </font></b>       </td></tr>     <tr bgcolor="#FFFF88"><td>       <font size="3"><i><span jwc             value="ognl:item.product.name"/></i></font>     </td></tr>     <tr bgcolor="#FFFF88"><td>       <span jwc condition="ognl:item.quantity lte 0">         <font color="RED" size="2"><i>Back ordered.</i></font>       </span>       <span jwc condition="ognl:item.quantity > 0">         <font size="2"><span jwc               value="ognl:item.quantity"/> in stock.</font>       </span>     </td></tr>     <tr bgcolor="#FFFF88"><td>       <span jwc value="ognl:item.listPrice"               format="ognl:numberFormat"/>       </td></tr>     </table>   </span>       </body> </html>

The page template is mostly a simple conversion of the original JSP template for viewing an item, item.jsp. However, an input box has been added at the top so that the user may enter an item ID and submit it. Showing the item detail is handled by the last two-thirds of the template, but this is wrapped in a conditional so that the item is shown only if the item page property is not null. Therefore, on the initial view of the page, it will display only the input box asking for an item ID, and on subsequent views, assuming the item could be successfully looked up, the item detail will also be shown.

Tapestry Integration Final Thoughts

As you've seen, the point of integration between Tapestry and Spring is very thin, and the mechanism is not too complicated. It is also not very intrusive. Tapestry's page definitions with the property declarations are already a form of IoC. Java code implementing a page class just declares abstract properties and uses them; it doesn't care how those properties are actually initialized. This makes it easy to declare all services that need to come from Spring as page properties, and then in the page property definition declaratively set the initial values of those properties as beans obtained from a Spring application context. The one small annoyance with this mechanism is that while this is still IoC for the Java code (which is what really matters), it's not IoC for the page definition files; that is, OGNL expressions in the page definitions are actually responsible for pulling beans from the context, instead of the context itself specifying properties for pages, as if those pages were any other bean.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net