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!

Saturday, November 07, 2009

Progress on Cascade

Meanwhile, in spare minutes (and during sessions at ApacheCon), I've been continuing to work on Cascade. It's been a great learning exercise for me, pushing my understanding of both Clojure and functional programming in general ... and especially, some pretty advanced meta-programming with macros.

I'm also using Cascade as a kind of test bed for ideas that will eventually appear in Tapestry.

Not everything turns out exactly as I've planned. For example, I've been very excited about invariants, portions of the rendered DOM that could be cached from one request to another, to speed up the rendering. Like Tapestry, Cascade views render a DOM structure which can be manipulated (in an intermediate stage) before being streamed as text. This is a useful and powerful concept in a number of ways.

My thinking has been that a typical view will contain sections of the template that are invariant: unchanging, and that there would be a benefit to building that sub-section of the DOM once and reusing it efficiently in later renderings of the view.

Clojure template forms are processed by macros to become normal Clojure code. Thus something like (template :p [ "Hello" ]) will be transformed into code, approximately (element-node :p nil (combine (text-node "Hello"))). My approach was to tag the new Clojure code forms (the list consisting of element-node, :p, etc.) with meta data to identify it as invariant. Eventually this would propagate up to a higher level and code to manage a cache would be wrapped around it: (or (read-view-cache 97) (update-view-cache 97 (element-node :p ...

Fun stuff ... until I put it into practice (after a fair amount of debugging) and discovered that in the views I've created so far (for testing purposes), the number of nodes that can be cached is low; any use of a symbol or a function call mixed into the template "taints" it as variant. I wasn't set up to do performance measurements, but my gut feeling is that the overhead of managing the caches would overshadow the savings from the small islands of reused DOM nodes.

Back to Cascade as a learning experience: just because this didn't work out doesn't mean I didn't learn a lot from heading down that direction, and certainly the amount of code it took was quite small. I have it stored in a branch in case I ever want to give it another shot.

I will have all the basic features of Cascade implemented pretty soon; I'm looking forward to seeing what the larger Clojure community makes of it. In the meantime, it has served as a great way for me to dig deep into Clojure ... I'll be putting together more sessions for NoFluffJustStuff and more articles for the companion magazine based on all this.

Rethinking Tapestry's approach to JavaScript

I've been doing a lot of work for a client using the ExtJS library and that, combined with many other things I've been looking at, has started to shift my attitude to the correct approach to client-side JavaScript. Not a sea change, just a minor adjustment.

Until now, I've had the approach that the page should popup "complete" and that the JavaScript should only manipulate (or add to) the content already present. In most cases, such as adding validations to user input fields, that works fine: you write out the HTML for the field, remember the id for the field, then add JavaScript that adds event handlers to the field, finding it by its id.

However, for more complex cases, such as Tapestry's Palette component, I've been coding like its 2001 for too long.

The Palette component renders out two <select> elements, plus a number of <divs>, a few buttons, and a chunk of JavaScript to connect it all together. This means generating a lot of related ids on the server (to match against the generated HTML markup) and passing those down to the client.

It's effective but it reflects my relative naivete with JavaScript back in 2001. It's now so much easier to create a DOM structure on the client, using any of the major JavaScript libraries. That, in turn, makes it much more reasonable to just write out an empty <div> element with an id and install into that empty space all the markup for the component, generated on the client side. In fact, I envision a version of the Palette for Tapestry 5.2 that starts as a <select> configured for multiple selection and is converted on the fly to the familiar Palette component entirely on the client side ... which means that it could actually operate in a functional, if limited way, even if JavaScript is disabled in the client.

ExtJS includes a number of other ideas that are worth thinking about; I've been very impressed by the concept of a separate controller hierarchy on the client side (ExtJS calls these "components" as differentiated from the DOM nodes they create and manage). That's something that's ad-hoc and inconsistent in Tapestry's client-side library. I think embracing a similar, more structured approach could make it easier for Tapestry to embrace even more dynamic Ajax behavior.

Thursday, November 05, 2009

Tapestry 5: Java Power, Scripting Ease (ApacheCon 2009)

I've uploaded my presentation from ApacheCon 2009. Of coure, the fun parts are the embedded screen casts, and you need to see me live to get that part!

Saturday, October 31, 2009

Maven: Throwing out the bath water, keeping the baby

... in other words, a first step towards using Maven for dependency management but NOT builds. That's the irony of Maven ... they've conflated two things (dependency management and builds) in such as way that they make the useful one (dependency management) painful because the build system is so awful.

As an interrum step between full Maven and (most likely) Gradle, I've been looking at a way to use Maven for dependencies only in a way that is compatible with Eclipse ... without using the often flakey and undependable M2Eclipse plugin.

In any case, rather than assuming that dependencies might change at any point in time at all, let's assume that when I change dependencies (by manually editing pom.xml) I know it and am willing to run a command to bring Eclipse (and my Ant-based build) in line.

First, my pom.xml:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>myapp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-hibernate</artifactId>
      <version>${tapestry-version}</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>log4j</artifactId>
          <groupId>log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>slf4j-log4j12</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-test</artifactId>
      <version>5.2.0-SNAPSHOT</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>2.4.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search</artifactId>
      <version>3.1.1.GA</version>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.8.0</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>0.9.17</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.8</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>8.4-701.jdbc4</version>
    </dependency>
    <dependency>
      <groupId>xerces</groupId>
      <artifactId>xercesImpl</artifactId>
      <version>2.4.0</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-datefield</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-prototype</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.9</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>tapestry360-snapshots</id>
      <url>http://tapestry.formos.com/maven-snapshot-repository/</url>
    </repository>
    <repository>
      <id>repository.jboss.org</id>
      <url>http://repository.jboss.org/maven2/</url>
    </repository>
  </repositories>
  <properties>
    <tapestry-version>5.1.0.5</tapestry-version>
    <lucene-version>2.4.1</lucene-version>
    <tapx-version>1.0.0-SNAPSHOT</tapx-version>
  </properties>
</project>

(This was adapated from one of my client's POMs).

Next, an Ant build file that compiles this, runs tests and builds a WAR:

<project name="example" xmlns:mvn="urn:maven-artifact-ant">

  <property name="classes.dir" value="target/classes" />
  <property name="test.classes.dir" value="target/test-classes" />
  <property name="web.lib.dir" value="target/web-libs" />
  <property name="webapp.dir" value="src/main/webapp" />
  <property name="webinf.dir" value="${webapp.dir}/WEB-INF" />

  <path id="compile.path">
    <fileset dir="lib/provided" includes="*.jar"/>
    <fileset dir="lib/runtime" includes="*.jar" />
  </path>

  <path id="test.path">
    <path refid="compile.path" />
    <pathelement path="${classes.dir}" />
    <fileset dir="lib/test" includes="*.jar" />
  </path>

  <target name="clean" description="Delete all derived files.">
    <delete dir="target" quiet="true" />
  </target>

  <!-- Assumes that Maven's Ant library is installed in ${ANT_HOME}/lib/ext. -->

  <target name="-setup-maven">
    <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" />
    <mvn:pom id="pom" file="pom.xml" />
  </target>

  <macrodef name="copy-libs">
    <attribute name="filesetrefid" />
    <attribute name="todir" />
    <sequential>
      <mkdir dir="@{todir}" />
      <copy todir="@{todir}">
        <fileset refid="@{filesetrefid}" />
        <mapper type="flatten" />
      </copy>
    </sequential>
  </macrodef>

  <macrodef name="rebuild-lib">
    <attribute name="base" />
    <attribute name="scope" />
    <attribute name="libs.id" default="@{base}.libs" />
    <attribute name="src.id" default="@{base}.src" />
    <sequential>
      <mvn:dependencies pomrefid="pom" filesetid="@{libs.id}" sourcesFilesetid="@{src.id}" scopes="@{scope}" />
      <copy-libs filesetrefid="@{libs.id}" todir="lib/@{base}" />
      <copy-libs filesetrefid="@{src.id}" todir="lib/@{base}-src" />
    </sequential>
  </macrodef>

  <target name="refresh-libraries" depends="-setup-maven" description="Downloads runtime and test libraries as per POM.">
    <delete dir="lib" quiet="true" />
    <rebuild-lib base="provided" scope="provided"/>
    <rebuild-lib base="runtime" scope="runtime,compile" />
    <rebuild-lib base="test" scope="test" />
    <echo>
      
*** Use the rebuild-classpath command to update the Eclipse .classpath file.</echo>
  </target>

  <target name="compile" description="Compile main source code.">
    <mkdir dir="${classes.dir}" />
    <javac srcdir="src/main/java" destdir="${classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="compile.path" />
    </javac>
  </target>

  <target name="compile-tests" depends="compile" description="Compile test sources.">
    <mkdir dir="${test.classes.dir}" />
    <javac srcdir="src/test/java" destdir="${test.classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="test.path" />
    </javac>
  </target>

  <target name="run-tests" depends="compile-tests" description="Run unit and integration tests.">
    <taskdef resource="testngtasks" classpathref="test.path" />
    <testng haltonfailure="true">
      <classpath>
        <path refid="test.path" />
        <pathelement path="${test.classes.dir}" />
      </classpath>

      <xmlfileset dir="src/test/conf" includes="testng.xml" />

    </testng>
  </target>


  <target name="war" depends="run-tests,-setup-maven" description="Assemble WAR file.">

    <!-- Copy and flatten the libraries ready for packaging. -->

    <mkdir dir="${web.lib.dir}" />
    <copy todir="${web.lib.dir}" flatten="true">
      <fileset dir="lib/runtime" />
    </copy>
    <jar destfile="${web.lib.dir}/${pom.artifactId}-${pom.version}.jar" index="true">
      <fileset dir="src/main/resources" />
      <fileset dir="${classes.dir}" />
    </jar>

    <war destfile="target/${pom.artifactId}-${pom.version}.war">
      <fileset dir="${webapp.dir}" />
      <lib dir="${web.lib.dir}" />
    </war>
  </target>
</project>

The key target here is refresh-libraries, which deletes the lib directory then repopulates it. It creates a sub folder for each scope (lib/provided, lib/runtime, lib/test) and another sub folder for source JARs (lib/provided-src, lib/runtime-src, etc.).

So how does this help Eclipse? Ruby to the rescue:

#!/usr/bin/ruby
# Rebuild the .classpath file based on the contents of lib/runtime, etc.

# Probably easier using XML Generator but don't have the docs handy

def process_scope(f, scope)
 # Now find the actual JARs and add an entry for each one.
 
 dir = "lib/#{scope}"

 return unless File.exists?(dir)
 
 Dir.entries(dir).select { |name| name =~ /\.jar$/ }.sort.each do |name|
   f.write %{  <classpathentry kind="lib" path="#{dir}/#{name}"}
   
   srcname = dir + "-src/" + name.gsub(/\.jar$/, "-sources.jar") 

   if File.exist?(srcname)
      f.write %{ sourcepath="#{srcname}"}
   end
   
   f.write %{/>\n}
 end
end

File.open(".classpath", "w") do |f|
  f.write %{<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/java"/>
  <classpathentry kind="lib" path="src/main/resources"/>
  <classpathentry kind="src" path="src/test/java"/>
  <classpathentry kind="lib" path="src/test/resources"/>
  <classpathentry kind="output" path="target/classes"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
}

 process_scope(f, "provided")
 process_scope(f, "runtime")
 process_scope(f, "test")
 
 f.write %{
</classpath>
}
    
end

That's pretty good for half an hour's work. This used to be much more difficult (in Maven 2.0.9), but the new scopes attribute on the Maven dependencies task makes all the difference.

Using this you are left with a choice: either you don't check in .classpath and the contents of the lib folder, in which case you need to execute the target and script in order to be functional ... or you simply check everything in. I'm using GitHub for my project repository ... the extra space for a few MB of libraries is not an issue and ensures that I can set up the exact classpath needed by the other developers on the project with none of the usual Maven guess-work. I'm looking forward to never having to say "But it works for me?" or "What version of just about anything do you have installed?" or "Try a clean build, and close and reopen your project, and remove and add Maven dependency support, then sacrifice a small goat" every again.

Next up? Packaging most of this into an Ant library so that I can easily reuse it across projects ... or taking the time to learn Gradle and let it handle most of this distracting garbage.

Thursday, October 29, 2009

Sunday, October 04, 2009

Cascade Exception Reporting

I've been taking a little time from my billable projects to continue working on Cascade. One feature that's very important to me is to have great exception reporting, akin to Tapestry's. Here's a current snapshot of where I am:


This is very Tapestry-like (I've even borrowed the CSS styles). You can even see the start of the Request object's properties being displayed.

Something to notice here: Clojure stack frames are in Clojure syntax. To appreciate this, see what you get when you click the "Display hidden detail" button:


The exception report view is omitting a lot of clojure.lang internals, and it is working backwards from the mangled Java class name to the Clojure namespace and function name. This, plus only displaying the stack trace for the root exception, makes it much more reasonable to figure out where problems are actually occurring.

I expect to expand this further, adding a pop-up or hover window to display Clojure source associated with the stack frame.

Saturday, October 03, 2009

Tapestry 5.1 and IE 8 -- Customizing Tapestry

Tapestry is nice enough to bundle the Prototype and Scriptaculous libraries its client-side support is wired against, which is very convenient ... until you find out the the packaged version is not compatible with your shiny new browser, such as Internet Explorer 8.

Tapestry IoC to the rescue: you can override where Tapestry looks for the Prototype & Scriptaculous files (alas, it currently looks in the exact same place for them). Where these files are stored, and how they are exposed to the client is controlled by two contributions inside TapestryModule:

    public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
    {
        . . .

        configuration.add("tapestry.scriptaculous", "classpath:${tapestry.scriptaculous.path}");
        configuration.add("tapestry.scriptaculous.path", "org/apache/tapestry5/scriptaculous_1_8_2");

        . . .
    } 

    public static void contributeClasspathAssetAliasManager(MappedConfiguration<String, String> configuration,

      @Symbol(SymbolConstants.TAPESTRY_VERSION)
      String tapestryVersion,
      @Symbol("tapestry.scriptaculous.path")
      String scriptaculousPath)
    {
       . . .

       configuration.add("scriptaculous/" + tapestryVersion, scriptaculousPath);

       . . .
     }

The first contributions set where, on the classpath, the Prototype & Scriptaculous files are located, defining symbols that can be referenced in various servers. The second uses some of those symbols (and a few others) to map the classpath location to a URL (this is the job of the ClasspathAssetAliasManager service).

However, what's being contributed is Prototype 1.6.0.3 and for compatibility with Internet Explorer 8, we need the latest and greatest: 1.6.1. That's what tapx-prototype does. And at its core it's just a couple of lines of code:

public class PrototypeModule
{
    public void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
    {
        configuration.override("tapestry.scriptaculous.path", "com/howardlewisship/tapx/prototype");
    }

    public static void contributeClasspathAssetAliasManager(
            MappedConfiguration configuration)
    {
        configuration.add("tapx-prototype/1.6.1", "com/howardlewisship/tapx/prototype");
    }
}

Notice that we can just override part of the configuration of one service (FactoryDefaults) and extend the configuration of another service (ClasspathAssetAliasManager) without disturbing anything else. This is Tapestry IoC in a nutshell!

Glenn Vanderburg has been popularizing a terminology for extensible software: a "seam" is any point where existing software can be extended. He's used to the Ruby world where, literally, every method is a seam. Tapestry, too, is all seams ... every service, and (with more effort than Ruby) every method of every service is a seam, and lots of effort has gone into the design of Tapestry IoC to ensure that it can be extended without massive cut-and-paste or other disruptions.

I'll be releasing the tapx code soon as a stable release, once I do a little more testing with IE8.

Friday, September 25, 2009

Off topic: Quizissippi

For those of you in the Portland, OR area ... mark your calendars for Wednesday evenings at the Mississippi Pizza Pub for Quizissippi, hosted by the wonderful Red Molly. It's a trivia contest with cash prizes ... and since it's run by Molly it will be very fun and very competitive and not quite like any trivia contest you've previously experienced.

The first Quizissippi is at 7pm, on October 7th in the Atlantis Lounge. I'm already looking forward to Mark pouring some of his great drinks, and Molly dazzling us all with her cleverness. And maybe some quick cash ... if I'm smart enough!

Joys of cross-browser development

I'm working on a dependent drop-down list inside my Tapestry application (one I'm building for a client). Guess what? They fire the critical "change" even completely differently.

In FF, change is fired when:

  • You hit enter after tabbing into the select and changing its value
  • You tab out of the field after changing its value with the up and down arrows
  • You mouse click on the select, then click on an entry in the pop up list

This is sensible, even if it does not, perhaps, match the spec.

In IE 6:

  • After using the mouse to select a value from the popup
  • After changing the value with the up and down arrows

In other words, any visual change to the select causes an immediate change event. Of course, I need to work around IE's behavior since it will result in way too many Ajax requests. My event handler needs to set a timer and wait some time, perhaps 1/4 seconds, before doing the Ajax update (but only if another change event hasn't been triggered). Fortunately, I have Prototype's delay on my side.

Meanwhile, who implements the spec correctly? Here's what it says:

The onchange event occurs when a control loses the input focus and its value has been modified since gaining focus. This attribute applies to the following elements: INPUT, SELECT, and TEXTAREA

So, when using mouse selection, FF fires early (before the tab out of the field, or when you hit enter), and IE fires early all the time.

And for those who have asked ... yes, I'll "productize" this code and put it into Tapestry 5.2!

Thursday, September 24, 2009

Devoxx 2009: Tapestry and Clojure

devoxx logo Coming up in November ... I'll be at the Devoxx 2009 conference in Antwerp, Belgium. I'll be presenting two 60 minute talks: one as an introduction to Tapestry (Nov 20th, 10:30-11:30, Room 9) and another as an introduction to Clojure (Nov 18th, 12:00-13:00, Room 6). Given that my existing talks take closer to 90 minutes, this is going to take fast talking and a bit of editing!

Devoxx is a very cool conference, run by the user group community and very well organized. It's one of the best places to speak because of the quality of the A/V: it takes place in a movie theater, with giant screens and plush, stadium seating.

This is my first return to Devoxx since 2004, when it was still called Javopolis. With luck, this time, I won't succumb to jet lag and comfy seats and start snoring in someone's session!

Tuesday, September 22, 2009

No Fluff Just Stuff Seattle Roundup

It was a huge pleasure to be speaking at a No Fluff Just Stuff conference again, it's been much too long. This was a good show with a lot of folks coming down from Vancouver, BC to attend.

This was an odd show for me, as we didn't do any Tapestry talks at all. However, all three of my talks (an introduction to Cappuccino, and two talks on Clojure) were well attended, with good questions and some fun discussions between sessions. I'm looking forward to doing these same sessions, with a number of improvements, in the Spring when NFJS starts back up.

Interestingly, I was busy writing Clojure code almost continuously in the back row of other speaker's sessions ... but this didn't stop me from appreciating Scott Davis's great talks on Grails, and on "deconstructing Web 2.0". Also, I had a lot of interaction with Brian Goetz ("are all web application's broken"). As usual, as soon as you mix Java with concurrency, it's a bit sobering. Tapestry helps here, but is not a complete panacea against some of the "nightmare scenarios" that may become more commonplace as the number of CPU cores increases.

I'm getting more adept at advanced Clojure; I've been busy adapting the concept of parameter destructuring to pulling data out of the request path and query parameters.

(defaction increment-count
  {:path "count/increment"}
  [env] 
  [count :int] ; destructure the value after "/increment" as an integer
  (send-redirect env (link env show-counter [(inc count)])))

This defines a Cascade action, mapped to the "count/increment" path. A number is expected to follow (i.e., /count/increment/4) and the job of this action is to increment that value and redirect the user to the show-counter view.

Cascade actions are still functions, thus the normal parameter declaration: [env]. Cascade views and actions always take a single parameter, a map called "the environment" that provides access to Cascade-specific values as well as Servlet API objects.

The path destructuring follows: [count :int] which destructures the first "extra path" value as an integer. There's a number of built-in parsers referenced by keywords such as :int, or you can supply a function that takes a string and returns a value. If another pair of values is given, that applies to the next value in the path.

I'm finding this a pretty elegant way to express this secondary aspect of mapping URLs to view and actions; there's additional syntax for destructuring query parameters instead of path values. I may extend this further, to handle default values for missing terms in the path.

I try to capture some of this in my Clojure talks; the idea of growing the language up, adding the new features you need. From one perspective, this is just a domain specific language, but in the Lisp world, that concept is so deeply entrenched that there isn't even a word for it beyond "programming". This is one part of the overall Clojure picture that gets me very excited, and I'm continuing to struggle with how to express this to people for whom even the Lisp syntax is alien and off-putting.

Monday, September 21, 2009

Speaking at ApacheCon 2009

Just a reminder ... I'll be spreading the good word about Tapestry at ApacheCon this year. My session is Tapestry 5: Java Power, Scripting Ease! on Wed Nov 04 at 11am.

Wednesday, September 16, 2009

Tapestry Performance Benchmark (vs. Wicket, JSF, etc.)

Peter Thomas has created a detailed performance analysis of Wicket, Tapestry, Grails and Seam. It's an interesting read from non-Tapestry user's perspective, and complements Ben Gidley's findings.

He's measuring raw performance and Wicket narrowly bests Tapestry in most categories, with Seam pretty close and Grails much further out.

I'm disturbed by some of his problems developing the application (with respect to adding client-side credit card number validation) and there's no mention of Tapestry's other qualities, such as live class reloading, exception reporting, etc. Still, criticism of Tapestry's documentation hits close to home (and it, alas, too fair). Accurate and very complete, but not organized for a beginner ... something I'm hoping to address over the next few months.

Like Tapestry? Read German? There's a book for you!

book cover

Igor Drobiazko has just announced his new book, Die Entwicklung von Webanwendungen mit Leichtigkeit! (The development of web applications with ease!).

Alas, it is in German, though he hopes good sales in Germany may lead to a translation for the English speaking market. It's available as an e-book now, and in hardcover at the end of September.

Wednesday, September 09, 2009

Formos in the news

I may no longer work at Formos, but I'm still pleased to see a nice writeup about Formos and StreamBank. StreamBank is an application that helps land owners and The Freshwater Trust work to improve stream health and water quality across Oregon. The article doesn't state this, but StreamBank is written in Tapestry (it started as Tapestry 4 and was ported to Tapestry 5).

Friday, August 28, 2009

Keeping track of Clojure dependencies

I've been coding more of Cascade and I'm tending to do lots of small namespaces. I'm concerned about circular dependencies, so I've been using OmniGraffle to keep track of what uses what:

I end up keeping this open in the background and updating it manually as I add new namespaces or change dependencies. I'm pretty sure it's accurate.

It does raise the question ... am I using the correct level of granularity? I think I am, and the individual files are pretty short:

$ find src/main/clojure -name \*.clj | xargs wc -l
      51 src/main/clojure/cascade/config.clj
     115 src/main/clojure/cascade/dispatcher.clj
      92 src/main/clojure/cascade/dom.clj
     107 src/main/clojure/cascade/filter.clj
      51 src/main/clojure/cascade/internal/parse_functions.clj
      94 src/main/clojure/cascade/internal/parser.clj
     205 src/main/clojure/cascade/internal/utils.clj
     113 src/main/clojure/cascade/internal/viewbuilder.clj
      36 src/main/clojure/cascade/jetty.clj
      39 src/main/clojure/cascade/logging.clj
      29 src/main/clojure/cascade/map_utils.clj
      56 src/main/clojure/cascade/mock.clj
      73 src/main/clojure/cascade/path_map.clj
      41 src/main/clojure/cascade/pipeline.clj
      30 src/main/clojure/cascade/urls.clj
      81 src/main/clojure/cascade.clj
    1213 total

... and that includes the ASL header comment in each file. Short and sweet.

Maintaining this chart seems like something I should automate at some point, however: It should be possible to use the reader to parse a Clojure source file without evaluating it; the first form should be the (ns), from which can be expanded the used and required namespaces (with a bit of effort, because of Clojure's very concise syntax). One more project for the back burner ...

Thursday, August 27, 2009

Return to Independent Consulting

I'd like to announce to the Tapestry community that I've returned to independent consulting. As an independent consultant, I'll have more opportunities to pursue training, mentoring, and project work that did not fit with Formos' overall goals.

Formos continues to be committed to Tapestry, and to maintaining the Tapestry360 web site. I'd like to thank Matt Tunnel, President of Formos, for the opportunities he's provided: a "dream job" that let me focus on completing Tapestry 5.0 and 5.1, with a scope of features far beyond what I had originally envisioned when I started Tapestry 5 over four years ago.

Now is a new chapter; I'm starting to search for my next dream job, while actively seeking out new Tapestry training, mentoring and support projects, as well as working with my existing clients. In addition, I'm using my improved freedom to pursue other important technologies beyond Tapestry, such as Clojure, Cappuccino, and CouchDB. I expect to be able to offer the same kind of compelling training and project work in these technologies as I currently provide for Tapestry.

I'm also taking this time to pursue one of the opportunities I could not take on while at Formos: a Tapestry 5 book. I'm currently contacting a number of different publishers to find the best home for a new book specifically about Tapestry 5.

I'd also like to thank the Tapestry community for all the enthusiasm and dedication that you've given to Tapestry. I'm looking forward to helping you create even more insanely great applications!

Sunday, August 16, 2009

Article: Meta-Programming Java

In the last couple of years, if you mention the term meta-programming, people's ears perk up ... and they start looking around for Ruby. That's fair; Ruby makes a lot of meta-programming concepts very, very easy. However, that doesn't mean you can't do any meta-programming in Java; you just are a bit more limited and need a lot more infrastructure.

Tapestry 5, both the web framework and the underlying Inversion of Control container, is rife with meta-programming options. Let's talk about one of the most versatile: the thunk.

Thunks and Laziness

A thunk is a placeholder for a value to be computed as-needed. The Haskell programming language makes great use of these; thunks are the essense of lazy programming: each thunk represents a set of parameters to a function1 and the function itself.

The upshot of this is that when you see a function call (or other expression) in Haskell code, what really happens is that a thunk of the invocation of that function is created to capture the values to be passed in (some of which may themselves be thunks of other expressions). Its only when the value is needed, when the result of the expression is used in some other expression that is evaluated, that the thunk itself gets evaluated; the function is invoked, the return value is cached in the thunk and returned. This makes the order in which things happen in Haskell very difficult to predict, especially from the outside. Because of thunks, algorithms that look tail recursive aren't (the recursive call is just another thunk, evaulated serially). Further, algorithms that appear to be infinite, aren't: the thunks ensure that just values that are actually needed are ever computed.

It's an elegant and powerful approach, and it's even fast, because the fastest code is the code that is never executed in the first place.

Other languages have this feature; Clojure reflects its Lisp heritage in that almost everything operates in terms of accessing, iterating and transforming collections ... and all of those collection operations are lazy as well. Unlike Haskell, this is more a function of a carefully crafted standard library than a direct offshoot of the language, but the end result is quite similar.

But what happens when you want to accomplish some of these features (such as lazy evaluation) within the tight constraints of standard Java? That's when you need to get creative!

Thunks in Tapestry 5

Tapestry 5 uses thunks in many different places; the most common one is the use of proxies for Tapestry 5 IoC services. In Tapestry 5 every service has an interface2. Let's take a peek at a typical service in Tapestry 5, to illustrate the typed-thunk concept.

Listing 1: ComponentMessagesSource.java

public interface ComponentMessagesSource
{
    Messages getMessages(ComponentModel componentModel, Locale locale);

    InvalidationEventHub getInvalidationEventHub();
}

The purpose of the ComponentMessagesSource service is to provide a Messages object representing a particular component's message catalog. This is part of Tapestry's localization support: every page and component has easy access to its own message bundle, which includes messages inherited from base components and from a global message catalog.

A central tenet of Tapestry 5 is that service instantiation is lazy: services are only constructed as needed. What does "as needed" mean? It means, the first time any method of the service is invoked. This kind of lazy instantiation is accomplished by using thunks. So for a service such as ComponentMessagesSource, there will be a class somewhat like ComponentMessagesSourceThunk to handle the lazy instantiation:

Listing 2: ComponentMessagesSourceThunk.java

public interface ComponentMessagesSourceThunk implements ComponentMessagesSource
{
    private final ObjectCreator creator;

    public ComponentMessagesSourceThunk(ObjectCreator creator) { this.creator = creator; }

    private ComponentMessagesSourceThunk delegate() { return (ComponentMessagesSourceThunk) creator.createObject(); }

    public Messages getMessages(ComponentModel componentModel, Locale locale)
    {
        return delegate().getMessages(componentModel, locale);
    }

    public InvalidationEventHub getInvalidationEventHub()
    {
        return delegate().getInvalidationEventHub();
    }
}

You won't find the above class in the Tapestry source code: it is generated on-the-fly by Tapestry. That's great, because I know I'd hate to have to supply a service interface, a service implementation and a thunk class for each service; the interface and implementation is already plenty! One of the reasons that Tapestry all but requires that services have a service interface is to support the automatic creation of thunks or other proxies around the interface.

However, you can see the pattern: every method of the interface is, of course, implemented in the thunk. That's what it means to implement an interface. Each method obtains the delegate and then re-invokes the same method with the same parameters on the delegate. The trick is that the first time any of these methods are invoked, the delegate does not yet exist. The ObjectCreator will create the delegate object during that first invocation, and keep returning it subsequently. That's the essence of lazy instantiation.

The point here is that for any interface, you can create a typed-thunk that can stand in for the real object, hiding the real object's lifecycle: it gets created on demand by the ObjectCreator. Code that uses the thunk has no way of telling the thunk from the real objects ... the thunk implements all the methods of the interface and performs the right behaviors when those methods get invoked.

Creating Thunks Dynamically

Before we can talk about using thunks, we need to figure out how to create them dynamically, at runtime. Let's start by specifying the interface for a service that can provide thunks on demand, then figure out the implementation of that service.

Listing 3: ThunkCreator.java

public interface ThunkCreator
{
    /**
     * Creates a Thunk of the given proxy type.
     *
     * @param proxyType     type of object to create (must be an interface)
     * @param objectCreator provides an instance of the same type on demand (may be invoked multiple times)
     * @param description   to be returned from the thunk's toString() method
     * @param <T>           type of thunk
     * @return thunk of given type
     */
    <T> T createThunk(Class<T> proxyType, ObjectCreator objectCreator, String description);
}

Remember that this is just an automated way of producing instances of classes similar to ComponentMessagesSourceThunk. A simple implementation of this service is possible using JDK Proxies:

Listing 4: ThunkCreatorImpl.java

public class ThunkCreatorImpl implements ThunkCreator
{
    public <T> T createThunk(Class<T> proxyType, final ObjectCreator objectCreator, final String description)
    {
        InvocationHandler handler = new InvocationHandler()
        {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                if (method.getName().equals("toString") && method.getParameterTypes().length == 0)
                    return description;

                return method.invoke(objectCreator.createObject(), args);
            }
        };

        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                              new Class[] { proxyType },
                                              handler);

        return proxyType.cast(proxy);
    }
}

JDK Proxies were introduced way back in JDK 1.3 and caused a real flurry of activity because they are so incredibly useful. A call to Proxy.newProxyInstance() will create an object conforming to the provided interfaces (here specified as the proxyType parameter). Every method invocation is routed through a single InvocationHandler object. The InvocationHandler simply re-routes method invocations to the object returned from objectCreator.createObject().

Tapestry's implementation of ThunkCreator uses the Javassist bytecode manipulation library to generate a custom class at runtime. The generated class is much closer to the example CompnentMessagesSourceThunk; it doesn't use JDK proxies or reflection. This means that Java's Hotspot compiler can do a better job optimizing the code. In reality, you'll be hard pressed to spot a difference in performance unless you use these thunks inside a very tight loop.

Great so far; now lets think about how we could use this in another way. What if you have a service that returns an object that is expensive to construct and may not even get used? An example of this in Tapestry is the Messages object, obtained from the ComponentMessagesSource service. Building a Messages instance for a component involves a lot of hunting around the classpath looking for properties files, not just for the component but for its base-class and for application-wide message bundles. That means a lot of I/O and and a lot of blocking, waiting for the disk drive to catch up. In many cases, these Messages objects are injected into components, but aren't used immediately. In terms of getting markup into the user's browser faster, avoiding all of those file lookups and file reads until absolutely necessary is an appreciable win.

Our goal is to intercept the call to ComponentMessagesSource.getMessages() and capture the parameters to the method. Instead of invoking the method, we want to return a thunk that encapsulates the method call. This is where we can really start to talk about meta-programming, not just programming: we aren't going to change the ComponentMessagesSource service implementation to accomplish this, we are going to meta-program the service. This is a key point: A Tapestry service is the sum of its interface, its implementation, and all the other parts provided by Tapestry. We can use Tapestry to augment the behavior of a service without changing the implementation of the service itself.

This approach is in stark contrast to, say, Ruby. When meta-programming Ruby you often end up writing and rewriting the methods defined by the class in place. In Java, you will instead layer on new objects implementing the same interface to provide the added behavior.

Accomplishing all this is suprisingly easy ... given the infrastructure that Tapestry 5 IoC already provides.

Lazy Advice

The goal with lazy advice is that invoking a method on a service short-circuits the method invocation: a thunk is returned that is a replacement for the return value of the method. Invoking a method on a thunk will invoke the actual service method, then re-invoke the method on the actual value returned from the method.

Image 1: Lazy Advice Thunk/

This is shown in image 1. The service method is represented by the blue line. The advice intercepts the call (remembering the method parameters) and returns a thunk. Later, the caller invokes a method on the thunk (the green line). The thunk will invoke the service method using the saved parameters (this is the lazy part), then re-invoke the method on the returned value.

To the caller, there is no evidence that the thunk even exists; the service method just returns faster than it should, and the first method invocation on the return value takes a little longer than it should.

Now we know what the solution is going to look like .. but how do we make it actually happen? How do we get "in there" to advise service methods?

Advising Service Methods

Tapestry's Inversion of Control Container is organized around modules: classes that define services. This is in contrast to Spring, which relies on verbose XML files. Tapestry uses a naming convention to figure out what methods of a module class do what. Methods whose name starts with "build" define services (and are ultimately used to instantiate them). Other method name prefixes have different meanings.

Module method names prefixed with "advise" act as a hook for a limited amount of Aspect Oriented Programming. Tapestry allows an easy way to provide around advice on method invocations ... a more intrusive system such as AspectJ can easily intercept access to fields or even the construction of classes and has more facilities for limiting the scope of advice so that it only applies to invocations in specific classes or packages. Of course, it works by significantly rewriting the bytecode of your classes and Tapestry's IoC container aims for a lighter touch.

Being able to advise service methods was originally intended to support logging of method entry and exit, or other cross-cutting converns such as managing transactions or enforcing security access constraints. However, the same mechanism can go much further, controlling when method invocations occur, in much the same way that the lazy thunk described above operates.

Listing 5 shows the method advice for the ComponentMessagesSource service.

Listing 5: TapestryModule.java

    @Match("ComponentMessagesSource")
    public static void adviseLazy(LazyAdvisor advisor, MethodAdviceReceiver receiver)
    {
        advisor.addLazyMethodInvocationAdvice(receiver);
    }

This method is used to advise a specific service, identified by the service's unique id, here "ComponentMessagesSource". An advisor method may advise many different services; we could use glob names or regular expressions to match a wider range of services. An advisor method recieves a MethodAdviceReceiver as a parameter; additional parameters are injected services. The intent of module classes is to contain a minimal amount of code, so it makes sense to move the real work into a service, especially because it is so easy to inject services directly into the advisor method.

The LazyAdvisor service, built into Tapestry, does most of the work:

Listng 6: LazyAdvisorImpl.java

public class LazyAdvisorImpl implements LazyAdvisor
{
    private final ThunkCreator thunkCreator;

    public LazyAdvisorImpl(ThunkCreator thunkCreator)
    {
        this.thunkCreator = thunkCreator;
    }

    public void addLazyMethodInvocationAdvice(MethodAdviceReceiver methodAdviceReceiver)
    {
        for (Method m : methodAdviceReceiver.getInterface().getMethods())
        {
            if (filter(m))
                addAdvice(m, methodAdviceReceiver);
        }
    }

    private void addAdvice(Method method, MethodAdviceReceiver receiver)
    {
        final Class thunkType = method.getReturnType();

        final String description = String.format("<%s Thunk for %s>",
                                                 thunkType.getName(),
                                                 InternalUtils.asString(method));

        MethodAdvice advice = new MethodAdvice()
        {
            /**
             * When the method is invoked, we don't immediately proceed. Intead, we return a thunk instance
             * that defers its behavior to the lazily invoked invocation.
             */
            public void advise(final Invocation invocation)
            {
                ObjectCreator deferred = new ObjectCreator()
                {
                    public Object createObject()
                    {
                        invocation.proceed();

                        return invocation.getResult();
                    }
                };

                ObjectCreator cachingObjectCreator = new CachingObjectCreator(deferred);

                Object thunk = thunkCreator.createThunk(thunkType, cachingObjectCreator, description);

                invocation.overrideResult(thunk);
            }
        };

        receiver.adviseMethod(method, advice);
    }

    private boolean filter(Method method)
    {
        if (method.getAnnotation(NotLazy.class) != null) return false;

        if (!method.getReturnType().isInterface()) return false;

        for (Class extype : method.getExceptionTypes())
        {
            if (!RuntimeException.class.isAssignableFrom(extype)) return false;
        }

        return true;
    }
}

The core of the LazyAdvisor service is in the addAdvice() method. A MethodAdvice inner class is defined; the MethodAdvice interface has only a single method, advise(). The advise() method will be passed an Invocation that represents the method being invoked. The Invocation captures parameters passed in as well as the return value or any checked exceptions that are thrown. Invoking the proceed() method continues on to the original method of the service3.

At this point, the thunk encapsulates the original method invocation; we even have an object for that: the Invocation instance originally passed to the advise() method. Invoking any method on the thunk will cause the ObjectCreator.createObject() method to be triggered: this is where we finally invoke proceed() and return the value for the lazily invoked method.

Other uses for Thunks

In essence, this thunk approach gives you the ability to control the context in which a method is executed: is it executed right now, or only when needed? It is only a little jump from that to executing the method in a background thread. In fact, Tapestry includes a ParellelExecutor service that can be used for just that.

Conclusion

Type-safe thunks are a powerful and flexible technique for controlling when (or even if) a method is invoked without sacrificing type safety. Unlike more intrusive techniques that rely on manipulating the bytecode of existing classes, type-safe thunks can be easily and safely introduced into existing code bases. More than that, this exercise opens up many exciting possibilities: these techniques (coding to interfaces, multiple objects with the same interface, delegation) open up a path to a more fluid, more responsive, more elegant approach to coding complex behaviors and interactions ... while reducing the total line count and complexity of your code.

One of the things I am most happy about in Tapestry is the way in which we can build up complex behaviors from simple pieces. Everything stacks together, concisely and with minimum fuss:

  • We can create a thunk around an ObjectCreator, to defer the instantiation of an object
  • We can capture a method invocation and convert that into an ObjectCreator and a lazy thunk
  • We can advise a method without changing the actual implementation, to provide the desired laziness
  • Tapestry can call an advisor method of our module when constructing the ComponentMessagesSource service
  • We can inject services that do the advising right into advisor methods

Footnotes

1 Actually, all functions in Haskell take exactly one parameter which is both mind-blowing and not relevant to the discussion.

2 Services can be based on classes rather than interfaces, but then you lose a lot of these interface-based features, such as lazy proxies.

3Or, if the method has been advised multiple times, invoking proceed() may invoke the next piece of advice. For example, you may have added advice to a method for logging method entry and exit, and for managing database transactions as well as lazy evaluation.

Saturday, August 15, 2009

Detailed analysis of Tapestry 5

Sebastian Hennebrueder has just finished a detailed analysis of Tapestry 5. He comes at it from a few odd angles (for instance, he likes PicoContainer and shows how to integrate it). After a few misteps, he reaches these conclusions:

Once I overcame the first hurdles, I became more and more impressed. Building CRUD (create, read, update, delete) dialogs is incredible fast. The form component renders a form for a model, adding labels, input fields and validations. All this information is extracted from the model and its annotation and you don't have to write a single line of code. Here is the code for a complete form.

<t:beaneditform object="person"/>

You have control over the generated form and the possibility to change whatever you need either application wide or just in a single form. As a consequence, you get even less code than in a Ruby on Rails application. The learning curve is of course steeper than the one of the Stripes framework, but this is naturally. Stripes is a thin layer above the underlying technologies. Tapestry abstracts from the underlying technology in order to provide a lot of powerful functionality.

After having explored the functionality of the framework, writing my own components, writing mixins to extend existing components, I came to the conclusion that Tapestry is one of the most innovative frameworks and probably even the best candidate for enterprise applications.

To be honest, I think he makes the initial steps slighlty more complicated than they need to be and he properly criticizes the current state of the documentation. But he reaches the above conclusions, then goes into more detail, and finally outlines some performance data.

Quickie video from OSCON

In this video (from Greg Pollack), I have a very short segment discussing Clojure. Oddly, the fun part of what I said was clipped, which can be summarized as "Clojure is fast and I haven't had this much fun programming since I first learned Object Oriented programming (Objective-C, in 1995) or first started programming at age 13." I'm surprised they cut it because a few people in the speaker's lounge actually clapped after I was done!

Wednesday, July 29, 2009

Tapestry 2009 Summer Tour: England, Estonia

Just dropping a note from the road ... I'm currently in York, England for three days of Tapestry training here for a private client. Next week, five days of training and mentoring in Tartu, Estonia. I was a bit trepidatious about heading to Estonia, but now that I've read up on its history, I can't wait to get there.

After that, a bit of vacation in London and Paris ... then back to Portland (hopefully the temperatures will have dropped) to concentrate on Tapestry 5.1 documentation ... and learning Cappuccino for my upcoming talk at The Pacific Northwest Software Symposium.

Thursday, July 16, 2009

Infrequent commands

An odd usability thought just hit me as I'm making some simple updates to my slide decks in Keynote. Adding and positioning the page number is not something you can do without using the mouse (to click the button in the inspector and drag it into place). There's no menu item for this.

That's normally OK with me ... it's a very infrequent operation so why take up valueable menu space (as well as valuable user comprehension space) with it?

But the odd thing is that I think quite often, when you need to use an infrequent operation you need to use it a lot at once. That's probably why Microsoft applications have byzantine menu structures ... they never want anything to require the mouse and from a usability perspective they throw the baby out with the bath water over those infrequent commands.

All I'm missing from Keynote is a simple macro-recording feature: turn on page numbers, select it, drag it to position, update its style. I'd love to be able to run through my master slides (10 per presentation times 7 presentations!) and just it cmd-m or something.

Of course, if Keynote had master-masters, I might be able to make the change in one place per presentation (which would apply to the ten masters and the 60 or 70 slides per presentation).

Thursday, July 02, 2009

Caught between Two IDEs

I seem to be caught between two IDEs: Eclipse and IntelliJ. I abandoned Eclipse a couple of years back, partly based on wide spread recommendations from many different people, and partly because Eclipse just stopped working for me (it crashed out).

After I got started with IntelliJ I started to appreciate its merits, despite a generally clunky interface (with lots of modal windows), truly awful documentation. Many things are streamlined and only a ctrl-alt-shift-coke-bottle-touch-your-nose away.

However, over time, using IntelliJ got slower and slower and slower. It also started running the Tapestry test suite horrifically slowly: 40 minutes and up (it should be about five). It would often go away, even when memory wasn't tight. Indexing? Checking Repositories? Computing primes? No way to tell.

Meanwhile, Eclipse has been moving forward, with Eclipse Galileo being a Cocoa (not a Carbon) application. Critical plugins such as M2Eclipse have gotten nice, and the Clojure plugin is mostly better than the IntelliJ one (though both are very early).

For a while I was using IntelliJ when teaching Tapestry (as part of the VMWare image I use when training) ... and I got a lot of resistance. People were much happier with Eclipse on the last couple of go-rounds, and I'm sticking with it.

Overall, I'm feeling that most of what I've grown used to in IntelliJ is present in Eclipse, just handled a bit differently. The Clojure plugins are a wash; IntelliJ has the edge on the Git plugin. I think Subversion inside Eclipse is actually better.

I've even cranked up NetBeans but didn't find anything there compelling enough to switch.

It seems like all my major tools (Firefox, Firebug, Eclipse, IntelliJ) are in the habit of growing too complex, and doing too much stuff in the background that I don't care about. All those intentions in IntelliJ that you have to turn off (for performance reasons), and all those extra plugins for Eclipse that you need to not download in the first place ... they're all getting in my way.

I think a lot of this falls into the general category of accidental complexity ... to address the limitations of the Java programming language, all this extra stuff is coming into play: tools and wizards and plugins and indexes and whatnot. I find it pretty pleasant to work with Clojure instead, where the accidental complexity of Java is managed and isolated and the IDE doesn't feel the need to be overly ambitious. That's the Clojure concept right there ... grow the language to your needs, rather than building up tools. I think that's the Tapestry ethic as well.

Tuesday, June 30, 2009

Public Tapestry Training in London: Aug 10 - 12

Formos is partnering with SkillsMatter to provide open enrollment Tapestry training. The training will take place at the SkillsMatter offices in downtown London.

This is the same course materials that I use for my on-site Tapestry training ... and I'll be debuting a new lab on Ajax techniques.

Monday, June 22, 2009

Speaking on Clojure and Cappuccino as NFJS Seattle

I'll be talking about Clojure and Cappuccino at NoFluffJustStuff Pacific Northwest Software Symposium, which runs from Sept. 18th to the 20th in Redmond, WA.

I seem to have the 'C's covered, but how about the 'T's? If you are attending NFJS or you absolutely, positively, 100% would attend if I was speaking on Tapestry there ... provide some feedback to Jay!

Friday, June 19, 2009

Apache Tapestry 5: State of the Union

I've just uploaded my slide deck from JavaOne this year:

As usual, embedded animations and screencasts don't work, but there's a lot of value here regardless.

This presentation was a slightly extended version of the Webinar.

Thursday, June 18, 2009

Clojure talk from Open Source Bridge

Here's the slides for my Clojure talk at Open Source Bridge:

The upload to SlideShare was confused by parts of the slide deck, but you can follow the gist of it.

Presenting Clojure in 45 minutes is a challenge, and I'll need to be even briefer at OSCON next month.

Tuesday, June 16, 2009

Why choose Tapestry?

I recently had an e-mail exchange with a Tapestry user; after congratulating me on creating Tapestry, he went on with the following observation on his organization:

The company I work at unfortunately chose JSF for their big app. The reason was that Tapestry was "brittle" in the sense that, if one developer breaks something, on a page or a service, very often the whole site won't come up because the initial registry startup will fail. Or for example, if page A has a pagelink to page B, and page B is broken, then page A won't render. While I agree that we shouldn't ship unless the whole app is working, this is a thousands of pages big app with hundreds of mediocre (as in likely to break things) developers. They'd rather have 80% of the thing working than nothing at all. I never thought of this for my own projects, and haven't had the time to examine the truth of their claims. What's your take?

I provided the following response:

Early failures are absolutely, 100%, the only path towards code quality. You may have heard the phrase "no broken windows" (see "The Tipping Point" by Malcom Gladwell for more details) but the short form is that when errors go uncorrected (whether they are broken windows in an abandoned building, or broken code in an application) they tend to multiply quite rapidly.

The things that will "break" a link from page A to page B are substantial problems such as invalid templates, references to unknown properties or components, or compile errors in the page B class ... things that no other developer should ever see when page B's developer is working and checking in code. That is, problems that should never be checked into trunk, but instead kept in a local workspace or a private branch.

An organization that thinks that fail early is a problem is an organization that isn't prepared to develop a large application in any technology. The image I'm getting is one where there is no build server, no continuous integration, at best CVS for source code management (or possibly one of those "shared directory" monstrosities) .... i.e., a chaotic environment where errors are allowed to be checked in to the trunk and can go unnoticed for some time.

The solution to coding errors in pages or components is not to wait until your testers (or end users) find the bugs, but to identify and fix the bugs early. That's called "engineering discipline" and the reality is that even self-professed "mediocre" developers can do it. Tapestry helps because it fails early and has great exception reporting to guide you right the problem so that you can fix it.

Another factor here is enforced helplessness. If only Fred understands page B and he's out when it's broken, then all development stops waiting for Fred to get back. I hit this problem myself, years ago working on a large Struts application (those words give me the heebie jeebies now!). We had lots of code, a fragile and slow build process, and many little code "fiefdoms". I spent too much wasted time twiddling my thumbs.

Nobody should "own the code"; if page B is is broken, Julie (who normally develops page A) should be free to fix it. Julie will need to understand the page B code well enough to fix it, but also you need an overall environment with shared source, no repository locks (that is, nothing that says "Only Fred can change this file"), and no management PHB's getting in the way. Pair programming is the best way for Fred and Julie to share knowledge so that they can understand each other's code. Even if pairing occurs only part time, it's very effective at knowledge transfer as well as ordinary coding.

The idea that "mediocre" developers should use JSF as it is more tolerant of errors is absurd! Tapestry 5 is designed to improve productivity for all developers, by streamlining, simplifying, being smart and being concise ... not to mention live class reloading and best-of-breed exception reporting, which makes it fast to identify and fix those errors.

If your doctor tells you to eat less red meat, that doesn't mean you should switch to a diet of fried chicken three meals a day! Likewise, if you have concerns with code quality from your developers, you should not switch to a less agile, more code-intensive, less supportive development model and hope to catch all the bugs in QA. Sweeping problems under the rug is never a winning strategy.

Coming down off my soap box, I should also add that Tapestry 5.1 works a little bit differently than 5.0 in this respect, so it does (in fact) defer more of the page loading and validation until a link is actually clicked. This is more for performance reasons than to shield developers from application problems. Even in 5.0, the loading and validation was the "reach" from page A to pages explicitly referenced (usually via PageLink during the rendering of page A), so it's a highly unlikely case that a single error in a 1000 page application will keep the application from starting up, unless the start page of the application links to all 999 other pages.

Re-reading the above post I can't emphasize enough: you can't ignore quality problems. Quality problems lead to development failures, schedule slips, missing functionality, low morale and high turnover. Saying "we don't have time to fix the quality problem first" is to ignore the second law of Thermodynamics. You are expecting a miracle, literally writing it into your project plan.

Formos addresses this issue two ways: First, we use Scrum and deliver on (typically) 4 week cycles. Thus we set real deadlines and have a constant check on quality (we're providing working code constantly). We don't even try to predict what we'll be doing six months or two years from now, we just deliver a steady, manageable stream of software.

Secondly, Formos uses Tapestry precisely for all the reasons that the anonymous developer's organization rejected it, and for many, many more reasons besides.

Friday, June 12, 2009

Back from Tapestry 5 Training / Madison, WI

Just got back last night from training in Madison WI. This was primarily an intro to Tapestry 5 for Tapestry 4 developers (and a couple of new-to-web-programming developers) and a lot of hands on pair programming to get them going with their conversion work. It was a lot of fun, and as usual, I learned a bit.

I have a couple of weeks respite now before OSCON which will be followed with training in York, UK, then Estonia, then London.

I'm continuing to hold off on code changes to Tapestry 5, though I hit enough minor annoyances that I'm considering a 5.1 bug fix release before starting 5.2 in earnest.

I have a backlog of work related to Tapestry360 and then my main emphasis is going to be Documentation, Screencasts, Articles, Documentation and Documentation, plus some additional Documentation. It may be a while before I start with new features for 5.2.

Sunday, June 07, 2009

Upcoming Clojure Talks

I'll be speaking about Clojure at Open Source Bridge on June 17th here in Portland.

OSCON 2009

In addition, I'll be speaking on Clojure at OSCON, on July 23rd. OSCON has left its traditional home, Portland, in favor of San Jose, alas. If you are thinking of attending, use my friends, co-workers and family discount code, os09fos, and save 20%.

Tapestry Road Show: Madison, Wisconsin

This week, I'm in Madison, Wisconsin doing some on-site Tapestry training and mentoring. I've been here before and remember it as a pretty cool little college town. In any case, if you want to get together one night this week and chat Tapestry, Java, Open Source, Clojure or anything like that, drop a line!

Monday, June 01, 2009

Vendor Client Relationship Video

This has been making the rounds on YouTube:

As a vendor, I can identify with this ... but I think a lot of clients out there (those not using Formos, for example) are ordering and charged for the Filet and receive the Tacos. Seriously ... multi-year development efforts are a different beast than purchasing a DVD, which is why short iterations and agile development keep everyone's expectations (of both cost and functionality) inline and satisified.

Sunday, May 31, 2009

Back from Training and Portland Code Camp

It's been a rough week ... I still had my sore throat (noticeable during the webinar) when I arrived to do four days of accelerated Tapestry training in Michigan. Returning after midnight on Friday, I had morning and afternoon slots at Portland Code Camp on Saturday to talk about Clojure and Tapestry. I think it was a good little conference, and 75 minute time slots are just barely enough time to say something meaningful.

I attended a nice introduction to jQuery (once again confirming that I backed the wrong horse when selecting Prototype over jQuery for Tapestry), and another session on coding for iPhone.

The only other session I attended was iPhone Development from an ex Softie by Rory Blyth. It was entertaining in an unusual way, since Rory is very glib in a stream of consciousness kind of way, but he spent all but five minutes of his time ranting against Objective-C and iPhone toolkits. Literally he had five minutes for the core of his talk!

I was one of a few people in the audience who knew Objective-C (though it's more than ten years since I coded in it) and found many of his objections quite unreasonable. Basically, he wants Objective-C to look like every other language derived from C, which is missing the point of what Objective-C actually is: a melding of concepts from C and Smalltalk designed to operate on the very constrained hardware available, even for desktops, in the late 80's. It obligates developers to do something unreasonable by today's standards (a cumbersome retain count mechanism, rather than garbage collection), and the (optional) type syntax (such as (NSString *)) reveals its C heritage (as Smalltalk doesn't deal with declared types).

I even made this point to him; that Objective-C may be a natural fit for the constrained devices such as mobile platforms. His response to any challenge from any audience member was that we were afflicted with "Stockholm syndrome".

Strangely, a few minutes after I pointed out the "constrained device" theory (which he dismissed, disjointedly citing Windows smart phones as a "success") he then talked about ... the constraints of the iPhone in terms of memory, battery and CPU utilization.

Basically, Rory is unable to wrap his head around anything unfamiliar or to understand how a difference in philosophy can inform how a language syntax evolves, as well as the terminology (i.e., Objective-C's "receivers", "messages" and "selectors") used to describe that language.

There's a quote from the book Freakonomics, roughly (from memory):

Morality is how we think we should live our lives. Economics reveals how we actually do.

Rory has a kind of "language morality" that states the objects should be listed first, with periods separating member access, such as method invocation, and that languages that deviate from this are failed and broken. Unfortunately for that argument, the explosive success of the iPhone and the iPhone app market indicates that Objective-C is a tremendous development platform for the kind of intuitive, focused, responsive applications that dominate the market.

It was a shame, because his style was entertaining, if very "slacker" styled, and if he organized his thoughts a bit and kept track of the clock, his valid criticisms of the iPhone development environment would hold a bit more weight and reach a wider, more receptive audience.

Tuesday, May 26, 2009

Tapestry 5 Support in IDEA Maia

The next generation of IDEA, Maia will include Tapestry 5 support; this is according to Hugo Palma, who wrote the initial version of the IDEA Tapestry plugin that has been expanded for inclusion in Maia. Cool!

Monday, May 25, 2009

Tapestry & Clojure @ Portland Code Camp

Portland Code Camp is a local (in Portland) free conference on development tools and languages ... and it's coming up fast (this Saturday). I'll most likely be talking about Tapestry and Clojure ... I actually won't know until they make their last minute session selection tomorrow (it's based on how many attendees express interest). If you're in the Portland area and want to learn a bit more about Tapestry or Clojure, sign up and vote!

Sunday, May 24, 2009

Tapestry Road Show: Ann Arbor, Michigan

This week, I'll be in Ann Arbor, MI to do a bit (aka, four grueling days) of on-site Tapestry training. If you're in Ann Arbor and would like to get together for a drink and a chat about Tapestry, Open Source, or anything along those lines, please drop a line!

Wednesday, May 20, 2009

Last call: Tapestry Webinar (Thu May 21)

Formos is sponsoring a live Tapestry Webinar on May 21st. I'll be presenting a 35 - 40 minute introduction to Tapestry 5, followed by live Q&A. I'll be focusing on the big-picture issues about what makes Tapestry useful, unique and fun. If you've been curious about what Tapestry is, or want a chance to ask a question about Tapestry directly, this is a great chance to do it!

Click a link below to register for the Webinar:

Thursday, May 14, 2009

Tapestry 5 Performance Study

Ben Gidley has done a detailed analysis of Tapestry 5 performance He used some quite reasonable assumptions, including running tests for over an hour to gauge memory use over time. His conclusion is that Tapestry performance is excellent, which is a relief if not a surprise.

What I'm really pleased with is that he's demonstrated that Tapestry will perform well under load; more importantly, if a Tapestry application gets "Slashdotted", it may slow down, but it recovers once the wave of requests dies down.

Ben compared a Tapestry application to a Struts application with similar behavior and found that T5 out-performed Struts pretty handily, despite the fact that T5 doubles the number of requests (due to its automatic redirect-after-post behavior).

Thanks for the great work Ben!

Tuesday, May 12, 2009

Clojure @ JavaWorld

Another nice introduction to Clojure is now online: Clojure: Challenge your Java Assumptions at JavaWorld. An introductory Tapestry article is due at JavaWorld shortly as well.

Saturday, May 09, 2009

Apache Tapestry Community Map

Inspired by a map for Clojure, I created a map for Tapestry users:


View Apache Tapestry Users Worldwide in a larger map

Want to add yourself? The map is wide open, something I'll probably regret later.

Update: People have been adding themselves over the weekend, and trends are starting to appear. It seems like adoption is heavier in Europe than in the US (at least, among the narrow set of people who use Tapestry, know about the map, and have actually added themselves). Still, if that sample is true, it begs the question: why? Is it more to do with the European developers, or due to features of Tapestry (such as localization support).

I'm tending towards the former: talking with many Europeans shows that managers have a more "hands off" attitude towards project leaders, who are given more free reign to use tools of choice as long as they deliver. But that's also a horrible generalization from limited data. In fact, a contrary opinion was offered by some developers in Oslo last september ... in Norway, a small number of developers make strong vocal choices (lately, Spring and SpringMVC) and the whole community follows along. But even Europe is a big place which room for many different developer cultures.

Friday, May 08, 2009

Tapestry 5 Webinar: May 21st

Formos is sponsoring a live Tapestry Webinar on May 21st. I'll be presenting a 35 - 40 minute introduction to Tapestry 5, followed by live Q&A. I'll be focusing on the big-picture issues about what makes Tapestry useful, unique and fun. If you've been curious about what Tapestry is, or want a chance to ask a question about Tapestry directly, this is a great chance to do it!

Click a link below to register for the Webinar:

Wednesday, May 06, 2009

Apache Tapestry 5.1 Final Release - 5.1.0.5

The stable release for Tapestry 5.1, release 5.1.0.5, is now available for download.

Tapestry 5.1 includes a large number of improvements to the speed and scalability of Tapestry without sacrificing backwards compatibility. However, you should still consult the upgrade notes that identify a couple of minor "gotchas".

Major improvements from Tapestry 5.0 to Tapestry 5.1:

  • Performance optimizations that speed rendering of very complex pages, and reduce memory usage
  • Static JavaScript libraries will now be dynamically combined into a single request
  • Automatic GZIP compression of static and dynamic content, for clients that support it
  • Significant improvements to Tapestry's property expression language
  • Partial page updates (via Ajax) may now update multiple client-side Zones
  • Many new features and improvements to Tapestry template files
  • Improved client- and server-side validation of numeric input values
  • User's preferred locale is now encoded into the URL, rather than stored in a cookie
  • A new ProgressiveDisplay component for incremental page loading
  • Vastly improved Quickstart Archetype
  • New Tapestry IoC features to make it easier to override services and contributions to service configurations
  • Many new localizations of validation messages
  • Component reference documentation now identifies events triggered by components
  • Really slick new client-side logging facility (based on Blackbird)
  • New API for URL rewriting (to provide improved control over Tapestry URLs)

... and many, many more bug fixes and improvements; full details are available in the release notes.

Download Tapestry 5.1.0.5

Tuesday, May 05, 2009

Clojure: Turns out, parsing is somewhat iterative, not functional

... at least for me. I'm working on some more real-world code in Clojure, and I'm struggling with turning a stream of tokens (representing XML content parsed from a stream) into a DOM-like hierarchy. The tokens represent open tags, attributes, close tags, and text content.

I've written dozens of recursive decent compilers in the past, in Pascal, C, PL/1 and Java. The problem is, that style of compiler works by consuming a stream (of characters or tokens) ... a destructive operation.

Logically, I want code such as:

List parseTemplate(tokens)
{
  result = []

  while (tokens not empty)
  {
    token = pop(tokens)
    if (token.type == text)
    {
      add text token to result;
      continue;
    }

    if (token.type == element)
    {
      e = parse_element (token, tokens)
      add e to result;
      continue;
  }
}

Now, in Clojure I can write a (parse-element) function that takes a bunch of tokens, and it can work its way through the tokens using Clojure's (loop) and (recur) forms ... but to the calling function, tokens will not have changed.

One approach might be to pass along some pretty complex state; literally a state machine (inside a map) so that each function can keep push and popping values into and out of the state before invoking each. This would involve recursing on each token. Anyway, I have a rough idea of what that would look like and it seems complicated.

Instead, I've been pounding my head, looking at Clojure's support for monads. Monad are a way to combine certain types of functions together in a way that appears as if there were mutable state. You provide some base types of monadic functions and, within the context of a monad definition, it streamlines the way those basic monadic functions can be strung together to form more complex monadic functions.

Still trying to wrap my head around the concept however. It gets really confusing because the real power of monads appears when the monadic values, the things returned from a monadic function, are themselves functions. Yep, that's where I get lost too.

Update: passed the initial Monad hurdle; I'm finally beginning to understand them, perhaps even enough to explain them to other people.

Sunday, May 03, 2009

Excellent Clojure Introduction

R. Mark Volkmann has put together a terrific Clojure Introduction. It starts with basic syntax and quickly works through all the core functions, broken out by categories such as "Collections", "Iteration" or "Concurrency".

Very brisk, but full of functions (from the core) that I didn't know of.

For example, here's a function I never noticed before, (get-in), as defined in the Clojure API

(get-in m ks)
returns the value in a nested associative structure, where ks is a sequence of keys

That's accurate, but really encourages you to read the source or experiment in the REPL to see exactly how it works. Worse, its mixed in with hundreds of other functions and really easy to miss (I never noticed it before!).

Here's what the Clojure Introduction says:

To demonstrate this we'll create a map that describes a person. It has a key whose value describes their address using a map. It also has a key whose value describes their employer which has its own address map.

(def person {
  :name "Mark Volkmann"
  :address {
    :street "644 Glen Summit"
    :city "St. Charles"
    :state "Missouri"
    :zip 63304}
  :employer {
    :name "Object Computing, Inc."
    :address {
      :street "12140 Woodcrest Executive Drive, Suite 250"
      :city "Creve Coeur"
      :state "Missouri"
      :zip 63141}}})

The get-in function takes a map and a key sequence. It returns the value of the nested map key at the end of the sequence. The -> macro and the reduce function can also be used for this purpose. All of these are demonstrated below to retrieve the employer city which is "Creve Coeur".


(get-in person [:employer :address :city])
(-> person :employer :address :city) ; explained below
(reduce get person [:employer :address :city]) ; explained below

This is a document to keep linked and to print out and study. Thanks Mr. Volkmann!