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, June 30, 2005

RAD That Ain't Bad: Domain-Driven Development with Trails

Chris Nelson, the creator of Trails, has written RAD That Ain't Bad: Domain-Driven Development with Trails, an introductory article to this great platform. This looks to be the first of several articles on this subject!

Trails is an attempt to capture the magic of Ruby on Rails using Tapestry, Spring and Hibernate.

Saturday, June 25, 2005

Tapestry 4.0-beta-1

The first beta release of Tapestry 4.0 is now available. Tapestry is a component based web application framework that provides lots of functionality with minimal Java coding, and creates an environment that supports high levels of reuse. Tapestry 4.0 represents a significant advance over Tapestry 3.0. A few of our favorite changes in 4.0:

  • The new 4.0 specification DTDs have been simplified.
  • The syntax used for binding parameters inside an HTML template and inside an XML specification is now consistent. Both make use of the binding prefixes.
  • "Friendly" URLs (that is, URLs that pack more information into the path and less into query parameters) are built in. This makes it easy to divide your application across many folders (reducing clutter), and leverage J2EE declarative security along the way.
  • Listener methods are much easier and more flexible; listener parameters in the URL are automatically mapped to listener method parameters, and listener methods can return the page name or page instance to activate.
  • Component parameters now just work, without having to worry about "direction".
  • Applications can now have a global message catalog, in addition to per-page and per-component message catalogs. Messages not found in the component message catalog are searched for in the application catalog.
  • Full, native support for developing JSR-168 Portlets has been added.
  • Tapestry 4.0 makes much less use of reflection and OGNL than Tapestry 3.0; partly because there are many new binding prefixes and largely because of how parameters are now implemented.
  • HiveMind services and Spring beans to be directly injected into page and component classes.
  • Tapestry 4.0 includes optional JDK 1.5 annotation support (but Tapestry still works with JDK 1.3).
  • Tapestry 4.0 debuts a new and much more sophisticated user input validation subsystem. Thanks Paul!
  • Line precise error reporting can now display the contents of files containing errors.
  • Forms can now be canceled, bypassing client-side validation logic, and invoking an alternate listener on the server-side.
  • You are no longer limited to just Global and Visit; you can have as many application state objects as you like.
  • The use of HiveMind under the covers means that Tapestry can be easily customized to fit your needs.
  • Page properties can now be persisted on the client, as well as in the session.
  • Components and component parameters can now be marked as deprecated. Component parameters may have aliases (used when renaming a parameter).

The complete list of changes is almost too numerous to enumerate. Suffice to say, everything is about getting more bang for the buck; reducing the amount of Java code, reducing the complexity of templates, and simplifying (or eliminating) XML files.

Tapestry is distributed as a combined binary/source distribution, and a seperate documentation distribution.

Download Tapestry

Wednesday, June 22, 2005

Strange Milestone

When I used to work for Stratus Computer (first as a co-op, then full time from 89 to 97, then as a contractor) ... anyway, back at Stratus, whenever you asked for documentation, you would receive it as a think bundle of shrink-wrapped paper, punched for a three-ring binder, and a big blue Stratus three ring binder to go with it.

Somewhere in their Marlboro campus must have been a hidden warehouse the size of a football field with these blue binders stacked full width and straight to the ceiling (and maybe the occasional stray Arc of the Covenant tucked away in a corner). Everyone had offices full of these binders. When the documentation was out of date, the contents went to the recycle bin, but the binders tended to follow you home.

In fact, I was amused to see Dave Thomas pull out one of those binders, provoking a discussion of how much we missed s$parse_command (and how it needs to be ported to Ruby). He'd contracted with a company that used Stratus equipment.

So what's the milestone? Well, I'm printing out the documentation for Berkeley DB Java Edition and I went looking for a Stratus binder to put it in ... and couldn't find one! It's only been seven or eight years since I last set foot there ... I thought I'd never run out!

Wicket 1.0

I still haven't had much time to look at Wicket, which just announced a 1.0 release. I did see that their examples include a Hangman application (the new industry standard, I guess).

