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!

Sunday, October 16, 2005

Virtual Library Renovations

The Virtual Library example application has now been completely renovated for Tapestry 4.0. It now looks like an application built for Tapestry 4.0 from scratch. It makes heavy use of annotations, friendly URLs (including some custom friendly URLs), application HiveMind services ... all the 4.0 goodies. The only thing that didn't change was the underlying EJBs (except to switch over to HSQL from McKoi).

I'll be writing up some comparison documentation for the next beta.

One of the areas I'm really happy with is the inheritance hierarchy; I was able to flatten it quite a bit. The old code had several levels of inheritance, centered around the pageValidate() method used to restrict portions of the site to logged in users, or to users with admin privilege. The new system moves all that logic to a single base class and drives it by @Meta annotations (with some subclasses overriding the @Meta from the base class).

Part of the transformation was the removal of the VirtualLibraryEngine; all the logic that used to be there is now in various HiveMind services. I borrowed a page from Spring and created a RemoteTemplate service that can execute a RemoteCallback (with retry logic for RemoteExceptions). That removed lots of very ugly loop-and-retry logic from many pages and components, and replaced it with less-ugly RemoteCallback inner classes.

I hope people will take a close look at the new Virtual Library; it continues to be a small, manageable application that can serve as an excellent template for building much more ambitious, real-world projects.

Hopefully in the next beta release, these examples will be distributed as a pre-configured JBoss installation, rather than a patch on top of JBoss. This will be bigger but even easier to setup and execute. Probably in 4.1 we'll shift the code around to use JPox, Cayenne or Hibernate and get away from requiring EJBs or an application server.

Saturday, October 15, 2005

Blogs Aren't For Support

Hate to say this, but this Blog is not a forum for getting free Tapestry support. I've been seeing a creeping number of requests for help and this just isn't the right place!

For free community support, subscribe to the Tapestry User Mailing List and learn how to ask a question that will get a response.

For professional support, you can contact me, or any other member of the Tapestry Support Network to purchase support.

Sunday, October 09, 2005

Google: Tapestry

Just did a ego gratification Google search on the word Tapestry. Guess what's on the top of the list? Used to be, you had to qualify the search as "java tapestry" to even get close. Does this signal any new level of adoption of Tapestry?

In a perhaps related note, I got my Manning royalty statement for Tapestry In Action for April 1 - June 30th ... and it's my biggest (by 50%) to date. Perhaps that has something to do with my appearance at JavaOne?

Thursday, October 06, 2005

ApacheCon 2005 - Tapestry Tutorial

I'll be presenting a half-day tutorial on Tapestry at this year's ApacheCon -- an afternoon session on Saturday, Dec 10. This is a great chance to learn a lot about Tapestry and Tapestry 4.0 especially ... I'll be highlighting new features, such as annoations and the improved validation framework. The format will be based on my labs, though I don't think the time constraints will support interactive labs; instead, I'll be doing the work as we go, explaining Tapestry and Tapestry concepts while getting various mini-applications running in Tapestry.

ApacheCon is in San Diego this year, which offers fewer distractions than Las Vegas, but will be a pleasant change nonetheless.

Sunday, October 02, 2005

Ouch! Class.getDeclaredMethods() works differently in different JDKs

This one is a bit of a pain. In Tapestry, your page and component classes tend to be abstract, with Tapestry providing a subclass, filling in additional methods, fields, constructors and implemented interfaces.

Because of that, it's necessary to duplicate the checks normally done by the compiler ... to check that all abstract methods inherited from base classes actually end up with implementations in the enhanced subclass (that is, the subclass that Tapestry creates).

At the core of this is code that finds all the non-abstract methods in the enhanced class, and up its inheritance chain. It then checks all abstract methods from superclasses or implemented interfaces to ensure that each one is, in fact, implemented.

The problem here is that the built-in Eclipse compiler seems to work differently than the Sun JDK compiler (this is for JDK 1.5).

Under Eclipse, an abstract class that implements an interface does not report those interface methods from getDeclaredMethods(). The only methods reported are the ones in the actual code for the class.

Under Sun JDK, an abstract class will report those interface methods, even if it does not provide implementations for them.

Now, I have tests for this class that I usually run from Eclipse. In fact, this was triggered by a specific bug concerning methods inherited from interfaces; the code is broken into two parts (scanning the inheritance tree for implemented methods, and scanning the interfaces for missing methods). Under Eclipse, interface methods are not reported from getDeclaredMethods(), so we make it to phase 2.

Under Sun JDK, interface methods are reported, it looks like a non-interface abstract method with no implementation, and we fail in phase 1 -- breaking my unit test.

Sigh. This may take some hackery to work in Eclipse and in Sun JDK.

Friday, September 30, 2005

Reworking Tapestry Docs and Demos

A slight shift of focus this week, especially today. I've been reorganizing the Tapestry project home page; from now on, the menu down the left side will be consistent from page to page, not contextual. The tabs across the top are now more like book marks. I think it will make navigating the site easier. Further, the first thing on the home page is another set of quick links to the most common pages, including the Users Guide, Quick Start Tutorial, JavaDoc, etc.

Now, the Quick Start tutorials represent another way to ease newbies into Tapestry. It's been something I delegated to others in the past, and it never happened, so I've taken it back on myself. I'd rather be writing new code or even fixing bugs (or working on my own labs), but I've been getting feedback that the QuickStart is making a big difference among prospective Tapestry users. In fact, there have been over 2200 downloads of the tutorials already!

