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!

Tuesday, May 16, 2006

Tapestry: Duke's Choice!

In something that was a bit of a surprise, even to me, Tapestry won a Duke's Choice award for innovation at this years JavaOne.

They gave me a big, wacky Duke statuette (photos to follow). I mean, I'm going to have to hire a pack mule to get it back to Portland. I also had a chance to chat with James Gosling about Java. I complimented him on the language and told him how much fun I'm having with it, which he seemed to like. On the other hand ... when I mentioned wanting "all the goodness of Java, but a little less typing", ala Ruby, he went a little cold. I didn't realize until this moment that he might have thought I was referring to object typing (an issue for another day) when I was, in fact, talking about keystrokes!

Meanwhile, I had to drop off the statuette in the room, so I've probably missed the Java bloggers meetup at the Thirsty Bear. We'll see if I can catch the end of that.

Been having a lot of fun so far ... maybe tomorrow, I'll even make it to a session!

Monday, May 08, 2006

NFJS Meltdown

I had a "perfect storm" of technical problems at yesterday's No Fluff Just Stuff which, sadly, scared away part of the audience. I had just re-installed Eclipse on my laptop, reorganizing my plugins into multiple extension folders (so make my eventual upgrade to Eclipse 3.2 easier) and I inadventently installed a new version of the Eclipse Jetty Launcher plugin, version 1.4, that is not compatible with Eclipse 3.1, just with 3.2. Ouch!

So, a lot of fumbling there that got in the way, took up time, and put me off my game. But people were still very interested and involved, with good questions. I have to think about the JavaScript stuff though ... it may just be too much for people who haven't seen Tapestry before.

See folks at JavaOne!

Tuesday, May 02, 2006

Synergy

So I find myself with a crowded desk: one laptop, a mouse for the laptop, a keyboard, mouse, and two LCDs for my desktop. It's getting crowded, and it gets annoying to switch between different keyboards.

Worse, I do all my Thunderbird email just off the laptop (though I use GMail on either or both). That's a problem when I need to copy text from the desktop to the laptop so I can mail it.

Solution: Synergy -- an open source tool that links my computers together.

Synergy allows me to use my desktop keyboard and mouse on both systems. The mouse travels off the left side of my desktop screen onto the right side of my laptop screen. Focus follows mouse, at least in terms of system-to-system, so I can type on one system (say, the desktop), move the cursor over to the laptop, and type into the active window there (without clicking). Synergy even updates window decorations.

Better yet ... Synergy supports cut-and-paste of text between the two systems. So I can copy text out of my browser on the desktop and paste it into compose window on the laptop. I had thought about using a KVM switch at one time, and this kicks its ass! In fact, had I discovered this a few weeks ago, I might not have bought a mouse for the laptop.

Not an issue for me, but it's also cross-platform. It supports Windows, Linux and (with some problems) Mac OS X.

Monday, May 01, 2006

Tapestry 5 Class Reloading

One of the big advantages of the scripting approaches to Web development (including JavaServer Pages at one extreme, and Ruby on Rails at the other) is the velocity of change: you can change a source file (a JSP, a Rails template, or even a Rails class) and see the change immediately.

Tapestry has traditionally been more aggressive at caching. Once a template or a XML file has been loaded into memory, it's stuck there. During development, you can use the -Dorg.apache.tapestry.disable-caching=true trick to turn caching off, but this not only slows down every request, but includes a memory leak that will eventually tank the application. It also does not reload classes the way it reloads templates and XML files.

In my quest to create a Java web development environment as compelling as Rails, one of the first things to tackle is this aspect of development: effective and efficient reloading of component classes.

The first pass at that code is now in the repository. Component classes are loaded in their own class loader (this is also necessary for the Tapestry AOP code that transforms classes as they are loaded into memory). When any transformed classes are changed externally, the class loader, as well as all instances derived from those classes, are discarded and a new class loader is created.