I think many of their goals are quite laudable; less XML is a good thing (you may notice I'm on an annotation streak right now).

I do have some questions about lifecycle; I need to dig into the docs or code to see how they maintain page state across requests; I'm concerned that each session will get a giant serialized tree of components, something that Tapestry's structure works exceptionally hard to avoid ... even though seperating a page's persistent state from the tree is what lead down the abstract accessor method path in the first place.

I was amused by the live examples; the URLs that were generated look like Tapestry 0.0.1 URLs (I could even see the loop index terms in the middle of the URLs). Tapestry has taken some flack for ugly URLs, which are much improved in Tapestry 4.0 (in fact, limited by the Servlet API more than anything else).

Wicket seems to have a page-state version number encoded into the URL to detect browser back button issues (Tapestry, of late, has been moving towards storing more data in the client to circumvent problems caused by mismatched client- and server-side state).

Mostly, I'm envious of the chance to start with a clean slate. The demand for backwards compatibility is surely holding me back from fixing a good number of things. And if I was starting again today, I would not require subclassing from base classes (as Tapestry and Wicket both require). It really would be POJOs, plug optional interfaces and naming conventions (and maybe annotations) ... and no XML at all.

At the end of the day, it's great to see folks "gunning" for Tapestry, it's a kind of validation. I'm looking forward to meeting the Wicket crew at JavaOne.

Further discussion on The ServerSide.

Tuesday, June 21, 2005

More Tapestry Annotations

Just added the @Component annotation, which works like the <component> element:

    @Component(type = "Conditional", bindings =
    { "condition=message", "element=div" })
    public abstract IComponent getIfMessage(); 

The syntax is definately one of those "lesser of many evils" decisions. Using an array of strings, rather than a series of @Binding elements, means a lot less typing. I actually did the @Binding route first and it was much more verbose and not any more readable.

With this, the XML is becoming increasingly vestigal. There are still a limited number of things that can be expressed in the XML that can't be done using annotations, including <meta>. In addition, line precise exception reporting is compromised by annotations ... there's no file and line data to report. I may have to kludge together something that simply identifies the class, with no line number information.

The test suite is up to 1298 unit tests.

Update: changed the syntax to name=binding reference.

Also, added a very nifty @Message annotation.

Sunday, June 19, 2005

Listener method improvement

Had an idea recently; another improvement for Tapestry listener methods. Listener methods return void, and if a method wants to activate another page, it must accept the IRequestCycle as a parameter, so it can invoke activate() on it.

What if listener methods could also return a string (the name of a page), or a page instance to activate? Especially paired with injection of pages (and better yet, an annotation), this results in something very flexible and even familiar to Struts and WebWork users.

Just blogging while Eclipse warms up ... should have this written, tested and checked in shortly.

Ok, so its all done, came out great: Tapestry 3.0.x:

public void doShowDetails(IRequestCycle cycle)
{
  Object[] parameters = cycle.getServiceParameters();
  Long accountIdLong = (Long)parameters[0];
  long accountId = accountIdLong.longValue();

  Details page = (Details)cycle.getPage("Details");

  page.setAccountId(accountId);

  cycle.activate(page);
}
Tapestry 4.0 alpha-3:
public void doShowDetails(IRequestCycle cycle, long accountId)
{
  Details page = (Details)cycle.getPage("Details");
  page.setAccountId(accountId);

  cycle.activate(page);
}
Tapestry 4.0 beta-1:
@InjectPage("Details")
public abstract Details getDetailsPage();

public IPage doShowDetails(long accountId)
{
  Details details = getDetailsPage();
  details.setAccountId(accountId);

  return details;
}

Friday, June 17, 2005

HTML form trick

Sometimes you come up with something so simple and so useful you have to share.

So ... I'm working on a Wizard and I have four buttons across the bottom of the form: "< Previous", "Next >", "Finish >>" and "Cancel".

But I noticed that when I hit the return key inside on of the fields of the form, it kept acting like "< Previous" was clicked. That's not what I wanted at all.

The browser basically worked forward in the tab order from the text field I clicked until it found as submit button within the form ... which happened to be "< Previous".

My solution?

<input type="submit" style="display: none"/>

That empty submit gets "found" (desipite being invisible) before the "< Previous" button, and the form submits normally.

Thursday, June 16, 2005

Sprinting towards Tapestry 4.0 beta-1

Things are coming together faster and faster. Paul Ferraro has checked in his revamp of Tapestry's validation subsystem. It's looking cool, but needs a little tweaking to maximize useability. In the meantime, I've marked ValidField as deprecated ... because Paul's code extends and improves TextField, TextArea, DatePicker and maybe others (in the future).

Its a proper seperation of concerns, something that validation has needed for a while; a single "translator" converts back and forth between a server-side representation (int, Date, BigDecimal, etc.) and strings for the client side. A variable number of "validators" apply constraints to the converted value (such as minimum and maximum length for strings, or minimum and maximum values for numbers and dates). Of course, everything is just implementations of an interface, so its quite extensible.

In a flurry of work (I guess I ended up playing hooky from my paying client today), I integrated in the major portions of my client-side event bus. This has been necessitated by the need for different "flavors" of form onsubmit handlers: normal, refresh and cancel. normal triggers all validation, refresh limits the number of listeners (its primarily meant for refreshing the page when data, such as in a drop down list, changes), and cancel bypasses all other handlers.

I've also added a "translator:" binding prefix to make it easier to use Paul's changes, i.e.

<component id="inputDate" type="TextField">
  <binding name="value" value="date"/>
  <binding name="translator" value="date"/>
  <binding name="validator" value="bean:dateValidator"/>
</component>

However, I don't like having to configure a bean just to do the validation; I like that last parameter to be more like:

  <binding name="validation" value="required,min=7/1/2005,max=12/31/2006"/>

And that means another smart binding factory for constructing and configuring a list of Validator objects.

Wednesday, June 15, 2005

Annotations + Bytecode Generation == Mixins

I'm beginning to use the new Tapestry annotations as part of my current paying gig and I'm very much liking what I see. Starting from the basic Tapestry assumption that classes are abstract and need to be extended at runtime to be concrete actually opens up a goodly number of new techniques.

For example, I have a series of pages that all help edit a large shared object, a RegistrationData instance, stored as an application state object.

Before annotations, I would have had to duplicate the following on each page's specification and Java source:

  <inject property="data" type="state" object="registration-data"/>

  public abstract RegistrationData getData();

But with annotations, the XML part goes away:

  @InjectState("registration-data")
  public abstract RegistrationData getData();

But does even this have to be inside my page class? I have a number of pages that all need the same injected state object, so I can put this into an interface:

public interface RegistrationWizardPage {

  @InjectState("registration-data")
  RegistrationData getData();
}

Now we're on to something; each page extends this interface (but is still abstract) and, in effect, this interface is acting as a mixin. Implementing the interface adds the abstract getData() method and annotation and, thanks to Tapestry's runtime bytecode enhancements, the implementation of getData().

In the past, I've talked about getting away from abstract classes in Tapestry. Now I'm not so sure; the combination of abstract classes and annotations seems awfully potent.

Side note: the Tapestry test suite is well over 1200 individual tests now.

Saturday, June 11, 2005

NoFluff Raleigh/ Coding Galore

Did a quick trip down to Raliegh, NC for the Research Triangle Software Symposium, where I gave the Tapestry intro and Tapestry components sessions. I've been ahving bad allergies lately, and my throat was raw even before I started my three hours of sessions ... fortunately, Justin Gehtland was in the audience and got me some tea, but even so, it was very, very rough. Coughing until I was light headed. Losing track. Getting confused. Even so, I got some pretty rave complements after the session (thanks!).

Interesting crowd, with a few very seasoned Tapestry folks sitting in. Once more, a product company building on Tapestry that I haven't heard of before. The same question ("how does it compare to JSF") and my now stock answer ("Tapestry is designed to allow you to solve problems by easily creating components").

When I wasn't talking, I was coding, putting together more and more annotations (and tests --- 100% code coverage, by the way). You can now specify your component paraemeters via annotations, and your assets, and your beans (though you are slighly limited). Just @InjectMeta and @InjectScript left to go, and maybe @Description.

I got home and added a lookup system for resolving component classes from component types. That is, if you don't provide a component class in the XML, Tapestry will search for component classes in a list of packages you provide, before defaulting to BaseComponent. Just realized that I need to make component specifications optional in the same way that page specificaiton are optional (!) ... since most things you have to specify in XML can now be specified in annotations.

And that's the lesson ... moving away from XML. There's still many Tapestry things that can't (I think) be expressed as annotations, but many, many of the common things can be, and as I'm writing and documenting these annotations, I'm thinking that's the right approach. Side-lining (or at least, reducing) the XML in Tapestry is now a Good Thing (even if it's going to give Geoff fits!).

There's been some push back, the question why is this good? Well, the thing I'm most envious about in the Ruby space is the focus on the code (something brought into high relief by Ruby On Rails). Annotations are the same concept, expressed in Java terms ... there's the code and nothing but, with the annotations wired directly into the code. I am beginning to like!

Thursday, June 09, 2005

Annotations for Tapestry

I'm starting a last minute effort to put basic annotations support into Tapestry 4.0, as part of the main distribution. So far, it's going in very clean. The annotations will allow you to do things in the Java class that are normally reserved for the page or component specifications.

The support will be provided as its own separate, optional library, but part of the Tapestry distribution. The main code will still be JDK 1.3 compatible. We'll be building under JDK 1.5, but setting the compiler's source and target parameters to 1.3/1.1 when building the main line code (keep your fingers crossed!); only the annotation code gets the full JDK 1.5 support.

Because of HiveMind, it will just be a matter of dropping the annotations JAR into the classpath and annotation support will be wired in. That's what HiveMind's all about.

The first annotation I'm working on is @InjectObject, i.e.

@InjectObject("infrastructure:request")
public abstract WebRequest getRequest();

This is equivalent to:

<inject property="request" object="infrastructure:request"/>
in the XML.

One thing I'm missing would be the ability to specify, in Eclipse, on a source-folder by source-folder basis, what compiler and compiler options to use. In addition, I jettisoned commons-lang as a dependency ... its enum support conflicts with the JDK 1.5 enum keyword; that means EnumPropertySelectionModel had to go as well.

Friday, June 03, 2005

SeaView: Built on Tapestry

Found out something interesting today; Glen Stampoultzis mentioned how he's been using HiveMind in his SeaView content management project.

SeaView is written using open source, including Tapestry, HiveMind and Hibernate. This is the first commercial product (as opposed to hosted application) that uses Tapestry in this way. I'd bet there are others out there I don't know about.

Update: ... certainly not the first. I'm getting senile. I did a two-day training session at Widen last year. They sell a shrink-wrap digital asset management product, written in Tapestry.

Thursday, June 02, 2005

Tapestry Portlet Support Finished

I think we're there ... Tapestry 4.0 Portlet Support is finished. Things appear to be working correctly in both eXo and Jetspeed 2.

A last minute nightmare was something I missed in the Portlet spec ... that render parameters (set during an action request) are absolutely maintained when the user clicks a portlet command decoration (view, edit, maximimize, etc.). I had previously thought that there would be no query parameters on such render requests.

This affected my approach, since I had code that would see if there were query parameters and, if not, use a system of confiugrations and services to figure out what page to render. Basically, an extensible system whereby clicking the edit button would activate a page named "Edit", the help button would activate "Help" ... and you can even do tricks based on portlet mode and/or window type to determine the correct Tapestry page.

I had to do a little trickery to get this to work; during action requests, I figure out the page to render, and set the render parameters (service=render, page=XYZ). I also set two additional parameters, portlet-mode and window-state. The render engine service compares the portlet-mode and window-state in the request to the values tored as query parameters ... a mismatch means the user clicked one of those command buttons, so we ignore page and uses the configuration to figure out the page to display. Thus you see Help when the help button is clicked.

I'm not fully happy with it ... you tend to get dumped back out to the View page after help or edit, even if you were on another page. It's a little inconsistent.

From what I can read between the lines, the Portlet spec authors didn't really think this stuff through ... I think they expect Portlets to be brain-dead simple, and for there to be a very easy mapping from portlet mode to JSP, with maybe some conditionals in place for handling window state. I think any realistic Tapestry portlet will consist of several view pages.

This could have been handled more elegantly as a form of event notification ... or even a flag on the RenderRequest indicating that it was a "special" request caused by a change in portlet mode or view state.

Well, what's important is that it's working. You can have multiple Tapestry portlets of the same or different types. You can package multiple Tapestry portlets in a single WAR. Each gets its own HiveMind Registry. I've written documentation.

Now is the time is to get everything left in 4.0 wrapped up and finalized. Time for a beta, and then a goodly amount of bug fixing and documentation ... and a very tight schedule for 4.1.