Unlike servlets and JavaServer pages, Tapestry components including pages are thread safe and so they can encapsulate their state (instance data). This is a great relief for an object-oriented developer.
The state of components is held in component properties. Component properties are instance variables that may be declared and initialized in the component class or in the component specification.
The state of components may also be stored externally, like JSPs do, in HttpSession and/or ServletContext. Although, Tapestry shields the developer from these objects by allowing any POJO (plain old Java object) to be used in their place as we will see later. Tapestry's terminology for these objects is the Visit and Global respectively.
Components may have two types of states.
The state that endures only through the end of the currently processing request is transient state. When the request is processed and the response is rendered, this state of components is lost. Properties holding transient state are called transient properties.
The state that endures across multiple requests through the end of the client session is persistent state. In other words, this state of components, set during one request-processing, is available during another request-processing in the same session. Properties holding persistent state are called persistent properties.
At any given time, a page may be attached to one request only. In other words, every request gets its own copy of the page and all its embedded components. This means, when there is a simultaneous request for the same page, Tapestry has to create separate copies of the page for each request. In fact, Tapestry builds and maintains a pool of pages as needed. So, when a page is done processing a request, the page is not released for garbage collection, instead it is returned back to the pool for reuse, for a later request.
The pool of pages is shared across all clients. So, any client specific state, including persistent state, in the page and its embedded components should be set back to its initial value before returning the page to the pool.
Tapestry provides a listener interface, PageDetachListener, for this purpose, that components and pages may implement. Tapestry will invoke the pageDetached() method of this interface prior to returning the page to the pool.
This is quite a bit of grunt work for the developer to implement the PageDetachListener for each component and page, which is against the spirit of Tapestry. Hence the framework introduced <property-specification> which greatly simplifies this process.
Property specification. Properties defined via <property-specification> are automatically managed by the framework. Components defining properties via <property-specification> are automatically enhanced at runtime and the necessary behavior is added.
... <property-specification name="userName" |
![]() | Tip |
|---|---|
| With <property-specification>, there is very little reason to implement properties manually. Using the <property-specification> element is much easier, and nearly as efficient. It is highly unlikely that the extra developer effort used to implement properties manually will pay off in any improvement in application throughput. | |
Transient properties are easier to maintain since these don't have to persist across multiple requests. The only responsibility of the developer is to cleanup the client-specific state from the page and its embedded components before returning the page to the pool.
Implementing transient properties manually for pages. Manually implementing transient properties is a little different in pages and components. AbstractPage, the parent of all pages, has a special initialize() method that can be used for initializing properties. This initialize() method will be invoked once when the page is first created and again at the end of every request, before detaching the page.
public class PageClass extends BasePage { private String _message; public void setMessage(String message) { _message = message; } public String getMessage() { return _message; } protected void initialize() |
Implementing transient properties manually for components. Components do not have the equivalent of the initialize() method. Instead, they must register for an event notification to tell them when the page is being detached (prior to being stored back into the page pool).
public class ComponentClass extends BaseComponent implements PageDetachListener |
| The listener to be invoked during the page detach event.
| ||||
| Arbitrary method to initialize component state. | ||||
| finishLoad() is an AbstractComponent method that will be invoked at the end of the component loading process. | ||||
| pageDetached() will be invoked at the time of detaching the page from the request. | ||||
Implementing transient properties via <property-specification>. With <property-specification> there is not much for the developer to do. The property accessor and mutator methods are provided by the framework at runtime. So if there is a need to get/set properties, the component may declare abstract accessors/mutators.
<component-specification class="devguide.ComponentClass">
...
<property-specification name="message" type="java.lang.String"
initial-value="Some initial message." />
...
</component-specification> |
![]() | Tip |
|---|---|
| Properties defined via <property-specification> are by default transient properties. There is no need to specify the persistent attribute. | |
package devguide; public abstract class ComponentClass extends BaseComponent { // Accessor methods for property "message", defined via <property-specification> public abstract void setMessage(String message); public abstract String getMessage(); // Arbitrary method demonstrating the usage of the "message" property. public void someMethod() { if (getMessage() == null) setMessage("New message ..."); } ... } |
With the <property-specification>, the framework enhances the ComponentClass and adds the necessary behavior to initialize the state of the component prior to returning the containing page to the pool. The ComponentClass needs to declare the abstract methods only if the class needs to access them to define its behavior.
Persistent properties are managed by a framework component external to the page - the page recorder. It is the responsibility of the page recorder to record the client-specific persistent state of the page in HttpSession (or other storage) and retrieve it when the same client returns back to the page. The responsibility of the developer is to notify the page recorder as to when to record the state (typically whenever the state changes), and to cleanup the client-specific state from the page and its embedded components before returning the page to the pool.
The notification takes the form of an invocation of the static method fireObservedChange() in the Tapestry class. This method is overloaded for all the scalar types, and for Object.
Implmenting persistent properties manually in pages.
public class PageClass extends BasePage { private int _itemsPerPage; public int getItemsPerPage() { return _itemsPerPage; } public void setItemsPerPage(int itemsPerPage) { _itemsPerPage = itemsPerPage; Tapestry.fireObservedChange(this, "itemsPerPage", itemsPerPage); } protected void initialize() { _itemsPerPage = 10; } ... } |
This sets up a property, itemsPerPage, with a default value of 10. If the value is changed (perhaps by a form or a listener method), the changed value will "stick" with the user who changed it, for the duration of their session.
Implementing persistent properties manually in components.
public class ComponentClass extends BaseComponent implements PageDetachListener { private String _myProperty; public void setMyProperty(String myProperty) { _myProperty = myProperty; Tapestry.fireObservedChange(this, "myProperty", myProperty); } public String getMyProperty() { return _myProperty; } protected void initialize() { _myProperty = "a default value"; } protected void finishLoad() { initialize(); } /** * The method specified by PageDetachListener. */ public void pageDetached(PageEvent event) { initialize(); } ... } |
The only difference here is the property initialization as we have already seen in transient properties. Again, there is no particular need to do all this; using the <property-specification> element is far, far simpler.
Implementing persistent properties via <property-specification>. Persistent properties have to be explicitly specified in the <property-specification> element via the persistent attribute.
<component-specification class="devguide.ComponentClass">
...
<property-specification name="message" type="java.lang.String"
persistent="yes" initial-value="Some initial message." />
...
</component-specification> |
The usage of the property in the component class is identical to the usage of transient properties we saw earlier.