Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Thursday, August 19, 2010

Groovin' on the Testin'

I'm at the point now where I'm writing Groovy code for (virtually) all my unit and integration tests. Tapestry's testing code is pretty densely written ... care of all those explicit types and all the boilerplate EasyMock code.

With Groovy, that code condenses down nicely, and the end result is more readable. For example, here's an integration test:

    void basic_links() {
        clickThru "ActivationRequestParameter Annotation Demo"
        assertText "click-count", ""
        assertText "click-count-set", "false"
        assertText "message", ""
        clickAndWait "link=increment count"
        assertText "click-count", "1"
        assertText "click-count-set", "true"
        clickAndWait "link=set message"
        assertText "click-count", "1"
        assertText "click-count-set", "true"
        assertText "message", "Link clicked!"        

That's pretty code; the various assert methods are simple enough that we can strip away the unecessary parenthesis.

What really hits strong is making use of Closures though. A lot of the unit and integration tests have a big setup phase where, often, several mock objects are being created and trained, followed by some method invocations on the subject, followed by some assertions.

With Groovy, I can easily encapsulate that as templates methods, with a closure that gets executed to supply the meat of the test:

class JavaScriptSupportAutofocusTests extends InternalBaseTestCase
    private autofocus_template(expectedFieldId, cls) {
        def linker = mockDocumentLinker()
        def stackSource = newMock(JavaScriptStackSource.class)
        def stackPathConstructor = newMock(JavaScriptStackPathConstructor.class)
        def coreStack = newMock(JavaScriptStack.class)
        // Adding the autofocus will drag in the core stack
        expect(stackSource.getStack("core")).andReturn coreStack
        JSONObject expected = new JSONObject("{\"activate\":[\"$expectedFieldId\"]}")
        linker.setInitialization(InitializationPriority.NORMAL, expected)
        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor)
        cls jss
    void simple_autofocus() {
        autofocus_template "fred", { 
            it.autofocus FieldFocusPriority.OPTIONAL, "fred"
    void first_focus_field_at_priority_wins() {
        autofocus_template "fred", {
            it.autofocus FieldFocusPriority.OPTIONAL, "fred"
            it.autofocus FieldFocusPriority.OPTIONAL, "barney"
    void higher_priority_wins_focus() {
        autofocus_template "barney", {
            it.autofocus FieldFocusPriority.OPTIONAL, "fred"
            it.autofocus FieldFocusPriority.REQUIRED, "barney"

That starts being neat; with closures as a universal adapter interface, it's really easy to write readable test code, where you can see what's actually being tested.

I've been following some of the JDK 7 closure work and it may make me more interested in coding Java again. Having a syntax nearly as concise as Groovy (but still typesafe) is intriguing. Further, they have an eye towards efficiency as well ... in many cases, the closure is turned into a synthetic method of the containing class rather than an entire standalone class (the way inner classes are handled). This is good news for JDK 7 ... and I can't wait to see it tame the class explosion in languages like Clojure and Scala.

Tuesday, August 17, 2010

Tapestry Frequently Asked Questions

I'm taking some time to work on the Tapestry documentation ... starting with the FAQ. It's great fun, though this could get to be quite large. I'm just spewing out content right now, over time we'll clean it up, reorganize it, and add further hyperlinks and annotations.

In fact, as I'm working on the FAQ, I'm thinking this might be the best way to document open source projects in general. User's guides and reference documents are rarely read, everyone just Google's their question, so put those questions in their most findable format. Also, it's hard to write a consistent user guide start to finish ... but more reasonable to document one tidbit at a time.

Also, I'm reminded of The Little Schemer, a book that teaches the entire Scheme language (a Lisp variant) via a series of questions of ever broadening scope.

Feel free to suggest additional FAQ topics on the Tapestry Users mailing list.

Monday, August 09, 2010

Tapestry 5.2 leaves the gate

It's been a long time coming. Originally, I had thought we'd be producing Tapestry 5.2 six to eight months after Tapestry 5.1 ... instead, it's been more like 14 months just to get to the alpha release. Why? Well, in that time, I've personally changed jobs (back to an independent consultant), and I've been actively using the nightly snapshots of Tapestry 5.2 in two different projects for two different clients. I've had a lot of chances to see Tapestry in practice and, as always, identify the rough edges and smooth them out.

This new release enhances one of Tapestry secret strengths: meta-programming. It is now ridiculously easy to extend the behavior of components, or method or fields within components, using annotations .... without getting mixed up in all that Javassist business. I'm using that now just about everywhere you might think about using a base class: everything from securing page access, to caching, to integration with Google Analytics.

The big change here is the switch from pooled pages to singletons: In Tapestry 5.1 and earlier, Tapestry kept a pool for page instances. On each request, a localized page instance was pulled from the pool, used exclusively by the one request thread, then returned to the pool. The pool had to be able to expand dynamically, and shrink to release memory.

Starting with Tapestry 5.2, the page pool is deprecated (and only enabled with extra configuration). Instead, a single page instance is created and shared between threads. That may raise your red alert flag ... doesn't that make Tapestry non-thread-safe?

Nope. Tapestry now reworks your simple POJO classes, changing access to all local mutable fields to instead store the value in a per-thread Map. It's an extrapolation of how Tapestry already managed persistent fields (storing the persistent field values in the Session between requests) ... but it now applies to all request-scoped state.

It's an interesting trade off: a lot less memory (just a single instance of each page and all its components) for a bit more work during each request. Part of the reason for this alpha release is to get this code into more hands and get more performance analysis on the result. I'm confident that these changes will not noticeably affect small applications and reasonable request loads but will make a big difference in handling larger applications with heavy request loads.

Meanwhile, the goal is to keep the APIs stable, address a bunch of bugs, and get another release out soon, then vote that up as a beta release. Preferably before JavaOne!