This will work quite well, far better than Tapestry 4's development mode. In production, you'll just throttle down how often Tapestry checks to see if underlying files have changed.

What's interesting is that I've been working, off and on, on this code base since mid-February. This compares to how long I spent on the initial prototype of Tapestry way back in January 2000, where I spent a couple of weeks of furious coding just to get to a simple "Hello World". In those couple of weeks, I "invested" Tapestry with many of the features that would grow with it, and support that growth, over the intervening years: the component hiearchy, the original and primitive form of templates, component parameter bindings, page loading, external XML page and component specifications, and other things long forgotten. To me, it was like winding up a spring ... all that upfront effort paid off with a basic design that was "right enough" to keep Tapestry innovative for many years.

The reason I'm starting with a new code base for Tapestry 5 is that it's time to wind up that spring again. The couple of design errors, only apparent after the fact, in Tapestry 4 are all but insurmountable without a fresh start (this primarily includes the requirement to extend base classes, but there are many more subtle things that need addressing). Further, the fact that I've spent a chunk of time getting the basic class loading and AOP support in place, the fact that anything web application is still weeks or months off, is actually a Good Thing. The spring is going to be wound nice and tight, full of energy for years to come.

Wednesday, April 26, 2006

Gambling on AspectJ

Squeezed in with everything else that's going on (the end of one client project and a lot of personal and business travel), I've been working on the start of the code base that will be Tapestry 5.

I think it is going to be a monster. In a good way. More on that later.

Tapestry 5 is targetted at JDK 1.5 and above. That means annotations, and not XML, will be the rule of the day. JDK 1.5 also means the use of Doug Lea's concurrency support, now java.util.concurrent.

Further, this is a chance for me to dip my toes in the Aspect Oriented pool.

I can picture the reaction: "Howard, that's the wrong 'A'! AOP is so ... 2004? Shouldn't you be focused on Ajax?"

However, the AOP support actually aligns nicely with much that I've done with HiveMind, using Javassist. It's been interesting to see the kind of things you can do statically, at build time, using AspectJ and how they compare to what you can do dynamically, at runtime, using HiveMind. Each approach has merit in different circumstances, and I'm trying to find out exactly where the lines of demarkation are.

For example, I've been doing a lot of defensive programming. So I've been creating aspects to help with that. It's been relatively easy to write aspects that inserts null check code into each non-private method. I like the don't accept null, don't return null coding approach, and my aspect allows me to ensure that all my code works that way. Further, for the rare cases where I want to allow null, I can place a @SuppressNullCheck annotation on a class or individual method.

I could do the same thing using HiveMind ... but I'd really be restricted to applying it to HiveMind services, since it would take the form of an interceptor. Tapestry tends to have lots of data and modeling objects in memory, that would benefit from these checks as much, or more so, than the service code. AspectJ is a better fit there.

I'm also doing some interesting work to use annotations to control concurrent read/write access to my classes.

I'm finding the mix of aspects and annotations to be very powerful. I use type annotations to narrow the set of classes to be affected by the aspect, and use method annotations to include or exclude individual methods. In my case, I have a @Synchronized annotation that indicates a class can have some of its method synchronized, and I have @Synchronized.Read and @Synchronized.Write to control which methods require a read lock, and which require the exclusive write lock.

I'm pretty happy from the point of view of getting more functionality (and, more robust functionality) from less code. Aspects are another approach to doing things that I normally associate with components and layering. The gamble part is how I'll feel once I'm trying debug into and through AspectJ-generated code. It becomes a bit tricky to figure out exactly what's going on once you've let AspectJ work things over.

I've also found that AspectJ really wants to work at the method level, when some of the things I want to do (such as validating individual parameters) are done at the individual parameter level. For example, I'd like to create method advice as the combination of a particular method and a particular non-primitive parameter of that method ... but the best I can do is advise the entire method and recieve all the parameters as an object array (whether they are primitive or not).