In the same light is my new approach to the Tapestry demos: the Workbench and the Virtual Library. In the past, you had to build them yourself (because they use non-ASF code, and can't be distributed from the ASF). But that's asking a lot from people, especially those just casually investigating Tapestry.

The new approach is a seperate distribution for the demos, that comes from a non-ASF server (http://howardlewisship.com/downloads/quick-start/).

This bypasses the concerns of the ASF ... though it does eat into my personal web site's bandwidth! But what it means is people can get up and running with Tapestry really easily:

  • Install JBoss 4.0.2
  • Install Ant
  • Unpack the examples
  • Run the build script using Ant
  • (Re)start JBoss
  • Run the applications in a browser

A step up from this would be an installer, but I was short on time to do that today. It would be nice, and there are reasonable, free installers out there. That would eliminate the need for Ant ... but I assumed that nearly everyone interested in Tapestry would already have Ant.

It's a shame that living inside Apache causes these convulsions; it will rise up again in 4.1 when we start wanting to do deeper integration of Tapestry and existing AJAX client-side JavaScript libraries ... that work will have to take place largely off-Apache.

Wednesday, September 28, 2005

Switch?

So, the other day, I had some time to kill at the mall, and I wandered into the Apple store. Walked up to a big G5 with the killer 30" display and downloaded Eclipse 3.1 ... well, within a couple of minutes I was pulling down HiveMind code from subversion and compiling and executing the tests.

Now, on the one hand, I had severe techno-lust. All that eye candy! Vast tracts of land on that giant screen. Candy shaped buttons. Going home in a way (I used to develop on NeXTSTEP). Realistically, I don't think I can afford the 30" display, but maybe the medium sized one and I probably need a pretty maxxed out G5 for the kind of stuff I want to be doing. I mean, right now I do all my development on a Pentium 4M 2ghz with 1 gig of ram and a 40GB drive, but I'm increasingly impatiant with my development environment ... but what I want is something that really screams for all the development I do! And I do want to do some video editting as well.

So, convince me ... I can build out a monster 'doze box and buy a big LCD, or I can go the Apple route. How much is the tax on all those pretty icons? What's the tarrif on the sleek design? And what's the future with the move to Intel looming?

Lots of small objects ... or too many?

I was very interested in the post Object Count Impact on Garbage Collection Performance. As someone who tends to work using large numbers of small objects, I do have a concern in the back of my head about crossing over the line ... especially in light of the slower startup speed of Tapestry 4 (on top of HiveMind) vs. Tapestry 3.

In fact, the article jives well with my thinking; Slobodan's minimum object count map (which he promises to discuss in later blog entries) may likely look like the class I have yet to write ... a very, very simple Map implementation tuned for a small number of keys and a very, very, very high ratio of reads to updates ... the kind of thing that, say, stores attribute values for an XML entity (something used all over HiveMind). I was thinking about using an Object[] array at the core, with alternating keys and values.

Meanwhile, I still think the general approach I take is the right one ... and the reason I can't ever go back to a non-garbage collected language. I like breaking problems into tiny pieces and getting emergent behavior by combining the tiny pieces into more complicated wholes. From an efficiency standpoint, Tapestry is doing the right thing by caching entire pages (not components within pages, which would be more of a JSP tag model). But Tapestry does use an awful lot of HashMaps.

Tuesday, September 27, 2005

Resurrecting the Virtual Library

One of my goals for the final release of Tapestry 4.0 is to have the Virtual Library working again. The goal is to work through the issues, fixing things that are broken by the upgrade from 3.0 to 4.0, and taking advantage of the new features of Tapestry 4.0 as well (including annotations). I think this is important; the Virtual Library is a small but real application, and seeing the upgrade path for it will allow other 3.0 users to extrapolate what they are facing in upgrading to 4.0.

Getting the Virtual Library working has been my focus all day today ... mostly struggling with JBoss. Eventually I gave up, and the turnkey demo will overwrite the defaultDB instance rather than create a vlib database instance ... with that compromise in place, I'm again making rapid progress.

I've also considered porting the code to Hibernate and/or JDO (away from entity EJBs). This would allow the entire application to be delivered as a simple WAR, not an EAR. Getting the shared libraries working in JBoss was a pain ... I had to set the Manifest Class-Path of my EJB jar, to pick up the shared libraries (shared by the EJBs and vlib.war). However, changing the ORM will partially confuse the issue of the changes in the presentation tier, so I doubt I'll even attempt that until 4.1 ... and by then, I might have a bigger, better Tapestry example.

Looks like I'll be spending some time manually re-entering the data as well; QuantumDB had problems exporting and importing the data for me.

Still, I should have most of it done this week .. assuming I continue to play hookey from my paying gig (don't count on it!) So we probably won't see this until beta-10.

Bad JBoss feedback

I harp on feedback all the time, here's an example of why it's so important.

I've been trying to deploy a new Hypersonic database into JBoss 4.0.2, to contain the data for the Tapestry virtual library demo.

When I deploy my application, here's the exception I get:

org.jboss.util.NestedSQLException: No matching credentials in Subject!
       at org.jboss.resource.adapter.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:107)
       at org.jboss.ejb.plugins.cmp.jdbc.SQLUtil.fixTableName(SQLUtil.java:159)
       at org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge.init(JDBCEntityBridge.java:143)
       at org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager.initStoreManager(JDBCStoreManager.java:420)
       at org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager.start(JDBCStoreManager.java:353)
       at org.jboss.ejb.plugins.CMPPersistenceManager.start(CMPPersistenceManager.java:157)
       at org.jboss.ejb.EntityContainer.startService(EntityContainer.java:340)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:272)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:222)
       at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:897)
       at $Proxy0.start(Unknown Source)
       at org.jboss.system.ServiceController.start(ServiceController.java:418)
       at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:177)
       at $Proxy49.start(Unknown Source)
       at org.jboss.ejb.EjbModule.startService(EjbModule.java:395)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:272)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:222)
       at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:897)
       at $Proxy0.start(Unknown Source)
       at org.jboss.system.ServiceController.start(ServiceController.java:418)
       at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:177)
       at $Proxy22.start(Unknown Source)
       at org.jboss.ejb.EJBDeployer.start(EJBDeployer.java:605)
       at org.jboss.deployment.MainDeployer.start(MainDeployer.java:964)
       at org.jboss.deployment.MainDeployer.start(MainDeployer.java:956)
       at org.jboss.deployment.MainDeployer.deploy(MainDeployer.java:775)
       at org.jboss.deployment.MainDeployer.deploy(MainDeployer.java:738)
       at sun.reflect.GeneratedMethodAccessor48.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.interceptor.AbstractInterceptor.invoke(AbstractInterceptor.java:121)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:74)
       at org.jboss.mx.interceptor.ModelMBeanOperationInterceptor.invoke(ModelMBeanOperationInterceptor.java:127)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:74)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:177)
       at $Proxy8.deploy(Unknown Source)
       at org.jboss.deployment.scanner.URLDeploymentScanner.deploy(URLDeploymentScanner.java:325)
       at org.jboss.deployment.scanner.URLDeploymentScanner.scan(URLDeploymentScanner.java:501)
       at org.jboss.deployment.scanner.AbstractDeploymentScanner$ScannerThread.doScan(AbstractDeploymentScanner.java:204)
       at org.jboss.deployment.scanner.AbstractDeploymentScanner.startService(AbstractDeploymentScanner.java:277)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalStart(ServiceMBeanSupport.java:272)
       at org.jboss.system.ServiceMBeanSupport.jbossInternalLifecycle(ServiceMBeanSupport.java:222)
       at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.system.ServiceController$ServiceProxy.invoke(ServiceController.java:897)
       at $Proxy0.start(Unknown Source)
       at org.jboss.system.ServiceController.start(ServiceController.java:418)
       at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
       at java.lang.reflect.Method.invoke(Method.java:585)
       at org.jboss.mx.interceptor.ReflectedDispatcher.invoke(ReflectedDispatcher.java:141)
       at org.jboss.mx.server.Invocation.dispatch(Invocation.java:80)
       at org.jboss.mx.server.Invocation.invoke(Invocation.java:72)
       at org.jboss.mx.server.AbstractMBeanInvoker.invoke(AbstractMBeanInvoker.java:249)
       at org.jboss.mx.server.MBeanServerImpl.invoke(MBeanServerImpl.java:644)
       at org.jboss.mx.util.MBeanProxyExt.invoke(MBeanProxyExt.java:177)

