Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Thursday, November 04, 2004

Tapestry 3.1 and HiveMind 1.1 work accelerating

I've been able to sneak in a little bit of work on Tapestry 3.1 and HiveMind 1.1 over the last week or so. It's been fun stuff; On the Tapestry side, I've been re-working the enhancement subsystem.

Enhancement is the process of taking your classes (which are usually abstract) and the corresponding page or component specification, and building a subclass that fills in all the Tapestry details. It is the sub-class that gets instantiated. The class is abstract because Tapestry needs to fill in some critical details on each property, to make the resulting page or component work properly within Tapestry's page pool.

In fact, the whole process has gotten much, much smarter for Tapestry 3.1. There's the <inject> element, which allows HiveMind objects to be pulled out of the registry and plugged into a page (or component) as a read-only property. Also, any abstract property on a page (or component --- jeez that gets repetative; pages are components) will turn into a transient property automatically.

The <property> element is now only needed for persistent properties, for properties that want to set an initial value, or for properties that aren't referenced in Java code (but are simply used to move data beteween components).

The approach, using a configuration of worker services, each with a specific responsibility, works great. It will be easy to extend it in the future with new properties; for example, I want to be able to assign a property name to a component or asset, and have a property, of the correct type, show up.

Some things are getting more efficient as well. In Tapestty 3.0, each specified property must have an object implementing PageDetachListener, whose job is to reset the property back to its default value. This is needed even if there isn't an OGNL expression for the property's initial value. For the moment, that hasn't changed ... for specified properties. For unspecied abstract properties, a more sophisticated changed takes place: the class is made to implement PageDetachListener (if it did not already), and the pageDetached() method is created or overriden to take care of resetting the properties; in addition, the finishLoad() method is overriden to snapshot the initial values of the properties (each property gets a pair of instance variables).

So we've broken down a complex job into several much simpler jobs; each individual worker is fully tested (100% code coverage). They each work with an EnhancementOperation object that encapsulates most of the work of analyzing the existing class and constructing the enhanced class.

By plugging more workers into the configuration, even application-specific ones, new types of enhancements will be added. It will be possible, for example, to create a library that understands some form of JDK 1.5 annotation and have it plug into the configuration ... the Tapestry framework can continue to be compiled against JDK 1.3 while using JDK 1.5 features. Hows that for late binding?

This big payback for all of this is coming up: elimination of parameter direction. The definition of dread is when I get to that part of my Tapestry presentation. Parameter direction is a hint, given to Tapestry, that describes when OGNL expressions should be evaluated to move data out of a container (typically, the page) and into a contained component's properties. The problem is, you have to either understand a lot of what Tapestry is doing under the covers to decide what the right direction should be, or at least, do certain things by rote ("if I access that parameter property in a listener method, then it must be direction auto").

Yuck. It should just work ... and in Tapestry 3.1 it just will. The generated code for parameter properties will be more involved ... it will have to be aware of whether the component is currently rendering or not, whether a cached value for the property is available, and some tricky logic about converting types as needed.

That's going to take a little work to get right, which is why I flipped back to the HiveMind side. HiveMind has the ClassFactory service, which puts a relatively pretty face on the arcane aspects of Javassist. However, given how much runtime bytecode enhancement is going to occur, and how much of it will be distributed across many objects, ClassFactory and ClassFab have a weakness: they did not implement toString(). Now they do, I spent some time last night getting them to generate a reasonable string representation that looks kind of like Java psuedocode, so that you can see what's going to be generated by the time the class is created from the ClassFab. The end result for an enhanced class can look like:

ClassFab[
public class $ExceptionDisplay_4 extends org.apache.tapestry.html.ExceptionDisplay
  implements org.apache.tapestry.event.PageDetachListener

private org.apache.tapestry.util.exception.ExceptionDescription[] _$exceptions;

private int _$index;

private int _$index$defaultValue;

private int _$count;

private int _$count$defaultValue;

public org.apache.tapestry.util.exception.ExceptionDescription[] getExceptions()
return _$exceptions;

public void setIndex(int $1)
_$index = $1;

public int getCount()
return _$count;

public void finishLoad(org.apache.tapestry.IRequestCycle $1, org.apache.tapestry.engine.IPageLoader $2, org.apache.tapestry.spec.IComponentSpecification $3)
{
  super.finishLoad($$);
  _$index$defaultValue = _$index;
  _$count$defaultValue = _$count;
}


public void setCount(int $1)
_$count = $1;

public void pageDetached(org.apache.tapestry.event.PageEvent $1)
{
  _$index = _$index$defaultValue;
  _$count = _$count$defaultValue;
}


public int getIndex()
return _$index;

public void setExceptions(org.apache.tapestry.util.exception.ExceptionDescription[] $1)
_$exceptions = $1;

]

It's not Java syntax, and its not even quite Javassist syntax ... but it certainly identifies what the new class is all about and that's what counts!

4 comments:

Cardsharp said...

I'm a big fan of Tapestry but have been using the Spring Framework for dependency injection. Yes, I know it's heresy and all, but I was using it long before I discovered Tapestry. My application makes heavy use of Spring's Hibernate and Transaction support and I don't see that HiveMind has similar capability. I REALLY want dependency injection in my Tapestry pages, but I don't think I can justify the switch from Spring to HiveMind. Is there going to be a way to implement dependency injection in Tapestry 3.1 using Spring instead of HiveMind?

Jean-Francois Poilpret said...

Hi Cardsharp,

Off the shelf, this is right that HiveMind does not offer support for Transactions handling or Hibernate.
However, some work has been done by developers (including me) to implement such support.
As far as I know, as of today, there is no "official" project supporting that.
But you could still have a look at HiveMind mailing lists, HiveMind WIKI and the JIRA repository to check out some contributions that have already been made.

I think there is also some pending effort (Howard can you confirm this?) on integrating Hibernate transactions handling facilities into HiveMind, I ignore what is the standpoint for that initiative.

Regarding my current work, I am still working on a generic HiveMind transaction service with support for Hibernate. I intend to open a project on Sourceforge, but do not hold your breath for it! I think it would take me a few more weeks to start the SF project with the current standpoint that includes JDBC and Hibernate support, transaction demarcation (a la EJB), but _witout_ JTA support (not yet).

Jean-Francois Poilpret said...

Oops! I posted too quickly.

Read:
there is also some pending effort on integrating SPRING transactions handling facilities into HiveMind

Instead of:
there is also some pending effort on integrating HIBERNATE transactions handling facilities into HiveMind

Fadamsen said...

Cardsharp, HiveMind has a SpringLookupFactory service in hivemind.lib - perhaps this is what you need? Then you can continue using Spring - and start using HiveMind. ;o) You'll have the best of both worlds!

Furthermore, in Tapestry 3.1 Howard has mentioned that you should be able to use a "spring" prefix to bind properties/services. If that ain't cool...