Tapestry

8 articles

When @Inject* Silently Fails

Published by Marco on in Tapestry

Most Tapestry programming involves writing event handlers and operations on page objects. In order to execute these operations, you need access to properties of the form and properties of the session and application in which the page resides. For convenience, developers can add references to all sorts of objects in the system using various forms of the @Inject* annotation (like @InjectPage, @InjectObject and so on). Pages are declared abstract and, when instantiated, Tapestry extends the abstract class to fill in all of these injected objects and maintain proper initialization and linkage with rest of the system.

As long as this works as expected, there’s no problem. However, when Tapestry can’t inject a property as specified, it fails silently instead of throwing an exception. Surely, the failure is logged somewhere, but turning on logging results in a flood of output that is all-too-quickly overwhelming. If an object cannot be injected, there should be an exception—else why would... [More]

Override an implementation in HiveMind

Published by Marco on in Tapestry

If you are not already familier with HiveMind, read Setting up a Service in HiveMind for an introduction.[1]

In the article mentioned above, we learned how to set up a new HiveMind service. What if we want to replace the implementation for an existing service? Is it even possible? Why would you want to do that? This article answers these questions in the context of a real-life example from one of our applications.

Extending Tapestry

In our Tapestry applications, we use the ExternalLink because it provides a standard URL that can be bookmarked and refreshed across sessions (which is handy during development as well). This link relies on the ExternalService, which is configured and created by HiveMind. We wanted to experiment with this implementation to include more flexibility as to which method would be called[2]; at first, we just replaced the class in our application and worked with it from there.

This worked fine until we wanted to be able to configure some basic properties we’d... [More]

Session and Requests in HiveMind

Published by Marco on in Tapestry

If you are not already familier with HiveMind, read Setting up a Service in HiveMind for an introduction.[1]

Almost every application is going to need to have information that is session-specific. This is accomplished by adding a member to Tapestry’s application objects list and assigning it the proper scope. With a scope of “session”, HiveMind makes sure that each session in the web application has its own copy.

  <contribution configuration-id="tapestry.state.ApplicationObjects">
    <state-object name="CustomSessionService" scope="session">
      <create-instance class="tapestry.CustomSessionService"/>
    </state-object>
  </contribution>

The tag names are quite straightforward in this case, with a CustomSessionService instantiated for each session.

A Note on Auto-Wiring

There is, within HiveMind, a concept known as “auto-wiring”, which purports to automatically make the connection between services based on interfaces: if one HiveMind service has a setter accepting an... [More]

Setting up a Service in HiveMind

Published by Marco on in Tapestry

HiveMind is the IOC manager used together with Tapestry; it’s in charge of bootstrapping and connecting all of the myriad objects and services available to a Tapestry application. Applications based on Tapestry are encouraged to use it to configure their application- and session-level objects and services as well.

Once it works, it works well. Getting it configured in the first place—especially when new to HiveMind—is an exercise in patience. Larger errors are detected at startup, when HiveMind tries to parse the configuration you’ve entered. Once you’ve gotten a bit better at this, you’re building a configuration that parses correctly, but fails during execution when HiveMind fails to connect one object to another as you expected (usually due to a naming mismatch of some sort or another).

Module IDs

Every module starts with the <module> tag, like this:

<module id="com.encodo.customer.project" version="1.0.0">

If HiveMind can’t locate a class specified in the configuration,... [More]

Missing ognl?

Published by Marco on in Tapestry

Every once in a while, when adding a new component to or changing an existing one on a Tapestry page, you’ll make a mistake. Most of the time, the exception handler page is pretty good; sometimes the exception can be quite confusing. For example, suppose we have a custom component with a single property:

package com.encodo.blogs.samples;

class CustomComponent extends BaseComponent {
  public abstract SomeObject getCustomParameter();
}

To use this component in a page, you just write the following:

<span jwcid="@CustomComponent" customParameter="obj"/>

This looks ok[1], but when loaded in a browser causes the following error:

org.apache.tapestry.BindingException Error converting value for template parameter customParameter: No type converter for type com.encodo.blogs.samples.SomeObject is available.

With this kind of error message, you’re ready to start imagining all sorts of horrible things:

  • Is something declared with the wrong type? No … there’s no type declaration for... [More]

Using @InvokeListener

Published by Marco on in Tapestry

Recursive Components in Tapestry discussed several approaches to nested components in Tapestry, finally settling on a solution that had only the following Java code:

public abstract class DataObjectTree extends BaseComponent {
  public abstract DataObject getContext();

  public Object getBlockContext() {
    return ((Block) getComponents().get("Node")).getParameter("value");
  }
}

Since the BlockContext is called many times from the component—real-world implementations will also likely need more such properties—, we’d like to set up all necessary data when starting the DataRowBlock. To do this, use the @InvokeListener from the HTML to call a function on the page instance.

public abstract class DataObjectTree extends BaseComponent {
  private Object blockContext;
  public abstract DataObject getContext();

  public Object getBlockContext() {
    return blockContext;
  }

  public void updateBlockContext() {
    blockContext = ((Block)... [More]

Recursive Components in Tapestry

Published by Marco on in Tapestry

Given a recursive object structure in memory, what’s the best—and most efficient—way to render it with Tapestry? First, let’s define a tiny Java class that we’ll use for our example:

public class DataObject {
  private String name;
  private List<DataObject> subObjects = new ArrayList<DataObject>();

  public String getName() {
    return name;
  }

  public List<DataObject> getSubObjects() {
    return subObjects;
  }
}

Imagine an application has built a whole tree of DataObjects and wants to display them in a page. Since the page doesn’t know how many objects—or nesting levels—there are, it can’t be defined statically. This sounds like the perfect place to use a Tapestry component. Since each component must know about its context object (the DataObject), there must be an instance of the component for each object. This sounds like the perfect place to use recursion.

Let’s take a crack at defining the template for a component named “DataObjectTree”, which has a single... [More]

Pitfall in the @For component in Tapestry

Published by Marco on in Tapestry

Any properties used from a Tapestry template have to be declared in the corresponding Java page class. It is highly recommended to declare these properties as abstract; Tapestry implements them for you, automatically including code that re-initializes each property automatically when a page is re-used from the cache. If you implement the properties yourself in the customary Java getter/setter way, it is up to you to clear them in order to ensure that users can’t see one another’s data.

That said, there are a few bumps in Tapestry’s current implementation. For example, the @For component requires a source list and an iteration value from that list.

The declaration in HTML looks like this:

  <tr jwcid="@For" 
       source="ognl:DataObjectList" 
       value="ognl:DataObject"
       element="tr">
    <td>
      <span jwcid="@Insert" value="ognl:DataObject.Name">Name</span>
    </td>
  </tr> 

That is, the component iterates the list returned from getDataObjectList() (Tapestry... [More]