I'm still digging around; since I can connect to the database over TCP/IP, it means the problem is in the connection pool. Still, wouldn't it be nice if JBoss told me that? How about if it told me what was broken - where it was broken - how to fix it? That's the kind of support built into Tapestry and HiveMind: line precise exception reporting plus lots of attention to detail in error messages and error reporting in general.

Well, I've been hacking away for a couple of hours, and will probably be at it for a while yet. Not the way I like to spend my day. Sigh.

Sunday, September 25, 2005

Code Coverage Metrics and Testing

For the last few months, I've been tracking some metrics about Tapestry. On each release, and occasionally in between, I've been tracking the NCLOC (non-comment lines of code), the number of Java source files, the number of tests, and the percentage of code coverage (according to Clover).

I have an almost religous belief that you must have at least 85% code coverage for a project; I think the bugs "hide like cockroaches" and are only flushed out when you hit that magic number. Thus, I spend most of my coding time writing tests, not production code. The trick to staying sane is to remember that production code and test code are all just code, of the same cloth, each useless without the other.

You can see the amount of code growing with occasional downward spikes as dead code is located and eliminated (the large spike around 8/10 represents a change in how I was counting code, counting the code for each sub-project seperately -- these charts are just for the framework itself).

And here you can see the stuggle to keep the code coverage about 85%. Over this same time range, the number of unit tests has risen from 906 to 1,257.

In fact, the news is actually pretty good; in Tapestry most new code, code introduced for release 4.0, has close to 100% code coverage. These charts are just for the framework proper, but the annotations library, for example, has true 100% code coverage (every line of code, every branch).

The majority of new tests are written as true unit tests, on top of EasyMock ... all but about 40 of the tests, which execute mini-applications inside a simulated servlet container. Those 40 tests take about 220 seconds to execute; all the remaining tests combined take about 25 seconds. Thus the power of unit testing.

All told, the Tapestry project includes over 1400 tests. And Tapestry is built upon HiveMind, which has about 740 tests itself.

Personally, I'd like to see the code coverage numbers jump up into the 95% range. I think Tapestry 4.1 will start with a purge of dead and deprecated code, and then a lot of filling in the gaps in the unit tests, before we start working on new ideas. And I have a dream of simplifying the integration tests and productizing them, so that they can be used by Tapestry developers to test their applications. What would be great would be to link together something like jWebUnit with a mock-up of the servlet API, for true unit testing (without having to run the application in a seperate process).

Friday, September 23, 2005

TheServerSide Software Problems

It's discouraging to see news stories on TheServerSide.com about their ongoing software problems. TSS has been a banner site for Tapestry, and chatter about problems makes for bad Tapestry press.

The basic issue is that I signed on with The Middleware Company to do a number of phases of development for TheServerSide.com. The first phase was the basic translation of the site to a component object model, leaving all the functionality unchanged.

At the same time this was occuring, a seperate team was converting the backend access from entity EJBs to Solarmetric Kodo.

In the end, I had less than a week to integrate the two before going live. And yet, for the most part, the result was quite succesful -- measured partly by the amount of time that passed before anyone realized that the site had been almost completely rewritten.

However, with the acquisition of The Middleware Company by Tech Target, my involvement with TSS came to an end; the later, more interesting phases, where we simplified the stack and built considerable UI improvements, has not come to pass. All I've seen is the introduction of more and more ads on the site.

I can't talk to the root problem today; it is incredible frustrating that many posts get accepted and lost. That speaks to some horrible issue with transaction management and the database. Interestingly, TechTarget has not approached me for help ... this also speaks volumes as to where the problems lie.

From observation, and from discussions with Joe Ottinger, I do know that Tapestry is doing exactly what its supposed to be doing, that the functionality problems (missing posts and such) are a problem at the application layer (the stateless session bean used to manage transactions) and the interaction between that layer, Kodo, Coherence, JBoss and the PostgreSQL database. In fact, given the simplicity of the database schema (just six or eight tables) I suspect the problem really is in the configuration and integration of these elements.

Based on what I've read, and some high level discussions I had with them last winter, I believe TechTarget is building a single enterprise wide solution for all their many web sites, migrating away from the Tcl-based Vignette solution used by the majority of their sites, as well as the Tapestry-based solution for TSS.com and TSS.net. All I know about the solution is that it will be based on JEE (assuming that hasn't changed since our discussions).

Thursday, September 22, 2005

Enjoying Web Development with Tapestry

Kent Tong has updated his e-book on Tapestry 3, Enjoying Web Development with Tapestry, in two ways:

  • It now covers Tapestry 4.0
  • You can now buy it as hard copy instead of PDF

I think it's great that books on Tapestry 4 are coming into print ... and that I'm not the one writing them. Not only will other people have a perspective closer to the reader, but the very fact that people outside the central Tapestry developer community, people like Kent Tong and Warner Onstine, are expending the effort to write these books, underscores the kind of high quality community Tapestry has developed over the last few years.

Thursday, September 08, 2005

Minor Eclipse Gripe

I'm a big fan of Eclipse; I think its full of great ideas, and is a big gift to the larger community. It gets the job done quite well. And it's free.

However, there are quite a few rough spots.

One thing I hit quite often is mysteriously locked files. I see this most often when updating or replacing my directories from SVN (and sometimes CVS). Often, an update will fail with some kind of locking error on the local file.

Sometimes, I have the file open in another window. I close the window and the process works. Less than ideal, but I can live with it.

Sometimes, I don't have the file open and I still get the error. My working theory is that Eclipse has it locked, perhaps due to indexing. Often I have to restart Eclipse to get the lock to clear!

Am I the only one who experiences this?

Saturday, September 03, 2005

A clean slate ...

After well over two years, countless installs and uninstalls, a creeping sluggishness, and one horrific event, I finally bit the bullet and rebuilt my laptop computer. Yep, I've formatted the hard drive and reinstalled everything.

Along the way I learned a few things:

  • You have to install Outlook just to transfer mail messages into Thunderbird. Hopefully it will uninstall OK.
  • JDK 1.5 will only install into C:/Program Files/Java/, which gives Ant fits (the space in the file name is a problem). After trying things with shortcuts, I eventually copied C:/Program Files/Java/jdk1.5.0_04/ to C:/.
  • I no longer can remember how I got svnserve (the Subversion server) to start automatically! Woops! Perhaps its a difference between the cygwin version and the Win32 native version?
  • I'm having trouble getting my SSH and PGP keys back. This could cause some grief.

