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!

Wednesday, November 24, 2004

Paint Shop Pitiful HiveMind Example

I surprised folks at ApacheCon by using a radically different example than what was shown in the printed handouts ... PaintShopPitiful, a very simple SWT application that reads in an image file and can perform some simple manipulations on it. The HiveMind part is all about the seperation between the GUI and the filter objects that do the manipulations, as well as the HiveMind services that connect the two together (creating an SWT Menu in the process).

Anyway, the source code (pitiful as it is) is now up in the downloads section as http://howardlewisship.com/downloads/paint-shop-pitiful.zip.

Friday, November 19, 2004

Latest Tapestry documentation, Death to Parameter Direction

After a fairly long struggle with Forrest, I've finally been able to get the Tapestry 3.1 documentation built properly. The upgrade from Forrest 0.5 to 0.6 has been very painful and, in fact, getting the documentation to build correctly requires the very, very latest from Forrest's SVN repository (what will become Forrest 0.7).

The results are pretty though (they're in a temporary location while the details get filled out, especially the new component reference).

In other news, parameter directions are dead. This is huge news if you use Tapestry 3.0 today ... understanding parameter directions was a real pain. Basically, you had to give Tapestry hints about how to synchronize the properties of your component against the properties of the page, effectively saying "I only access this parameter when rendering" vs. "I might access this parameter at any time." (vs. "Just let me deal with this in lots of Java code, please!").

That's completely gone in Tapestry 3.1. The new accessor methods for component parameter properties are now super-smart: they do what the old direction auto did, but handle optional parameters, type conversion, and really smart data caching. Less is more!

Tuesday, November 16, 2004

Rumor Control

So I'm here at ApacheCon and people keep coming up to me and asking me about my new job. And that's weird because I don't have one. Apparently, there's a strong rumor that JBoss is about to announce an existing Apache project moving under their umbrella, and from the hints, people think it's either Tapestry or HiveMind. How odd! Nice to be noticed, but there's no shred of truth there. I won't fan the flames of rumor with any speculations ... I guess we'll all see what's up in a day or two.

Thursday, November 11, 2004

A door closes, a door opens?

All good things must come to an end; alas, my work for The Middleware Company is rapidly drawing to a close, as part of the overall picture with the sale of TMC to TechTarget. That is unfortunate, because the Tapestry conversion of theserverside.com, while quite successful for all involved, was only the opening moves in a longer game that may not be played.

I was hoping that I could continue as I have for the last few months: working from home on TSS, speaking at JUGS and conferences, and building up a nest egg/warchest. I'm now back to doing things I should have been actively doing for the past few months ... calling back my contacts and working to find new ones.

So let's be clear ... if you are starting a Java web application project, you should be using Tapestry. It's just going to save you an awful lot of grief and worry. If you need help getting started, need professional support, or need an authoritative voice to help you convince your management (or all three at the same time) ... well folks, that's what I do for a living.

Knut's All Groovy on That HiveMind Thang

Knut Wannheden has just checked in Groovy support for HiveMind. The idea is that you can define your module descriptor using Groovy builder syntax rather than XML. I guess there are advantages when doing a lot of similar services. Anyway, that's one way of limiting the amount of XML.

Monday, November 08, 2004

Comparisons, Comparisons

Let's see, Mike Spille has done a fairly detailed comparison of HiveMind, Spring and PicoContainer. From a pure IoC (Inversion of Control) point of view, he likes HiveMind the most, which is heartening:

"All in all HiveMind looks to implement IoC in a very solid way, and generally only implement IoC and surrounding infrastructure (like configuration), and based on everything I've read is based on real project requirements (and I believe this shows). At the same time, some bits of HiveMind still seem a bit rough and appear to need polishing."

Meanwhile, Matt Raible has previewed his slides for his ApacheCon talk comparing web frameworks (here's a PDF link). He critiques Tapestry's documentation ... does nobody get that I spent most of a year of my life working on the Tapestry documentation ... beg, borrow or buy a copy, but don't claim there isn't documentation or good examples. Meanwhile, the community is creating its own sets of tutorials and you can find them all on the Wiki. Matt has a copy of the book but hasn't ready it yet, and I find that unfair.

Then there's the statement about Tapestry being impossible to test. That is simply not true, but the very question obscures a more relevant issue. You can test the methods, but there really is no such thing as unit testing a web application. Remember, you are supposed to test behaviors, not methods, and behaviors of web applications (or GUIs for that matter) are emergent from many things besides the raw code.

Given that, you had better have a plan for integration testing your application. There's a number of open-source and proprietary tools out there for this purpose, though I have yet to find one that uses a simulated servlet container that can be effectively run from within a unit test suite without any external requirements or servers.

For a servlets or Struts application, I have very, very little confidence that the application as a whole works, even if individual methods are tested and work. There's so much else that can go wrong. Tapestry is a much safer bet (because so much of the machinery is general purpose and tested) ... but still, what you are mostly testing in any kind of web or GUI application is that all the bits and pieces are configured correctly and hooked together just right ... things that don't show up in unit tests.

The best course of action for any kind of GUI is to embrace a proper separation of concerns and move important logic out of the presentation layer and into a more test-friendly environment. In Tapestry terms, keep those listener methods really small, and delegate out as much logic as possible into business objects. This will be a snap in Tapestry 3.1 ... just move all the logic into HiveMind and have the HiveMind services injected into your pages and components.

Matt does see something of worth in Tapestry:

"After working with Tapestry and JSF, I can see how component-based frameworks will be the wave of the future. I think as you develop more and more components, the code you write becomes less and less."

I maintain that this is much more dramatically true for Tapestry than for JSF, and Tapestry 3.1 will extend that difference. Further, I think Tapestry is the wave of the now. Anyway, look for the sparks to fly when I sit in on Matt's session at ApacheCon!

Friday, November 05, 2004

HiveMind AdapterRegistryFactory

Just did a little burst of work on HiveMind and added the AdapterRegistryFactory. What's it all about?

It's an implementation of the adapter pattern. The idea is that you have some common operation that should apply to all sorts of different types; perhaps its code that is used to output an XML representation of different objects, or performs some other common operation. In Tapestry, an example of this is the way different objects are evaluated as conditions for the Conditional component: a java.lang.Boolean is obvious, but others require some work: java.lang.String if it contains non-whitespace character; any Number if the value is non-zero, any java.util.Collection if not empty, etc.

With an AdapterRegistry, you define an interface for your adapters. The first parameter of each service method will be used to select the adapter.

public interface ConditionEvaluator
{
  public boolean evaluate(Object value);
}
This idiom reflects that the adaptors are singletons into which the object to operate upon is provided as a method parameter. This differs somewhat from the Gang Of Four useage, where a specific adaptor instance is created for a specific object instance.
You define a configuration and contribute classes and adapters:
<contribution configuration-id="ConditionEvaluators">
  <adapter class="java.lang.Boolean" object="instance:BooleanAdapter"/>
  <adapter class="java.lang.Number" object="instance:NumberAdapter"/>
  . . .

Your adapter implementations are simple:

public class BooleanAdapter implements ConditionEvaluator
{
  public boolean evaluate(Object value)
  {
    Boolean b = (Boolean)value;
 
    return b.booleanValue();
  }
}

Lastly, you define your service point:

<service-point id="ConditionEvaluator" interface="ConditionEvaluator">
  <invoke-factory service-id="hivemind.lib.AdapterRegistryFactory">
    <construct configuration-id="ConditionEvaluators"/>
  </invoke-factory>
</service-point>

At this point, you can reference this service and invoke methods on it, passing different instances into it. Internally, the service implementation will locate the matching adapter (BooleanAdapter for java.lang.Boolean, NumberAdapter for java.lang.Integer and friends) and let the adapter do the work. What's powerful is that your code just sees the one service proxy ... HiveMind connects the dots to get the correct adapter implementation invoked when you invoke a method on the proxy. This is similar to how the threaded and pooled service models expose just the one proxy. There are no questions such as "is this service threaded?" or "is this service an adapter?" ... it all just works.

For the moment, you are not allowed to pass null (you'll get a runtime exception). Still pondering the right approach to handling nulls.

Much like the PipelineFactory, this is a quick way to assemble a pretty sophisticated apparatus ... that all hides behind a single service and a single interface. I often talk about HiveMind's power coming from the mix of services and configurations. In a way that is familiar to Lisp hackers and the like, code (in HiveMind terms, services) gets all mixed up with data (configurations) to allow elegant, powerful solutions to arise.

Updated: Changed the naming from "Adaptor" to "Adapter".

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!

Tuesday, November 02, 2004

Back from the polls

It's 8:00am eastern time and I'm back from the polls. I think it is vitally important that everyone votes today (not just this election, but every election). I hear a lot of grousing about how unlikeable either candidate is, but that's just a smokescreen for neglect and laziness. Voting isn't just a right and a privilege, it is a responsibility. The more people vote, the more that special interests' power gets diluted, and that is good for democracy, good for America.