I just finished converting our company's back-office application from
Tapestry 5.1.0.5 to 5.2.5. I had been putting it off because I knew it
would take some time and there was always something to add to the
application. The application has functions for sales, customer support
and billing and is comprised of more than separate 250 pages (.tml
files.)
The conversion took about two days for code changes and testing, plus
less than another day for debugging based on user reports. Here are my
notes on the conversion, which may help others contemplating a similar
upgrade.
First, the new web site is a definite improvement. Secondly, it was
not as simple as updating my pom with the new version.
I first updated my pom for the new versions of Tapestry, Chenille Kit
and Tapestry Security. There were some typical dependency conflicts
that took a while to figure out (Maven is rather stupid.)
The first item I noticed was the changes to Hibernate's API, which
were all easy to fix. (All were constant name changes, which I think
improved the API.)
My @InjectSelectionModel annotation wouldn't compile, of course, due
to the changes to class the transformation API. I do like the new API
much better, since it's easier to debug. However, it did take a few
hours to get it working since I wasn't familiar with the New Way.
At this point, I tried to start up my web app, but it didn't appear to
work. Tapestry wasn't starting up and there was no logging information
to tell me why. After a while, I noticed that one library I included
used an older version of slf4j and Maven decided the old one was the
only one it would include. This made tapestry not startup and not
display any error messages to stdout, either. That was sort of
frustrating. After forcing Maven to use 1.6.1 and removing the
incompatible TapX I was using, it started up. I did google for quite
some time looking for a compatible version of tapx-datefield, but
failed. Later, someone posted the URL to the mailing list but I had
replaced it by then. I'm using the Chenille Kit one, which I like
better anyway. However, it can't easily be used in ModalBox dialogs,
since it wants to initialize positioning after page load, and it can't
do that inside a <div style="display:none">. I ended up adding an
"afterLoad" parameter to ModalBox.show and manually convert the
t:textfields via "new Control.DatePicker".
The most tedious part was converting the deprecated @Include...
annotations with the new @Import one. Also changed the RenderSupport
to JavaScriptSupport.
I had some overly clever code for session state objects where I was
casting it from the interface to the implementation and calling
protected methods from a service. That worked in 5.1, but it causes an
exception in 5.2. I just made all methods public on the SSO.
Next up was figuring out why some pages with Chenille Kit's OnEvent
mixin when added to select components weren't working right. I use
this in several places to update child select components as well as
updating zones. In the multiple select case, I settled on using the
onCompleteCallback feature of the mixin to re-populate the select
lists in JavaScript via JSON returned from the event handler. It's
very, very fast and seamless that way. Many of the select components
were setting an object property on a parent object, e.g. setting the
corporation object on an account object. Oddly, something changed such
that in the @OnEvent handler for changes to the select component, the
value of the property on the parent object was always null. The first
thing I did in the method was to do a query to find all relevant items
for the next select component. That caused Hibernate to flush changes,
resulting in an exception when it tried to write a null value to a non-
null property. I fixed that by explicitly querying based on the key
value passed to the event handler. Not sure what changed here, but it
is working.
Some AJAX routines didn't work due to a change where JSONObject's
toString pretty printed, causing the output to be on multiple lines. I
tracked all of those down and either changed it to toString(true) or
ended up passing the object directly to functions instead of a string
of a JSON object (e.g. in JavaScriptSupport.addScript()).
I ran into several instances where it couldn't find zome t:zones, but
adding a normal "id" attribute (in addition to the "t:id" attribute)
fixed that. I only needed to do this for some t:zones, however.
Continuing with t:zones, I also got the puzzling, "rendered content
did not include any elements that allow for the positioning of the
hidden form field's elements" exception. Searching google turned up
nothing terribly helpful. I ended up moving the t:zone to outside of
my t:form, and that fixed the problem.
I also had some instances where I wanted to assign an "id" to a
component (in my case a submit button.) I generated like: id="delete-$
{object.id}". In 5.2 my ID was nowhere to be found, and you can't do
that trick on t:ids, apparently. I ended up putting in an empty <span>
with the id and used Prototype's next() and previous() methods to find
my objects.
After I had everything working pretty well, I put it onto the
production server where it ran for a few hours and then died with a
PermGen exception. Previously, my app would run for months with the 64
MB of PermGen allocated to servers by default. Now, after upgrading to
5.2.5 it wouldn't run for more than a few hours at 128 MB. I ended up
setting it to 512 MB, but that just seems outrageous. Is this normal
to require 8x PermGen? I haven't made any other change to my app, just
those required to upgrade.
Now I guess I get to work on upgrading to 5.3... I see it claims to
remove the "suppress redirects" functionality, but I still do use that
via a custom annotation that injects in an overridden
ActionRenderResponseGenerator and the
IMMEDIATE_RESPONSE_PAGE_ATTRIBUTE. I hope that doesn't go away.
Norman Franke
Answering Service for Directors, Inc.
www.myasd.com