I'm still picking up the pieces but I'm very close now. A key tool for me in this endeavor was Acronis True Image, which can take a snapshot of an entire partition and put it on backup media (or an external drive, in my case). Even better, you can "mount" the image as a new read-only drive, making it easy to locate and retrieve individual files you need.

Will my Dell Inspiron 8200 return to its former glory? Hard to say ... I think Windows is getting slower with each release and security patch. But so far, things are much quicker. We'll see once we get to real development.

Update: I have Subversion for Cygwin working great. The missing piece was the --foreground argument. My repository is at c:/svnrepo, so my command line is: cygrunsrv --install Subversion --path /usr/bin/svnserve --chdir /cygdrive/c/ --args "--foreground -d -r svnrepo"

After that, I can start Subversion:

cygrunsrv -S Subversion

Once started, it appears to restart automatically on each reboot.

You can verify a correct start:

bash-3.00$ cygrunsrv -Q Subversion
Service             : Subversion
Current State       : Running
Controls Accepted   : Stop
Command             : /usr/bin/svnserve --foreground -d -r svnrepo

Wednesday, August 31, 2005

Tapestry 4.0 Documentaton

Just a public thank-you to Pierre-Yves Nicolas, the first person from the larger Tapestry developer community to start providing component reference documentation. As I said, this is a chance for people to get noticed and make a difference ... it's largely just documentation and some more bug fixes between now and a final 4.0 release.

Monday, August 29, 2005

Subversive Tapestry

A big move this weekend ... Tapestry source code has moved out of Apache's CVS repository and into the Apache SVN (Subversion) repository. Let the refactoring commence!

In addition, Tapestry 4.0-beta-5 was released, continuing our style of releasing every week or two. This release really cleans up the way Tapestry generates client-side element ids (they are now XHTML compatible, so no more embedded dollar signs or leading underscores). The JavaScript generates to support client-side validation has been cleaned up and simplified ... its much shorter and much more readable now.

People keep asking when Tapestry 4.0 will be final; right now, it's just bug fixes none of which seem too terrible, and documentation, which is more of a problem. I'm still hoping for release candidates in the month of September.

Wednesday, August 24, 2005

Tapestry 4.0 progress and such

HiveMind 1.1-beta-3 is out, which makes it easier to build Tapestry 4.0. That would be nice because I've been trying to get people to help with the documentation, and they need to be able to build things first.

I've also updated the Tapestry QuickStart Tutorials, bundling HiveMind 1.1-beta-3 and the latest snapshots of Tapestry 4.0-beta-5.

I've been fixing lots of little bugs in Tapestry 4.0, mostly stuff related to client-side JavaScript. There's some issues with the compliance of the HTML generated by Tapestry, Tapestry (beta-4) often generates ids that include the '$' character. For example, when a form contains a loop, the element ids will be 'textfield', 'textfield$0', 'textfield$1' (in beta-4). Now the same sequence will be 'textfield', 'textfield_0', 'textfield_1'. In addition, much of the client-side JavaScript has been changed to use document.getElementById() rather than document.form-name ... including accessing of form elements (which now render an id attribute as well as a name attribute).

There's still much to do with Tapestry 4.0 before it hits a release candidate, and balancing that work with my clients (not to mention my move to Portland, Oregon) has been challenging.

Meanwhile, I've been using Subclipse 0.9.32 and it's seeming a bit more stable than the .30 version I was using. It's about time for Tapestry to move over to SVN ... we already took the vote, and have just been waiting for a "quiet time". The fact that CVS has been behaving badly at apache.org just makes the urgency of the move that much more plain.

Tuesday, July 26, 2005

Tapestry 4.0-beta-3, TapestrySupport.com

Tapestry 4.0-beta-3 is now available; it's more bug fixes. I've reworked how automatic cursor positioning works; it's no prioritized: first field in error, or first required field, or just plain first field. You can now turn off automatic cursor positioning (aka "focus") but settting the enclosing Form's focus parameter to false.

I've also started reviving the Virtual Library example. I'm having a little trouble getting McKoi DB deployed into JBoss 4. I briefly considered switching over to Derby, but that's not available via the Maven repository yet (still alpha).

The Virtual Library will change quite a bit as I get it working for Tapestry 4.0, as I rework it to use all the nifty features (including annotations). I'm keeping notes to form a more general Tapestry 3.0 -> Tapestry 4.0 upgrade guide.

Meanwhile, after much too long, the TapestrySupport.com web site has gone live, and a press release is going out. This is a site dedicate to professional support of Tapestry. Check it out!

Friday, July 22, 2005

Don't shit where you eat

I made a recent change to the set of build scripts used by Tapestry and by HiveMind; it no longer builds inside my workspace. Let me explain why this is a good thing.

Your workspace is supposed to be a local image of the files stored in your source code repository, so that you can make changes to those files, then follow a compile/test/fix cycle, and eventually check the source files back in.

A common problem in many projects is when derived files, .class files, or .jar files, get accidentally checked in to a project repository. This can cause a lot of confusion, and may be difficult to clean up.

But more importantly, I've noticed when using Eclipse is that the number of files (not source files, but file system files) in my workspace affects the performance of the IDE. Somewhere, in the background, it is constanty checking for changes and re-indexing files using Lucene. Builds of Tapestry and HiveMind create a huge amount of documentation ... Javadoc, HiveDoc, test reports, clover code coverage and so forth. Thousands upon thousands of files ... none of which are interesting to me as a developer.

In the old scheme, all the output classes and jars and instrumented Clover source files, and everything else, were in target directories inside the workspace.

That's whay I mean about shitting and eating; it's better to stay clean.

What I did was change my Ant build files to create the target directories under ${java.io.tmpdir}. That is, a shadow project structure is created there, directories like C:\WINDOWS\TEMP\jakarta-tapestry\workbench\target. Just about everything goes there, except for a handful of output jar files (as a convienience) and some Forrest files (out of necessity).

My builds and my IDE run better and faster now. I think this is a model that should be followed by other build tools, such as Maven.

Sunday, July 10, 2005

Tapestry 4.0-beta-2

The newest release of Tapestry is available for download. The changes are largely bug fixes, mostly for problems with annotations and client-side JavaScript.

There's also the start of a Quick Start tutorial.

Tapestry JavaOne Examples Available

I've finally had a chance to package and upload my source code and presentation from JavaOne 2005. It's available as http://howardlewisship.com/downloads/javaone-examples.tar.gz

Thursday, July 07, 2005

