Spot the Difference
September 26, 2004 – 6:58 pmAt 13:26 this afternoon, more than a day and a half after the
deadline I set my students, I finally got the Tapestry/Hibernate
warmup exercise working to my satisfaction. The final bug took
several hours to track down; when I found out what it was, I almost
decided to throw Tapestry away and use
something a little more predictable.
The problem turned out to be the difference between this:
<!-- Buggy version -->
<tr>
<td colspan="2">
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.returnSubmitAction" value="Return"/>
</td>
<td colspan="2">
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.borrowSubmitAction" value="Borrow"/>
</td>
<td><span jwcid="@TextField" value="ognl:formBorrower"/></td>
<td><span jwcid="@TextField" value="ognl:formEmail"/></td>
<td><span jwcid="@DatePicker" value="ognl:formBorrowDate"/></td>
</tr>
and this:
<!-- Working version -->
<tr>
<td colspan="2">
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.returnSubmitAction" value="Return"/>
</td>
<td><span jwcid="@TextField" value="ognl:formBorrower"/></td>
<td><span jwcid="@TextField" value="ognl:formEmail"/></td>
<td><span jwcid="@DatePicker" value="ognl:formBorrowDate"/></td>
<td colspan="2">
<input type="submit" jwcid="@Submit"
listener="ognl:listeners.borrowSubmitAction" value="Borrow"/>
</td>
</tr>
Can you spot the bug? In the first version, the three text fields
used to submit the borrower’s name, email address, and borrow date
come after the button that the user clicks to borrow books.
In the working version, the text fields come before that button.
That is the only difference—I made no changes to the application’s
.page and .application files, or to the Java
class.
After half an hour of Tapestry in Action and on
Google, I still have no idea why the order matters. If anyone knows
why it does, I’d welcome an explanation…
The real problem here, though, isn’t this bug in Tapestry (and yes, it’s
a bug—even if there’s some obscure engineers-rule “but we had to
because…” explanation for it, it’s a bug). The real problem is the
“spooky action at a distance” [1] nature of framework
programming. Everything that happens between me clicking the “submit”
button on an HTML form, and the corresponding action method being
invoked in my Java class, is, for all practical purposes, hidden from
me.
Oh, sure, I could run Tomcat (or Jetty, or some
other container) under a debugger, and watch Tapestry snatch the form
data from the HTTP stream, read my .application and
.page files, load my class, subclass it on the fly to
create implementations of my abstract methods, add some data members
to store form values, and finally invoke the method I actually wrote,
but saints and small mercies, how long would that take to set up? Or
to run? And how much of it would I actually understand?
On the other hand, how much of that do I want to write myself, over
and over again? The whole point of Tapestry is that it
takes care of these routine tasks for me, so that I only have to worry
about application-specific code. I’ve written enough CGI scripts and
naked servlets to recognize just how much grief a good framework can
save.
So, in the end, this turned out to be another example of what Joel
Spolsky dubbed a leaky
abstraction. Tapestry really does
try to present a high-level model for web programming, but every once
in a while, you can still see what lies beneath. As he said:
Ten years ago, we might have imagined that new programming
paradigms would have made programming easier by now. Indeed, the
abstractions we’ve created over the years do allow us to deal with new
orders of complexity in software development that we didn’t have to
deal with ten or fifteen years ago, like GUI programming and network
programming. And while these great tools, like modern OO forms-based
languages, let us get a lot of work done incredibly quickly, suddenly
one day we need to figure out a problem where the abstraction leaked,
and it takes 2 weeks. And when you need to hire a programmer to do
mostly VB programming, it’s not good enough to hire a VB programmer,
because they will get completely stuck in tar every time the VB
abstraction leaks.The Law of Leaky Abstractions is dragging us down.
[1] In 1935, several years after quantum mechanics
had been developed, Einstein, Podolsky, and Rosen showed that under
certain circumstances quantum mechanics predicted a breakdown of
locality. Specifically, they showed that if quantum mechanics was
true, then someone could put a particle in a measuring device in one
place and, simply by doing that, instantly influence another particle
light years away. Einstein called this “spooky action at a distance”,
and felt it was proof that quantum mechanics couldn’t be the whole
story. Unfortunately for him, Bell’s work in the 1960s, and
experiments in the years that followed, proved that this actually does
happen. To quote another physicist (Haldane this time), “…the
universe is not only queerer than we suppose, but queerer than we
can suppose.” (Unfortunately, the same seems to be true of
many software systems.)
2 Responses to “Spot the Difference”
First, I’ll concede that it is a bug.
The reason this happens is that Tapestry “rewinds” the template and componentry when the button is pressed and the form submitted to the server side. The @Submit component invokes its listener when it is encountered during the rewind, which in the errant case happens *before* the form fields are processed and bound. So technically this is why it happens, but I agree this is awkward and causes confusion. This has been discussed in the Tapestry community and a fix is to defer listener invocation until the @Form itself has completed its work and just before it invokes its listener method.
A workaround is to use the @Form listener if you only have one button. If you have multiple buttons that need separate paths, use their listener methods to set a flag and in the @Form listener method key off the flag and act appropriately.
By Erik Hatcher on Oct 8, 2004
This behavior, although unintuitive, is well documented in Tapestry 3. See the Submit component documentation at jakarta.apache.org/tapestry/3.0.3/doc/ComponentReference/Submit.html
The Submit component’s selected and tag attributes are intended for the situation you describe.
In Tapestry 4, all listeners are deferred until after the form rewind so you can use them in an intuitive way and you don’t have to resort to using selected and tag attributes along with a form-level listener.
By Ryan on Nov 1, 2005