I have found the AJDT plugins for Eclipse to be a slight bit creeky. You definately want to upgrade your Eclipse to 3.1.2 (the latest and greatest). The visualization is great (when it works), as is the visual annotations of advised methods. There are numerous annoyances when editting aspects and browsing source, and I often have to perform clean builds of my code, something I hardly ever do when not using AJDT.

Thursday, April 20, 2006

Update on Tapestry @ OSCON

Earlier I had blogged about my frustration at not getting to speak at OSCON 2006. Well, word of my little outburst on this blog has trickled out to the key folks at O'Reilly and now both of my sessions are back in. They apologized for the lack of depth in the Java track, citing technical and procedural problems with that aspect of the conference (made that much worse by Daniel Steinberg's personal tragedy this year). They are working to make amends by inviting several prominent Java speakers to fill the gaps.

I'll have two sessions this year:

Wed 10:45 8588 - Metaprogramming Java with HiveMind and Javassist
Wed 2:35 8904 - Building Java Web Applications with Tapestry

This is great news. OSCON is a fun convention and it's about two miles from where I live. Maybe I'll get to ride the Segway again!

Wednesday, April 12, 2006

Java isn't Open-Sourcey Enough for O'Reilly

Let's just break the cardinal rule: don't blog when you are upset.

Both my sessions for this year's OSCON were rejected. Not just an improved, updated and Ajax-ey Building Web Applications with Tapestry, but a very differently scoped Metaprogramming Java with HiveMind and Javassist.

For O'Reilly ... if it doesn't say Ruby, it's not open source enough! This is doubly dissapointing, because last year's session went well (and turned out to be lucrative), and OSCON is held in Portland, OR ... just 10 minutes away by MAX.

Wednesday, April 05, 2006

Tapestry @ JavaOne Sessions