<> vs. () makes me :-(

All I wanted was fewer casts. I'm sick of typing all those casts (they tend to get out of control when using EasyMock). The compiler knows enough about the left and right side of my assignments to require a cast, why can't it just provide it (this is the Dave Thomas observation).

Now, in an attempt to simplify things, we've replaced casts with generic type constraints. So instead of:

Map _map = new HashMap();

public RegistrationData getRegistration(String userId)
{
  return (RegistrationData) _map.get(userId);
}
With generics we get:
Map<String, RegistrationData> _map = new HashMap<String, RegistrationData>();

public RegistrationData getRegistration(String userId)
{
  return _map.get(userId);
}
So, much more repetition and uglier code to avoid that one little cast. A cast the compiler could just as easily have provided for me? One that still exists at runtime (that's type erasure). This is simpler?

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.

Tuesday, May 17, 2005

MyEclipse now has Tapestry support via Spindle

MyEclipse (which creates and bundles Eclipse plugins for an annual subscription fee) has just announced a new 4.0 milestone release, with support for JSF and Tapestry. The Tapestry support is pretty obviously Spindle, but perhaps they'll do more than that in the future.

Wednesday, May 11, 2005

Big stack trace

So, I'm testing exception reporting in Portlet Tapestry under eXo and it's working pretty well. The same information as Servlet Tapestry, but not as pretty (because of the lack of style sheet control).

I'm amused at how deep the stack traces are. You can see a lot of Tomcat, eXo and AspectJ-generated code long before it even gets to Tapestry:

  • ognl.ObjectPropertyAccessor.getProperty(ObjectPropertyAccessor.java:123)
  • ognl.OgnlRuntime.getProperty(OgnlRuntime.java:1616)
  • ognl.ASTProperty.getValueBody(ASTProperty.java:96)
  • ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
  • ognl.SimpleNode.getValue(SimpleNode.java:210)
  • ognl.Ognl.getValue(Ognl.java:333)
  • ognl.Ognl.getValue(Ognl.java:310)
  • org.apache.tapestry.services.impl.ExpressionEvaluatorImpl.readCompiled(ExpressionEvaluatorImpl.java:84)
  • $ExpressionEvaluator_103cbf6e379.readCompiled($ExpressionEvaluator_103cbf6e379.java)
  • org.apache.tapestry.binding.ExpressionBinding.resolveExpression(ExpressionBinding.java:110)
  • org.apache.tapestry.binding.ExpressionBinding.getObject(ExpressionBinding.java:103)
  • org.apache.tapestry.binding.AbstractBinding.getObject(AbstractBinding.java:87)
  • $Insert_1.getValue($Insert_1.java)
  • org.apache.tapestry.components.Insert.renderComponent(Insert.java:42)
  • org.apache.tapestry.AbstractComponent.render(AbstractComponent.java:612)
  • org.apache.tapestry.BaseComponent.renderComponent(BaseComponent.java:92)
  • org.apache.tapestry.AbstractComponent.render(AbstractComponent.java:612)
  • org.apache.tapestry.AbstractPage.renderPage(AbstractPage.java:273)
  • org.apache.tapestry.engine.RequestCycle.renderPage(RequestCycle.java:355)
  • org.apache.tapestry.portlet.PortletRendererImpl.renderPage(PortletRendererImpl.java:70)
  • $PortletRenderer_103cbf6e30f.renderPage($PortletRenderer_103cbf6e30f.java)
  • org.apache.tapestry.portlet.RenderService.service(RenderService.java:45)
  • org.apache.tapestry.services.impl.EngineServiceOuterProxy.service(EngineServiceOuterProxy.java:65)
  • org.apache.tapestry.engine.AbstractEngine.service(AbstractEngine.java:240)
  • org.apache.tapestry.services.impl.InvokeEngineTerminator.service(InvokeEngineTerminator.java:60)
  • org.apache.tapestry.portlet.RenderRequestServicerToWebRequestServicerBridge.service(RenderRequestServicerToWebRequestServicerBridge.java:49)
  • org.apache.tapestry.portlet.ApplicationPortlet.render(ApplicationPortlet.java:161)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody8(PortletApplicationHandler.java:226)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody9$advice(PortletApplicationHandler.java:458)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody10(PortletApplicationHandler.java)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody11$advice(PortletApplicationHandler.java:340)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody12(PortletApplicationHandler.java)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody13$advice(PortletApplicationHandler.java:172)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody14(PortletApplicationHandler.java)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody15$advice(PortletApplicationHandler.java:738)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody16(PortletApplicationHandler.java)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.render_aroundBody17$advice(PortletApplicationHandler.java:628)
  • org.exoplatform.services.portletcontainer.impl.PortletApplicationHandler.process(PortletApplicationHandler.java:226)
  • org.exoplatform.services.portletcontainer.impl.servlet.ServletWrapper.service(ServletWrapper.java:70)
  • javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
  • org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
  • org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
  • org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:704)
  • org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:590)
  • org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:510)
  • org.exoplatform.services.portletcontainer.impl.PortletContainerDispatcher.dispatch(PortletContainerDispatcher.java:370)
  • org.exoplatform.services.portletcontainer.impl.PortletContainerDispatcher.process(PortletContainerDispatcher.java:310)
  • org.exoplatform.services.portletcontainer.impl.PortletContainerDispatcher.render(PortletContainerDispatcher.java:271)
  • org.exoplatform.services.portletcontainer.impl.PortletContainerServiceImpl.render(PortletContainerServiceImpl.java:144)
  • org.exoplatform.portal.faces.renderer.html.portlet.PortletRenderer.encodeChildren(PortletRenderer.java:100)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRowRenderer.renderViewMode(ContainerRowRenderer.java:40)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRenderer.encodeChildren(ContainerRenderer.java:34)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRowRenderer.renderViewMode(ContainerRowRenderer.java:40)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRenderer.encodeChildren(ContainerRenderer.java:34)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerColumnRenderer.renderViewMode(ContainerColumnRenderer.java:40)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRenderer.encodeChildren(ContainerRenderer.java:34)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRowRenderer.renderViewMode(ContainerRowRenderer.java:40)
  • org.exoplatform.portal.faces.renderer.html.container.ContainerRenderer.encodeChildren(ContainerRenderer.java:34)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.faces.core.renderer.html.HtmlBasicRenderer.renderChildren(HtmlBasicRenderer.java:63)
  • org.exoplatform.portal.faces.renderer.html.portal.PortalRenderer.encodeChildren(PortalRenderer.java:58)
  • javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:693)
  • org.exoplatform.portal.faces.application.ExoPortalViewHandler.renderView(ExoPortalViewHandler.java:63)
  • com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:87)
  • com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:200)
  • com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:117)
  • javax.faces.webapp.FacesServlet.service(FacesServlet.java:198)
  • org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
  • org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
  • org.exoplatform.portal.filter.PrivateRequestFilter.doFilter(PrivateRequestFilter.java:82)
  • org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
  • org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
  • org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
  • org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
  • org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
  • org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
  • org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
  • org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
  • org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
  • org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
  • org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
  • org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
  • org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
  • org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
  • org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
  • org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
  • org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
  • org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
  • org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
  • org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
  • java.lang.Thread.run(Thread.java:536)

Do stacks this deep affect performance? Who knows? It's just interesting to see. Back in the day, when I was a PL/1 coder for Stratus, there was operating system code and application code. The operating system code was very stable, and the Stratus file system was like a simple hierarchical database (not dissimilar to what you can do today with an embedded database like Berkeley DB).

There was no "application stack". No frameworks, no libraries, no toolkits, no open source -- except what we created ourselves (even back then, I tended to write libraries and test suites). The application was our single layer on top of the operation system. When things went wrong, you would rule out the operating system, and start picking apart your code.

It's so much more complex now. "Your code" is a single eye dropper-full in an olympic sized swimming pool, mixed in with open source and proprietary code up the wazoo. When things go wrong, it can be a major effort just to identify the culprit, never mind provide a fix or work around.

Just chalk it up as a sign of the times; the demands of having 24/7 coverage, supporting all kinds of clients, high reliability, remote monitoring and administration ... all of these things add up to more and more code around your application and, barring some blazing insight within the industry, it's not going to change any time soon. But it does make me cringe a bit a deployment time, makes me nervous -- will it work in production? And I've been there, reworking code under deadline to get transactions and remote JMS queues working together inside a WebLogic cluster.

There's a quote I half remember from one of the Apollo astronauts, sitting on the pad, during the countdown ... trying not to remember that their lives depended on hundreds of thousands of different parts, all provided by the lowest bidder. Deploying a major application sometimes feels like that ... even when some of the parts are quite expensive, and others are routinely free.

Sunday, May 08, 2005

OSCON 2005

I was just perusing the schedule for OSCON 2005, which is the first week in August this year, in Portland, Oregon.

They don't have a master side-by-side session schedule, which makes it tedious to pick a path through the many tracks. Here's what I've found, so far, for me:

Ruby: Ruby on Rails: Enjoying the Ride of Programming David Heinemeier Hansson Mon 8/1/ 1:30-5
Emerging Topics: Learning Ajax David Heinemeier Hansson Thu 8/2 8:30-12
Linux: Application Development With Firefox and Mozilla Shane Caraveo Wed 8/3 10:45-11:30
Java: SiteMesh: A Simple Approach to Web Site Layout Joe Walnes Wed 8/3 4:30-5:15
Java: Tapestry In Action Howard M. Lewis Ship Wed 8/3 5:20-6:05
Java: RAD That Ain't Bad -- Domain Driven Development with Trails Chris Nelson Thu 8/4 11:35-12:20
Ruby: Metaprogramming Ruby Glenn Vanderburg Thu 8/4 2:35-3:20
Ruby: Dependency Injection: Vitally Important or Completely Irrelevant? Jim Weirich Thu 8/4 4:30-5:15

Friday, May 06, 2005

I would'nt install Google WebAccelerator if I were you ...

I was a little "bleeding edge" today and installed Google Web Accelerator. Woops. It kind of screws things up for a lot of sites I hit and is a little tricky to uninstall (we'll see if I'm rid of it after a reboot). Others have seen security issues.

Update: After uninstalling it, rebooting, re-installing FireFox ... it is still running on my computer. Whenever I view source, I see:

<script language='javascript' src='http://127.0.0.1:1029/js.cgi?pca&r=5436'>>/script>

On my pages; worse, that URL is live, and returns:

bash-2.05b$ curl http://127.0.0.1:1029/js.cgi?pca\&r=5436

var blockedReferrer = 'blockedReferrer';
NS_ActualWrite=document.write;
// Popup Blocker -->
RanPostamble=0;
NS_ActualOpen=window.open;
function NS_NullWindow(){this.window;}
function nullDoc() {
   this.open = NS_NullWindow;
   this.write = NS_NullWindow;
   this.close = NS_NullWindow;
}
function NS_NewOpen(url,nam,atr){
        if((nam!='' && nam==window.name) || nam=='_top'){
           return(NS_ActualOpen(url,nam,atr));}
        obj=new NS_NullWindow();
        obj.focus = NS_NullWindow;
        obj.blur = NS_NullWindow;
        obj.opener = this.window;
        obj.document = new nullDoc();
        return(obj);
}
function NS_NullWindow2(){this.window;}
function NS_NewOpen2(url,nam,atr){
        if((nam!='' && nam==window.name) || nam=='_top'){
           return(NS_ActualOpen(url,nam,atr));}
    return(new NS_NullWindow2());
}
function op_stop() { NS_ActualOpen2=window.open; window.open=NS_NewOpen2; }
function op_start() { window.open=NS_ActualOpen2; }
function noopen_load() {
    op_stop(); if(zl_orig_onload) zl_orig_onload(); op_start();
}
function noopen_unload() { op_stop(); if(zl_orig_onunload) zl_orig_onunload(); op_start(); }
function postamble() {

  if(!RanPostamble) {
    RanPostamble=1;
        zl_orig_onload = window.onload;
        zl_orig_onunload = window.onunload;
        window.open=NS_ActualOpen;
  }
}
window.open=NS_NewOpen;
document.ignore = new Object()
bash-2.05b$

So don't install it; can't get rid of it, and I'm pissed!

Update #2: I'd some more checking; this thing claims to be a popup blocker, but I think it might be part of some Spyware. Or it might be some kind of kludgey thing supplied by Firefox to block popups?

Update #3: It is a popup ad blocker, it's part of ZoneAlarm Pro, which I've been testing out. Seems like it will hijack almost any HTTP traffic ... and interesting problem that could break some Ajax code potentially!

Just fired up Eclipse 3.1M6

I just fired up eclipse 3.1M6 for the first time, on my existing workspace. This will be interesting to see what works and what doesn't. I'm hoping to start experimenting with JDK 1.5 features at some point soon, even though I don't plan on using them in the Tapestry or HiveMind cores for some time to come.

JavaOne: Tapestry In Action

My session, Tapestry In Action, at this year's JavaOne has been scheduled. Mark your calendars!

It's a 60 minute session, from 1:30 PM to 2:30 PM on Wednesday June 29th. It'll be in Esplan 307-310, which seats 1200 (!).

Sixty minutes is not much time to cover any aspect of Tapestry; this will be an accelrated version of the my Tapestry NoFluffJustStuff session, with no live coding, and more code on the slides. It uses similar examples to NFJS, it's been recoding against Tapestry 4.0.

Thursday, April 28, 2005

An interesting idea: FieldLabel & ValidField

There's been some talk on the Tapestry developer list about radically reworking Tapestry's validation support (primarily, ValidField, FieldLabel and the helper classes ValidationDelegate and all the IValidator implementations). There's a lot of good reasons to do that ... the validation support in Tapestry is pretty good, but was written largely in 2001 and 2002, with only minor tweaks (such as client side validation) since then. Especially in Picasso, there's a lot of opportunity to improve it.