There are three sessions at JavaOne this year that will be of interest to the Tapestry community. All three will be held in Esplanade 307-310 (which was, I believe, where last year's Web Frameworks Smackdown took place).

Wed 17th 4pm RAD Frameworks Web Tier
Wed 17th 7:30pm The Component Edge: Creating and Using Components With Tapestry (BOF)
Thu 18th 10:30pm Trails BOF

I'm only involved in the Tapestry BOF; Chris Nelson (creator of Trails) will be presenting at the other two sessions.

I'll be arriving in San Francisco on the 14th and departing on the 19th. See everyone then!

Tuesday, April 04, 2006

Dissing Subversion is a bad call

Elliotte Rusty Harold:

The server side of Subversion needs a serious rethink, though. Currently you almost have to have a dedicated Subversion server and a fulltime Subversion administrator. That end needs to get about one thousand times simpler.

Howard Lewis Ship: Huh? I've had exactly one problem with Subversion in over a year of heavy use, that was solved entirely by upgrading to the latest recommended version of the server. You start it up, do a minimal configuration (a passwd file), and let it run. I also use Subclipse with repositories on javaforge.com and on apache.org. I've almost never used the svn command line, either.

Kind of dissapointing that ERH would bash a really great product like Subversion without even bothering to justify his erroneous conclusions. But he's bashed other cool software before.

Monday, April 03, 2006

tapestry-testng

This is another small library at Tapestry @ JavaForge. Unlike many of the others, this library is used during testing, not during deployment.

If you are used to using EasyMock 1.1 in combination with the HiveMindTestCase base class, then much of this library will be familiar ... except that it uses EasyMock 2.0, TestNG, and a lot of JDK 1.5 generics and annotations to keep the code short and simple.

As with the rest of Tapestry @ JavaForge, this stuff is still very alpha ... but also very useful.

Saturday, April 01, 2006

New Tapestry bug fix releases out!

Two new bug fix releases of Tapestry are now available: Tapestry 3.0.4 and Tapestry 4.0.1. Not only did tons of work happen, including documentation fixes and other great stuff but ... I didn't do any of it! I pretty much haven't updated my own workspace since Tapestry 4.0 was released ... I've been working on new demo apps, new side projects at Tapestry @ JavaForge, some prototypes of Tapestry 5, moving Tapestry to a TLP, as well as client project work and my No Fluff Just Stuff presentations. But I haven't been working on the Tapestry code base itself.

The system works! If there was any doubt that Tapestry is more, much more, than Howard's pet project, the energy and vitality that Jesse Kuhnert, Brian K. Wallace, and the many contributors from the community have demonstrated should put an end to that doubt.

I can now, officially, walk in front of a bus. Tapestry will continue.

Wednesday, March 29, 2006

Cognition: Tapestry 4 + Hibernate + Spring + Eclipse

Cognition is a new open source product similar in spirit to Trails. Both are approaches using Tapestry and Hibernate to streamline the process of creating a "typical" web application. Trails is very domain driven, creating the database schema from the object model. Cognition includes graphical editors inside Eclipse to build the schema, and derives the object model from the schema. There's a really impressive viewlet showing how to create a simple app really, really fast.

I can't wait for some time to open up in my schedule so I can give Rails, Trails and Cognition a spin.

Tuesday, March 28, 2006

HTML A tag vs. POST

Just an observation. If it is so damned important that GET operations be non-state changing and idempotent (blah blah caching, design of HTTP, firewalls, etc.) ... if it is so damned important that you never change anything expect using POST (and then send a redirect afterwards) ... tell me why the <a> tag does not support a METHOD attribute, the way <form> does. I mean, think back a few years, at all the crazy tags Netscape and Microsoft put into their browsers, and they miss this? Still?

I mean, this hasn't changed in years, and has consistently held web development hostage to the use of JavaScript to keep any non-trivial page from featuring a big old submit button. Apparently, the Powers That Be are afraid that without a big old submit button, we won't realize that hitting the "delete" key is going to delete something.

Sure JavaScript is all pervasive now, and you can force a form to submit using JavaScript ... and the use of Ajax will eventually mean that any non-trivial operation will be coded using XmlHttpRequest (which will do a post). Someday. For users with JavaScript enabled. But I just want to be able to write:

<a href=". . ." method="post">delete this report</a>

Monday, March 27, 2006

Tapestry Support Network Live Again

Woops. I accidentally let the domain registration for the Tapestry Support Network site lapse while I was on the road. All fixed now! Time to put some new content up there.

Sunday, March 26, 2006

New version of tapestry-spring

I've built out a new and improved version of tapestry-spring yesterday and today. This is a tiny module that properly initializes Tapestry to allow Spring beans to be injected into Tapestry pages.

This one works first off! I now run an integration test to prove this; it starts up a Jetty instance and injects a Spring bean into a Tapestry page.

More importantly, this version addresses the prototype issue. Using the standard @InjectObject annotation messes up when the bean being injected is not a singleton. Non-singleton Spring beans, aka prototypes, must be re-acquired from the BeanFactory in order to get the correct, new version.

There's now an @InjectSpring annotation that does that; reading the property will, beneath the covers, always go back to the BeanFactory to obtain a fresh copy. No caching at any layer, no problems.

I just realized that I forgot to update the documentation; that will come shortly.

I also bumped the version number up to 0.1.2 to celebrate the fact that this code works. But it is still alpha quality!

I did some trickery (I believe) so that the non-annotation classes are compiled for JDK 1.3, and the annotation classes are compiled for JDK 1.5. Took a bit of a struggle with Maven, but it works. If you aren't using annotations, you need to use a bit of XML:

<inject property="myProperty" type="spring" object="mySpringBean"/>

Static imports and Java language evolution

It's interesting to see the differences between how you expect to use a new language feature and how you end up really using it.

For example, of all the new features in 1.5, the idea of static imports seemed to be the least useful. From the outside, it looks like you get to say "min()" rather than "Math.min()". Big woop.

But what I've found when coding using static imports is a new clarity to my code. Once you strip away the class names from static methods the part that's left starts looking a lot like new and extensible language keywords.

For example, here's a test case from some code I'm working on.

    @Test
    public void test() throws Exception
    {
        Method m = ComponentFixture.class.getMethod("getSpringBean");

        EnhancementOperation op = newMock(EnhancementOperation.class);
        IComponentSpecification spec = newMock(IComponentSpecification.class);
        Location l = newMock(Location.class);
        Capturer c = newCapturer(InjectSpecification.class);

        spec.addInjectSpecification(capture(c));

        replay();

        new InjectSpringAnnotationWorker().performEnhancement(op, spec, m, l);

        verify();

        InjectSpecification is = c.getCaptured();

        assertEquals(is.getObject(), "someBeanName");
        assertEquals(is.getProperty(), "springBean");
        assertEquals(is.getType(), "spring");
        assertSame(is.getLocation(), l);
    }

What I've found as I've been coding is that I make extensive use of static imports. First of all, Eclipse makes it easy to do (Ctrl-Shift-M on top of a static method converts it to a static import). Secondly, my personal coding style has been to use a goodly number of static methods (if I can define a new feature in terms of the existing public API of an object, and I don't expect there to be a need for subclasses to override, then I create a static method that takes the object as a parameter).

Those assert calls at the end are actually static methods on an Assert class. Some of the other methods, newCapture() and capture(), are also static. The newMock() calls are inherited from a base class. It actually looks better inside Eclipse, because the static methods are italicized.

My point is ... the code is looking more expressive to me, as some clutter falls away. Autoboxing reduces some more clutter. So does the new for loop, and variable argument lists. Generics are still something of a wash (you get more self-describing code by adding lots of clutter, but you gain by removing explicit casts). The end result follows my motto, though: Less is More.

Has this transformed the language? No, and perhaps that's the point. It's some incremental change, but it's makes for some small steps in the right direction. It's not quite the fluid flow of static class methods in Ruby (used for all kinds of meta-programming), but it's still nice.

On the other hand, Sun is much less aggresive in adopting changes to Java than, say, Microsoft. I hate to say it, but innovation (where innovation is defined as adopting features that other languages have had for a decade or so) seems to be occuring in C# before Java. In some cases, it is occuring in other languages that execute on the Java VM. The end result is the same sad story of Sun chasing Microsoft's tail.

I'd like to see some real improvements to the language. I want Ruby blocks and continuations, C# autotype variables, built-in Aspect support and, hey, maybe something I don't know about that will make my programming life better (maybe a pervasive hot class reloader). In this respect, Java always feels like a perpetual bridesmaid, never a bride.

Saturday, March 25, 2006

NFJS Examples available

I've now been able to build and deploy the examples from my talks at No Fluff Just Stuff. The version number is still 0.0.1 and probably will be for some time. You can obtain a pre-compiled WAR, or a source distribution (with out without the PowerPoint slides) from http://howardlewisship.com/repository/com/howardlewisship/nfjs-tapestry/0.0.1/.

Huzzah! Subversion woes fixed.

I was just starting to panic. I was unable to check out my NFJS example code onto my desktop (it's stored using SVN, using my laptop as the SVN server). I've had some problems with Subversion recently, but thought I had resolved them.

From Eclipse, and from the command line, I was getting a wierd error:

bash-3.00$ svn update
svn: Can't read from connection: Software caused connection abort

From what I can tell, this occured when pulling down some large PowerPoint presentation files. Of course, there's no way to determine exactly what file the failure occured on, or why.

No output in the Subversion.log, or the svnserve command line, or any useful output from svn command line (or the Subclipse tool). Feedback anyone?

I tried updating to the latest Subclipse, 0.9.108. No help.

Finally, I tried upgrading my SVN server to 1.3.0 and ... things are working again!. Supposedly, I could have stayed with a Berkley DB repository using 1.3.0, but I've gone through the work of converting to a file system repository (as one of my attempts to resolve this problem), and I can't see the advantage of moving back. In fact, if anything, SVN seems faster or more responsive using the file system.

I should finally be able to get those NFJS examples up on the web site.

Friday, March 24, 2006

Back from the road! / tapestry-spring plans

Huzzah! Watch out Portland ... I'm back.

I actually had an exit row and room to work on the flight back from Phoenix. I decided to take another look at tapestry-spring.

First off, please remember that it is alpha code! So the fact that it completely doesn't work shouldn't be a total surprise. I had packaged the critical hivemodule.xml file under WEB-INF, not META-INF. Chalk it up to force of habit ... that's what you do for Tapestry applications, but this is a standalone library.

The reason I didn't catch that in my integration tests was ... I'm a lazy, lazy man. I need to learn how to write proper integration tests for tapestry-spring using Maven. I've actually had some good luck starting up Jetty inside a (TestNG) test case and that's the likely course I'll pursue.

So, that's half the story. The other half is dealing with Spring's prototype beans. This was someting that completely surprised me about Spring, once people started to complain. If you define a bean as having the prototype lifecycle, it means that you get a new instance everytime you ask the BeanFactory for it.

I really dislike that approach, because it puts the client code in charge of managing the lifecycle of the bean.

By contrast, HiveMind covers similar functionality quite differently. When you get a service from the Registry (the equivalent of getting a bean from the BeanFactory), you are going to get a proxy. Same service interface, different implementation (brewed up on the spot). The lifecycle of the service is hidden inside this proxy. You can share this proxy around, pass it between threads, even serialize and deserialize it (I think ... there's a few issues around that) and it just works.

The rough equivalent of Spring's non-singleton/prototype beans are HiveMind services with the threaded lifecycle. When you invoke a method on the proxy, it finds or creates a per-thread instance of the service. The per-thread instance is cleaned up and discarded at the end of the request (it's effectively built around a servlet request/response cycle).

This is used all over the place inside Tapestry. What's nice is that a piece of code can treat a proxy to a normal service, and a proxy to a per-thread service identically. That concern (in the AOP sense) is buried inside the proxy. Makes testing easier too.

Now, back to Tapestry's @InjectObject annotation. It's just broken for Spring prototype beans, because it's built around HiveMind's design, not Spring's. So it gets the object out of HiveMind just once and holds onto it, passing it into pages and components via a constructor (this is part of Tapestry's enhanced subclass approach to AOP).

For prototype beans, that means that we get the bean just once per component, and are stuck with it for any instances of that component we instantiate. The same bean instance, once aquired from Spring, will be passed into each new instance of a given page or component.

So ... I'm expanding tapestry-spring to add a specialized annotation, just for Spring. @InjectSpring will create a synthetic getter method that always gets a fresh instance of the named bean from the BeanFactory. That should solve the problem in its entirety.

I just need to stuggle with Maven a little bit more; I want to create a single JAR, where part of the code is compiled with JDK 1.3, but the annotation part is compiled with JDK 1.5. I think I can do this, by having multiple source roots (so src/main and src/annotations) and some extra compile executions.

Ultimately, this code may move under the Tapestry top-level project at Apache, once that gets set up. In the meantime, living at JavaForge is perfectly ok.

But, in the meantime, I need to catch up on the new Doctor Who, waiting for me on my Tivo. I've been out of town that long.

Sunday, March 19, 2006

NFJS code and slides ... just a little longer

It turns out I won't be able to post the updated slides and code from my No Fluff Just Stuff sessions because I left a critical password behind on my desktop. I'm on the road still (a long trip!) and will see about getting this stuff in place this coming weekend. Sorry for any inconvienience.