Until that happens (and based on the amount of movement on this subject, a complete overhaul is not likely to make it into the 4.0 release), people are still depending on the existing validation.

One of the most annoying, if relatively minor, problems with validation is the relationship between FieldLabel and ValidField when rendering, with decorations, inside a complex form (that is, with loops). There gets to be a nasty off-by-one problem, where the FieldLabel is out of sync with the ValidField and the wrong FieldLabel instance (within the enclosing loop) get decorated.

This is because the FieldLabel renders before the ValidField (in most Western language, left-to-right style layouts). I came up with a kind of kludge about how to approach this, where we gimic things to get the ValidField to render before the FieldLabel ... but then defer emitting that output into the response stream until the correct point.

What occurred to me this morning is that, with a modest amount of effort, we could bake this directly into the components That is, the FieldLabel could:

  • Obtain its field (i.e., a IFormComponent instance)
  • Create a nested IMarkupWriter
  • Have the field render into the nested writer
  • Store the writer as an IRequestCycle attribute, using a key that includes the form component's extendedId
  • Do it's own rendering

The ValidField (and all the other IFormComponent implementations) would have to:

  • See if there's a pre-renderered writer IRequestCycle attribute
  • If so: close the writer (which injects the buffered markup into the outer writer) and remove the attribute
  • If not: render normally

It's a bit of work, since it will require updating all the form control components. It's also a chance to get, at least, decoration of all form control components working.

In addition, I believe I'll change the Form component to provide a default IValidationDelegate even when its delegate parameter is not bound ... and change the code inside the various components to expect a non-null delegate from the form.

Finally, I've been thinking that an alternate way to define validators, using a binding prefix, would be useful, something like:

<binding name="validator" value="validator:string,required,minLength=3"/>

Friday, April 15, 2005

Tapestry Picasso will be 4.0

The finally tally for last week's vote is in. The next release of Tapestry, what's currently in CVS HEAD, will be Tapestry 4.0. So after all the talk of Tapestry 3.1, it's going to be a phantom. I hope to get a Tapestry 4.0-alpha-2 out soon as well (but need a stable HiveMind 1.1 alpha release first).

The numbering confusion is exactly why I'm pushing for the release naming experiment. What I've been calling "3.1" for so long is now going to be called, internally, "Picasso". This will give us the freedom to consistently discuss the code base before having to decide on a release number for it.

Other projects, such as Struts, work differently. After a release, they'll vote on its stability, and "grant" it final release status. So you might find that Struts 1.1.23 (or something) is the final release. I kind of like knowing that Tapestry 4.0 is the final, stable release for 4.0. Certainly, there'll be a 4.0.1 with bug fixes, and hopefully soon after that, a Tapestry Magritte (4.1) with more functionality. And having names is more fun than numbers.

Wednesday, April 13, 2005

Improved Listener Methods in Picasso

I'm always on the look out for ways to further simplify Tapestry development; that's one of my core principles. Simplicity. I personally love complexity ... on the inside, doing the magic that makes the end-developer's job that much easier.

One thing that's bugged people for a while concerns listener methods. These are public methods on your classes that can be invoked by forms or by the DirectLink component.

A common thing to do with DirectLink is to pass additional information in the URL. This information takes the form of listener parameters (which were confusingly called "service parameters" in 3.0 and earlier). Anyway, you can ask the request cycle for the parameters, and then extract and cast them to the right types. But that's not simple enough.

For example, suppose that the link encoded a String objectId and an integer index. The component in the template names the listener method, and the two parameters are passed into the DirectLink as an OGNL list expression:

<a jwcid="@DirectLink" listener="doClick" parameters="{ objectId, index }"> . . . </a>
This shows another recent recent improvement: the "listener:" prefix is assumed for the listener parameter, and "ognl:" is assumed for the parameters parameter. If that's not appropriate, you can still provide an explicit prefix. Kudos to Erik Hatcher for that idea.

In the Java class, the listener method might look like:

public void doClick(String objectId, int index)
{
  . . .
}

That's the innovation and simplification; the listener parameters are automatically converted into method parameters, saving you the trouble you'd have to do in release 3.0:

public void doClick(IRequestCycle cycle)
{
  Object[] parameters = cycle.getListenerParameters();
  String objectId = (String)parameters[0];
  int index = ((Integer)parameters[0]).intValue());
 
  . . .
}

The change is backwards compatible; there's a little search for the best matching method implementation, and the last thing checked is the Tapestry 3.0 style listener method (with the single IRequestCycle parameter).

I honestly can't remember where the idea for this came from; it seems so obvious in retrospect that you'd think I came up with up, but I think it was Erik Hatcher again. Oh, and the test suite is up to 960 tests, 277 seconds, and 82.4% code coverage.

Monday, April 11, 2005

HiveMind article on TheServerSide

James Carman's Introduction to HiveMind has appeared on TheServerSide.com today. James must have been working pretty hard, this final version of the article is much more in depth than the early version of the article I reviewed for him a couple of month's back.

I'm really psyched about the HiveMind community ... maybe even more so than the Tapestry community. Not to dig at the Tapestry or developer or user community, I just think that HiveMind is running truer to the ideals of an Apache open source project. I think that HiveMind started on the right foot, and has several key members who are contributing code, but also recognition (such as this article by James, who is a HiveMind committer). Perhaps its because HiveMind is a more well defined problem space than Tapestry, and started more clean and better organized. I hope to get the Tapestry project functioning as smoothly as the smaller HiveMind project some day.

Just finished reading the article; he covered a lot of ground quite nicely, but didn't even mention HiveMind's distinguishing characteristics: distributed configuration (that is, support for many individual modules) and configurations. Most of his examples could be done almost as easily in Spring (though I don't think Spring has a threaded service model). A follow-up article, perhaps?

Property Persistence on the Client

Over the weekend, in between NoFluffJustStuff sessions, I was busy working on adding the 'client' strategy for persistent page properties. In Tapestry 3.0 and earlier, there was a single strategy: persistent page properties are stored into the HttpSession.

Starting in Picasso (4.0), you can specify that a persistent property is stored on the client instead of in the session (on the server). It looks like this:

<property name="foo" persist="client"/>

Once this is in place, and once a change occurs to the foo property, all links and forms in the application will generate additional query parameters (or hidden fields) to track the value of foo. Actually, it uses a single query parameter per page to store all client persisted properties on that page.

It may still need some tweaking; already I can see that the URLs can get very long, very quickly. For example, after setting a couple of properties on a couple of pages of the Workbench:

http://localhost:8080/Palette.direct?component=%24Border.pageLink&session=T&sp=SChart&
state:Fields=H4sIAAAAAAAAAFvzloG1XI6BgYGRgUE8OSczNa8kLDEnMyWxJDM%2FzzUvMSknNaW4iEEwK7E
sUS8nMS9dzyk%2FPyc1Me%2BsQlHD1Tm%2F3jExMEYxsJYl5pSmVhQwAADTbn6TTwAAAA%3D%3D&state:Pale
tte=H4sIAAAAAAAAAFvzloG1nJuBgYGRgYGlOL%2BopLiIQSe%2FKF0vsSAxOSNVrySxILW4pKhSLzk%2Fr6Qo
M0mvIDEntaQkVS8YqNY3PyW1qyv4%2BsQ1f%2FczMTBUFDEoImlNzs%2FNzc8r1stJzEvXS80rzdVzBRI%2FbV
d5bjYMq2JiYPRhYM30S8xNLWEQ8slKLEvUB6nUDwZak5duXVFQwsDq4%2Bjk6gMAGprRD6MAAAA%3D

Given the practical limit of 4000 characters in a URL (don't ask me for references on that), this could be a problem.

The strategies are defined by a configuration point which is, of course, extensible. I expect to add more strategies. For example, a strategy that stores data on the client only until the there is a server-side session, then stores data on the server instead. Or, with some minor tweaks to the APIs, a strategy that store persistent property data, but only inside forms, not inside link URLs.

Drew Davidson has kludged into Tapestry 3.0 some other lifecycle options; he has pages that keep their state as long as the direct service is used, but when the page service is used (to leave a group of related pages), the persistent state for those pages is discarded.

I've been thinking of another option, where different versions of a page's state are stored on the server, and a token comes up in the request to indicate which state is used. This could go a long way towards automatically handling the browser-back button problem ... browser back to an earlier state, and continue working from that state. Actually, properly handling the browser-back button (and perhaps that duplicate form submit scenario) may take a mix of techniques.

Monday, April 04, 2005

JODE: Java decompiling plugin for Eclipse

One more tool for the standard toolkit: JODE Eclipse Plugin. By adding this plugin (and making one small configuration change), you will no longer see the "No source attachment" page when you navigate to a class provided by a JAR. Instead, it uses JODE to decompile the class back to source code and shows you that. It does a quite credible job, especially if the class was compiled with debugging symbols. This is usually more than sufficient for when I have a simple question about an API ... better than hunting around for the source on the web, or finding a source download to attach.

Wednesday, March 30, 2005

700

Just added the 700th test to the HiveMind test suite. I'm adding code so that you can omit the interface attribute on <service-point> when it matches the id of the service point. One interface, one service point (with the same name), one implementation class is a pretty common pattern.

Tapestry is closing in on 900 tests.

Tuesday, March 29, 2005

Tapestry Portlet Progress

Whether we call it 3.1 or 4.0, the new version of Tapestry (supporting JSR-168 Portlets) is coming along. Basic forms and links are now working. The portlet namespace is used to extend the name generated by Tapestry (so instead of "Form0" you might see "Form0Pf0403f0dc0a8fe0201a826c5ae430341I"). That goes most of the way towards ensuring that JavaScript variables and method names are unique ... just a little extra tweaking will virtually ensure it.

The hardest part, besides cleaving Tapestry away from the Servlet API, has been the division between the action request and the render request. This causes a few problems ... not least of which is what to do with an error during the action request? We can't just render the response immediately ... I'm going to experiment with a solution where we render the response into a buffer that will be available in later requests.

Some similar problems exist for handling stale link and stale session exceptions. Nothing insurmountable.

The asset service is a bit of a pain; it will be necessary to map an ApplicationServlet, just to handle the asset service. The Portlet API doesn't include any concept similar to a servlet that renders a dynamic image. This is problematic for assets ... its even worse for getting Portlets to support Ajax-style operations. The worst part is that the servlet will need its own HiveMind Registry ... as will each Portlet.

Still, I'm pretty pleased with the progress and how its shaping up. Servlet Tapestry is improved as well. Of course, I'm brimming over for ideas for the next generation of Tapestry (5.0) where we get rid of abstract classes, inheritance (from Tapestry base classes) and XML specifications (in favor of annotations). But 3.1/4.0 (whatever we call it) will be quite extraordinary in its own right.

Thursday, March 24, 2005

Another Satisfied Customer

Jordan Redner, of shopping.com is a past client (I did a few days of training for them back in November). He's turned into a very enthusiastic Tapestry booster in the Bay Area. He recently wrote:

I work at Shopping.com and we've just rebuilt 3 major public-facing applications using Tapestry. The old versions were Perl/Mason.

With a core team of 3 strong Java developers, 1 CSS/HTML person, and 1 Oracle DB developer, we rebuilt what was estimated to take 4 man years in 4 months with some time to spare. I seriously don't think we would have been able to pull this off in Struts/JSP which was the runner up choice for us. In fact I think it could have been a disaster, taking much longer. The application is fairly complex and includes very rich I18N functionality such as rendering of strings in various language (from resource bundles & from the database), rendering currencies, collation by language, etc... Anyway, it was a huge application, and it has been a huge success. I am happy about it and needless to say, so is the business.

I've used many frameworks and have been doing web development for years in the Bay Area. Previously, I've managed and implemented large projects in ASP.NET with back-end Java components. I actually like the class-backing-a-page design of .NET which is similar to Tapestry, except for the fact that ASPX has the same pitfalls as JSP, and it's not Java.

Wednesday, March 23, 2005

It's a Tap- Tap- Tap- Tapestry World!

One of the more frequent questions I get concerning Tapestry is ... how many sites are using it in the real world? It's a tough question, since I generally only hear from people who have problems and not nearly enough success stories. I asked around on the Tapestry mailing lists and got some pointers to a few sites ... here's just a few (that had cool logos).


TheServerSide.com should be pretty well known; it may be the largest Tapestry site, getting millions of hits per month.


TheServerSide.net runs on the same software, but with some different assets and configuration.


Shopping.com's main site just switched over from Perl/C++/CGI to Struts, but many of their current ancillary applications, such as Merchant account management, are done in Tapestry. They expect to use Tapestry on virtually all new internal and external web development.


The NHL may be having trouble, but it isn't because of Tapestry. Eric Everman, who created the site, tells me he is happy to return to more Tapestry work at NHL.com, after a painful sojourn into Struts development for another client.


Erik Hatcher and Jamie Orchard-Hays were part of the team that built a suite of applications for Darden ... in fact, that was one of my first Tapestry training engagements. The same application has been skinned and reconfigured for a number of clients, including the University of Chicago Business School.


This site was done by Paul Ferraro, a Tapestry committer who originated the friendly URLs patch for Tapestry 3.0. This site has also been repackaged and reskinned for a large list of clients.

And a few more I have no real information about (but I liked their logos):

And of course, there are far more than that. Geoff pointed out that Google can search for Tapestry sites due to the distinctive format of Tapestry URLs (if you aren't using the friendly URLs patch).