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, April 05, 2011

An Example Of Why I Like Spock

Spock is really making writing tests fun, instead of a chore.


    @Unroll("toClass '#javaName' should be #expectedClass")
    def "toClass tests"() {
        expect:

        PlasticInternalUtils.toClass(getClass().classLoader, javaName) == expectedClass

        where:

        javaName | expectedClass
        "java.lang.String" | String.class
        "java.lang.Integer[]" | Integer[].class
        "java.lang.Long[][]" | Long[][].class
        "void" | void.class
        "int" | int.class
        "int[]" | int[].class
        "float[][]" | float[][].class
    }

This combines lots of things I've seen before in TestNG, but nicer; just more readable ... and the @Unroll annotation (which guides Spock on how to report the test execution) is really handy, especially when things go wrong. It's just a slick overall package.

Saturday, March 26, 2011

Combining Gradle with Antlr3

I've been going through a relatively painless process of converting Tapestry from Maven to Gradle, and am thrilled with the results. My biggest stumbling point so far was Tapestry's use of Antlr3 for its property expression language.

The built-in support for Antlr only went as far as Antlr2. The Maven plugin I had been using understood Antlr3. After a bit of research and hacking, this is what I came up with as a solution for Tapestry:

description="Central module for Tapestry, containing all core services and components"

antlrSource = "src/main/antlr"
antlrOutput = "$buildDir/generated-sources/antlr"

configurations {
  antlr3
} 

sourceSets.main.java.srcDir antlrOutput

dependencies {
  compile project(':tapestry-ioc')
  compile project(':tapestry-json')
  
  provided project(":tapestry-test")
  provided "javax.servlet:servlet-api:$servletAPIVersion"

  compile "commons-codec:commons-codec:1.3"

  // Transitive will bring in the unwanted string template library as well
  compile "org.antlr:antlr-runtime:3.3", { transitive = false }

  // Antlr3 tool path used with the antlr3 task
  antlr3 "org.antlr:antlr:3.3"
}

// This may spin out as a plugin once we've got the details down pat

task generateGrammarSource {
  description = "Generates Java sources from Antlr3 grammars."
  inputs.dir file(antlrSource)
  outputs.dir file(antlrOutput)
} << {
  mkdir(antlrOutput)
  
  // Might have a problem here if the current directory has a space in its name
  
  def grammars = fileTree(antlrSource).include("**/*.g")
    
  ant.java(classname: 'org.antlr.Tool', fork: true, classpath: "${configurations.antlr3.asPath}") {
     arg(line: "-o ${antlrOutput}/org/apache/tapestry5/internal/antlr")
     arg(line: grammars.files.join(" "))
  }
}

compileJava.dependsOn generateGrammarSource

The essence here is to create a configuration (a kind of class path) just for running the Antlr Tool class. The new task finds the grammar files and feeds them to the tool. We also thread the output of the tool as a search path for the main Java compilation task. Finally, we define the inputs and outputs for the task, so that Gradle can decide whether it is necessary to even run the task.

Part of the fun of Gradle is that it is still a Groovy script, so there's a familiar and uniform syntax to defining variables and doing other non-declarative things, such as building up the list of grammar files for the Tool.

As you might guess from some of the comments, this is something of a first pass; the Maven plugin was a bit better at assembling the list of input file names in such a way that the Antlr3 Tool class knew where to write the output Java source files properly; if Tapestry used a number of grammars in a number of different locations, the solution above would be insufficient. It also seems roundabout to use Ant to launch a Java application ... I didn't see an easier way (though I have no doubt its hidden inside the Gradle documentation).

My experience getting this working was mostly positive; there's a very large amount of documentation for Gradle that helped, though it can be a bit daunting, as the information you need is often scattered across a mix of the Gradle DSL reference, the User Guide, the Javadoc and the GroovyDoc. Too often, it feels like a solution is only understandable once finished, working backwards from some internal details of Gradle (such as which exact classes it chooses to instantiate in a given situation) back through the various interfaces, Java classes, and Groovy MetaObject extensions to those classes.

In fact, key parts of what I did ultimately accomplish were discovered through web searches, not in the documentation. But, that also means that the system works.

Of course, this is the pot calling the kettle black ... one criticism of Tapestry can be paraphrased as we can customize it to do anything, and in just a few lines of code, but it can take three days to figure out where those lines of code go.

At the end of the day, I'm much happier with Gradle; the build process is faster, the build scripts are tiny and much, much easier to maintain, and the feedback from the tool is excellent. There's still many more issues to work out ... mostly in terms of Apache and Maven infrastructure:

  • Ensuring the Maven artifacts are created properly, with the right dependencies in the generated pom.xml
  • Generating a Maven archetype using Gradle
  • Generating JavaDoc and Tapestry component documentation with Gradle, along with a minimal amount of pages to link it together (akin to the Maven site plugin)
  • Generating source and binary artifacts and getting everything uploaded to the Apache Nexus properly

Regardless, I think all of these things will come together in good time. I'm not going back, and dearly hope to never use Maven again!

Wednesday, March 16, 2011

Better Namespacing in JavaScript

In my previous post, I discussed some upcoming changes in Tapestry's client-side JavaScript. Here we're going to dive a little deep on an important part of the overall package: using namespaces to keep client-side JavaScript from conflicting.
I'm not claiming to originate these ideas; they have been in use, in some variations, for several years on pages throughout the web.

Much as with Tapestry's Java code, it is high time that there is a distinction between public JavaScript functions and private, internal functions. I've come to embrace modular JavaScript namespacing.

One of the challenges of JavaScript is namespacing: unless you go to some measures, every var and function you define gets attached to the global window object. This can lead to name collisions ... hilarity ensues.

How do you avoid naming collisions? In Java you use packages ... but JavaScript doesn't have those. Instead, we define JavaScript objects to contain the variables and functions. Here's an example from Tapestry's built-in library:

Tapestry = {

  FORM_VALIDATE_EVENT : "tapestry:formvalidate",

  onDOMLoaded : function(callback) {
    document.observe("dom:loaded", callback);
  },

  ajaxRequest : function(url, options) {
    ...
  }, 

  ...
};

Obviously, just an edited excerpt ... but even here you can see the clumsy prototype for an abstraction layer. The limitation with this technique is two fold:

  • Everything is public and visible. There's no private modifier, no way to hide things.
  • You can't rely on using this to reference other properties in the same object, at least not inside event handler methods (where this is often the window object, rather than what you'd expect).

These problems can be addressed using a key feature of JavaScript: functions can have embedded variable and functions that are only visible inside that function. We can start to recode Tapestry as follows:

Tapestry = { 
    FORM_VALIDATE_EVENT : "tapestry:formvalidate"
};

function initializeTapestry() {
  var aPrivateVariable = 0;

  function aPrivateFunction() { }

  Tapestry.onDOMLoaded = function(callback) {
      document.observe("dom:loaded", callback);
  };

  Tapestry.ajaxRequest = function(url, options) {
    ...
  };
}

initializeTapestry();

Due to the rules of JavaScript closures, aPrivateVariable and aPrivateFunction() can be referenced from the other functions with no need for the this prefix; they are simply values that are in scope. And they are only in scope to functions defined inside the initializeTapestry() function.

Further, there's no longer the normal wierdness with the this keyword. In this style of coding, this is no longer relevant, or used. Event handling functions have access to variables and other functions via scoping rules, not through the this variable, so it no longer matters that this is often not what you'd expect ... and none of the nonsense about binding this back to the expected object that you see in Prototype and elsewhere. Again, this is a more purely functional style of JavaScript programming.

Often you'll see the function definition and evaluation rolled together:

Tapestry = { 
    FORM_VALIDATE_EVENT : "tapestry:formvalidate"
};

(function() {
  var aPrivateVariable = 0;

  function aPrivateFunction() { }

  Tapestry.onDOMLoaded = function(callback) {
      document.observe("dom:loaded", callback);
  };

  Tapestry.ajaxRequest = function(url, options) {
    ...
  };
})();

That's more succinct, but not necessarily more readable. I've been prototyping a modest improvement in TapX, that will likely be migrated over to Tapestry 5.3.

Tapx = {

  extend : function(destination, source) {
    if (Object.isFunction(source))
      source = source();

    Object.extend(destination, source);
  },
  
  extendInitializer : function(source) {
    this.extend(Tapestry.Initializer, source);
  }
}

This function, Tapx.extend() is used to modify an existing namespace object. It is passed a function that returns an object; the function is invoked and the properties of the returned object are copied onto the destintation namespace object (the implementation of extend() is currently based on utilities from Prototype, but that will change). Very commonly, it is Tapestry.Initializer that needs to be extended, to support initialization for a Tapestry component.


Tapx.extendInitializer(function() {

  function doAnimate(element) {
    ...
  }

  function animateRevealChildren(element) {
    $(element).addClassName("tx-tree-expanded");

    doAnimate(element);
  }

  function animateHideChildren(element) {
    $(element).removeClassName("tx-tree-expanded");

    doAnimate(element);
  }

  function initializer(spec) {
    ...
  }

  return {
    tapxTreeNode : initializer
  };
});

This time, the function defines internal functions doAnimate(), animateRevealChildren(), animateHideChildren() and initializer(). It bundles up initializer() at the end, exposing it to the rest of the world as Tapestry.Initializer.tapxTreeNode.

This is the pattern going forward as Tapestry's tapestry.js library is rewritten ... but the basic technique is applicable to any JavaScript application where lots of seperate JavaScript files need to be combined together.

Rethinking JavaScript in Tapestry 5.3

I've always had a love/hate relationship with JavaScript; some of the earliest motivations for Tapestry was to "encapsulate that ugly JavaScript stuff so I don't have to worry about it again." However, as I've come to appreciate JavaScript, over time, as a powerful functional language, and not as an incompletely implemented object oriented language, my revulsion for the language has disappeared ... even reversed.

Back around 2006, I started adding the client-side JavaScript features to Tapestry 5; this started with client-side form field validation, and grew to include a number of more sophisticated components. The good news is these features and components are fully encapsulated: they can be used freely throughout at Tapestry application without even knowing JavaScript. Tapestry includes the libraries (and related CSS documents) as needed, and encapsulates the necessary initialization JavaScript. The APIs for this were revamped a bit in Tapestry 5.2, but the core concept is unchanged.

The bad news is that the client-side is directly linked to Prototype and Scriptaculous (which are bundled right inside the Tapestry JAR file). These were great choices back in 2006, when jQuery was new and undocumented (or so my quite fallible memory serves). It seemed safe to follow Rails. Now, of course, jQuery rules the world. I've been talking for a couple of years about introducing an abstraction layer to break down the Prototype/Scriptaculous dependency; meanwhile I've recently seen that Rails and Grails are themselves moving to jQuery.

However, that abstraction layer is still important; I have clients that like MooTools; I have clients that are using YUI and ExtJS.

Certainly, it would have been too ambitious to try to start with such an abstraction layer from day 1. At the time, I had no real idea what the relationship between JavaScript on the client, and the application running on the server, would look like. Also, my JavaScript skills in 2006 are a fraction of what they are now. With several years of coding complex JavaScript and Ajax components for Tapestry, for TapX, and for clients, I think I have a much better understanding of what the APIs and abstraction layers should look like.

So suddenly, I have a number of goals:

  • Allow Tapestry to work on top any JavaScript framework
  • Support Prototype/Scriptaculous and jQuery as substrate frameworks "out of the box"
  • Make the built-in Tapestry library first class: documented and release-on-release compatible
  • Keep backwards compatibility to Tapestry 5.2

What I'm proposing is a gradual transition, over Tapestry 5.3 and 5.4, where new, documented, stable JavaScript APIs are introduced. and Tapestry and 3rd party libraries can code to the new APIs rather than to Prototype/Scriptaculous. The goal is that, eventually, it will be possible to switch the default substrate from Prototype/Scriptaculous over to jQuery.

Wednesday, March 09, 2011

Hibernate w/ transient objects

Am I missing something with Hibernate, or is it pretty darn hard to mix the following:

  • Session-per-request processing (the approach provided by the tapestry-hibernate module)
  • Transient objects (a wizard where a complex object is "built" across multiple request/response cycles)
  • Persistent objects (the transient keeps references to some persistent objects)

Hibernate seems to make it a bit tricky for me here. I get a lot of odd exceptions, because the new object has references and collections that ultimately point to persistent objects that are detached (their session is long gone).

I'm having to write a lot of code to reattach dependencies, just before I render the page (which traverses the transient object, eventually hitting persistent objects) and before persisting the transient object.

I'm having to iterate over various collections and a few fields and lock the object, to convert it back to a persistent object from a transient one:

    public static void reattach(Session session, Object transientObject) {
        if (transientObject != null) {
            session.buildLockRequest(LockOptions.NONE).lock(transientObject);
        }
    }
In other cases, where the transient object has a reference to an object that may already be present in the Session, I must use code like:
  category = (Session) session.get(Category.class, session.getId());

If Tapestry supported it, I suppose some of this would go away if we used a long-running Session that persisted between requests. However, that has its own set of problems, such as coordinating the lifecycle of such a session (when is it started? When is it discarded? What about in a cluster?)

My current solution feels kludgey, and not like Idiomatic Java, more like appeasing the API Gods. I'd really like to see this happen more automatically or transparently ... for instance, when persisting a transient instance, there should be a way for Hibernate to "gloss over" these detached objects and just do what I want. Perhaps its there and I'm missing it?

Monday, January 10, 2011

I recently stumbled across a blog post by Kalle Korhonen: Why Tapestry?. Kalle has been very busy with Tapestry as the force behind Tynamo, a RAD toolkit built on top of Tapestry.

On the subject of performance:

the performance of the framework itself, both in terms of CPU and memory consumption, is simply phenomenal. Performance matters.

On how Tapestry compares with the competition:

What I really like to give as an answer to people who ask why one should use Tapestry is this: because it is well-balanced and comprehensive.{excerpt} There are a lot of other web frameworks that are optimized with a certain thing in mind and in that narrow field, they typically beat the competition. It's difficult though to be a good all-around contender but that's exactly what Tapestry is all about.

On the effectiveness of Tapestry as a solution:

Today's Java is far from your grandfather's Java a few years back and Tapestry makes the best use of the more advanced, modern JVM techniques available today, such as bytecode manipulation, annotation-based meta programming and introspection without reflection. Tapestry code is purposefully remarkably succinct.
On how Tapestry enables modularity:
Perhaps we've gone a bit overboard with modularity, but since it's just that simple with Tapestry, most of our modules are independently usable but seamlessly work together in the same web application as soon as you add them to the classpath.

There's quite a bit more, and it is both favorable to Tapestry and well balanced. Read the full posting.

Tuesday, December 28, 2010

Is it time to switch back to IntelliJ?

I've been trying to stick with Eclipse now for a while. I switched from IntelliJ 8 to Eclipse because IntelliJ stopped working for Tapestry (I still don't know why) and because my fingers were getting tied in knots switching between IntelliJ and Eclipse. I have to switch back and forth because my Tapestry training uses Eclipse and I got a lot of negative feedback the times I tried to get people to use IntelliJ as part of the training.

In any case, I've been gritting my teeth against the travesty that is Eclipse; the inconsistent behaviors, the needless complexity, the lack of sense in much of the UI. I often wonder what IDE the Eclipse developers use, because it couldn't possibly be Eclipse itself, or they would have fixed some of the more brain-damaged stuff years ago.

One thing that is currently killing me is that Eclipse has no concept that some source folders contain production code and some contain test code. Because of that, when I collect coverage, EMMA instruments the test code as well as the production code. That not only throws off coverage figures, but breaks some of my tests (advanced ones about bytecode manipulation, that are sensitive to when EMMA adds new fields or methods to existing classes).

Fortunately, EMMA has an option to restrict its instrumentation (though it a global preference, and not configurable for individual projects) ... but shouldn't Eclipse understand this distinction natively? IntelliJ does, and it helps prevent a lot of problems, not just with coding, but with testing as well.

I keep hoping that there's a better, faster, simpler solution out there ... something that is elegant and precise. Eclipse is dumb as a sackful of hammers, and IntelliJ is almost fractal in its complexity, and ugly to boot.

I haven't found my perfect IDE yet. Maybe it's NetBeans?

Thursday, December 16, 2010

Announcing Tapestry 5.2

I'm very proud to announce that the next major release of Tapestry, release 5.2, is now available as Tapestry version 5.2.4.

This is the first stable release of Tapestry since 5.1.0.5 (back in April 2009), which is far too long a cycle. You might wonder: what's been the holdup? The answer, for myself personally, is that I've been using Tapestry on two very, very different applications for two very, very different clients and I've been updating Tapestry to embrace the real world concerns of both of them. At the same time, I've done about a dozen public and private Tapestry training sessions and gathered reams of input from my students.

Let's talk about some of the major enhancements in this release:

Removal of Page Pooling

Prior versions of Tapestry used a page pool; for each page, Tapestry would track multiple instances of the page, binding one page instance to a particular request. This was an important part of Tapestry's appeal ... all the issues related to multi-threading were taken over by the framework, and you could code your pages and components as simple POJOs, without worrying about the threading issues caused by running inside a servlet container.

Unfortunately pages are big: it's not just one object but instead the root of a large tree of objects: components and templates, bindings for component parameters, component resources, and all the extra infrastructure (lists and maps and such) to tie it together. Some of the largest Tapestry projects have hit memory problems when they combined deeply componentized pages with large numbers of parallel threads.

Tapestry 5.2 rewrites the rules here; only a single page tree is now needed for each page; the page and component classes have an extra transformation step that moves per-request data out of the objects themselves and into a per-thread Map object. Now, any number of requests can operate at the same time, without requiring additional page instances. Even better, the old page pooling mechanism included some locking and blocking that also gets jettisoned in the new approach. It's just a big win all around.

Live Service Reloading

People love the ability to change page and component classes in a Tapestry application and see the changes immediately; prior to 5.2 the same people would be disappointed that they couldn't change their services and see changes just as immediately. Tapestry 5.2 eliminates that restriction in most cases.

This is super handy for services such as DAOs (data access objects) where it is now possible to tweak a Hibernate query and see the results as immediately as changing some content in a template. This is another Tapestry feature that you'll find you can't live without once you use it the first time!

ClassTransformation API Improvements

At the heart of Tapestry is the Class Transformation API; the extensible pipeline that is the basis for how Tapestry transforms simple POJOs into working components. Prior to 5.2, if you wanted to do any interesting transformations, you had to master the Javassist psuedo-Java language.

Tapestry 5.2 reworks the API; it is now possible to do all kinds of interesting transformations in strict Java code; Javassist has been walled off, with an eventual goal to eliminate it entirely.

Query Parameter Support

Tapestry traditionally has stored information in the HTTP request path. For example, a URL might be /viewaccount/12345; the viewaccount part of the URL is the name of a page, and the 12345 part is the ID of an Account object. Tapestry calls the latter part the page activation context (which can contain one or more values).

That works well when the a page has a fixed set of values for the page activation context, but not so well when the values may vary. For instance, you may be doing a search and want to store optional query parameters to identify the query term or the page number.

Tapestry 5.2 adds the @ActivationRequestParameter annotation that automates the process of gathering such data, encoding into URLs as query parameters, and making it available in subsequent requests.

Testing

A lot of work has gone into Tapestry's testing support, especially the base classes that support integration testing using Selenium. The new base classes make it easy to write test cases that work independently, or as part of a larger test, automatically starting and stopping Selenium and Jetty as appropriate. Further, Tapestry expands on Selenium's failure behavior, so that failures result in a capture of the page contents as both HTML and a PNG image file. It is simply much faster and much easier to write real, useful tests for Tapestry.

JSR-303 Support

Tapestry now supports the Bean Validation JSR, converting the standard validation annotations into client-side and server-side validations.

Documentation

Tapestry's documentation has always been a challenge; for Tapestry 5.2 we've been doing a massive rework; doing a better job of getting you started using Tapestry; it's still a work in progress, but since it's based on a live Confluence wiki (and no longer tied to the release process) the documentation is free to quickly evolve.

Better yet, you don't have to be a committer to write documentation ... just sign your Apache Contributor License Agreement.

And in terms of exhaustive guides ... Igor Drobiazko's book is being translated from German to English as Tapestry 5 In Action.

Summary

I'm very proud of what we've accomplished over the last 18 months; we've added new committers, new documentation, and lots of new features. We even have a fancy new logo, and a new motto: Code Less, Deliver More.

Tapestry 5 was designed so that it would be possible to make big improvements to the internals and add new features to the framework without impacting existing users and applications; Tapestry 5.2 has validated that design and philosophy. It delivers a lot of punch in a non-disruptive way.

So, if you are looking for a high-productivity, high-performance web framework that doesn't get in your way, it's a great time to take a closer look at Tapestry!

Friday, November 12, 2010

Starting and Stopping Jetty Gracefully with Groovy and JMX

I'm working on a project that uses Tapestry and ActiveMQ together; it works great on my Mac, but on my client's Windows workstation, ActiveMQ doesn't shut down cleanly and corrupts its local files pretty consistently.

Unfortunately, there isn't a way (using RunJettyRun, the Eclipse plugin for Jetty) to gracefully shutdown Jetty. You just pull the plug on it, mid execution.

Looking for a solution, I realized that Jetty can expose most of its internals via JMX; this would allow us to start it up and shut it down cleanly in development.

So, I created a Groovy LaunchApp class to launch Jetty with JMX enabled:

package com.example.main

import java.lang.management.ManagementFactory 


import org.eclipse.jetty.jmx.MBeanContainer 
import org.eclipse.jetty.server.Server 
import org.eclipse.jetty.webapp.WebAppContext
import org.slf4j.LoggerFactory

/** 
 * Alternative the RunJettyRun Eclipse plugin that allows greater control over how Jetty starts up.
 */
class LaunchApp {
 
 static PORT = 8080
 
 public static void main(String[] args) {
  
  def LOG = LoggerFactory.getLogger(LaunchApp.class)
  
  LOG.info "Starting up Jetty ${Server.getVersion()} instance on port $PORT ..."
  
  def server = new Server(PORT)
  
  server.stopAtShutdown = true
  server.gracefulShutdown = 1000 // 1 second
  
  def context = new WebAppContext()
  
  context.setContextPath "/"
  context.setWar "src/main/webapp"
  
  server.setHandler context
  
  def mBeanServer = ManagementFactory.getPlatformMBeanServer();
  def mBeanContainer = new MBeanContainer(mBeanServer);
 
  server.container.addEventListener(mBeanContainer);
  
  mBeanContainer.start();
  
  server.start()
  
  LOG.info "Join the fun at http://localhost:$PORT/landing"
  
  server.join()
  
  LOG.info("Jetty instance has shut down")
 }
}
... and a Groovy StopApp class:
package com.example.main

import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

/** 
 * The flip-side of {@link LaunchApp}, this tool locates the running Jetty instance and uses JMX
 * to request a graceful shutdown.
 */
class StopApp {
 
 static JMX_PORT = 8085
 
 static JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:$JMX_PORT/jmxrmi"
 
 public static void main(String[] args) {
  println "Shutting down Jetty instance"
  
  def connector = JMXConnectorFactory.connect(new JMXServiceURL(JMX_URL), null)
  
  connector.connect null
  
  def connection = connector.getMBeanServerConnection()
  
  def on = new ObjectName("org.eclipse.jetty.server:type=server,id=0")
  
  connection.invoke on, "stop", null, null 
  
  println "Shutdown command sent"   
 }
}

The only trick is to ensure that LaunchApp's JMX MBean server is exposed for access, so you need the following system properties set:

-Dcom.sun.management.jmxremote.port=8085
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Thursday, November 04, 2010

First Rule of LinkedIn: Customize the Message

As far as social networks go, I like LinkedIn a lot. Nice site, lots of features, generally quite useful. In fact, I like it enough to be careful about who I link to ... for example, I only link to people I've met in person (or, at least, had a phone conversation with).

For some people, social networks are a game and the score is the number of connections. Thus, I get a fair number of "cold" link requests.

Here's a trick: if you want someone like me to link to you, you need to customize the message. It better say something like "Hey Howard, we hung out at JavaOne last year." or "I attended your talk on Clojure." or something else real and personal or it's quite, quite, likely to be deleted immediately.

Monday, November 01, 2010

Tapestry 5.2.2

... and the latest version of Tapestry, 5.2.2, is now available. This is the second beta release for Tapestry 5.2, addressing a few bugs in 5.2.1, and adding a couple of minor non-disruptive improvements ... read about it in the release notes.

Tapestry 5.2.2 is available for download, or via Maven:

<dependency>
    <groupId>org.apache.tapestry</groupId>
    <artifactId>tapestry-core</artifactId>
    <version>5.2.2</version>
</dependency>

I expect some minor issues will be addressed in Tapestry 5.2.3. Expect that in a week or so.

Thursday, October 28, 2010

Gradle is Great

My initial experience with Gradle is very positive. I've been quickly able to convert over my Maven POMs to Gradle build files, which are wonderfully concise. You can see the last of the results in this commit (work backwards to the parent commits to see some of the Gradle build scripts).

So far, Gradle is living up to its motto: A better way to build.

Next up is dealing with Maven artifact deployments, and the generation of project reports.

I will absolutely be using Gradle for all upcoming projects, and will hopefully revise Tapestry itself to use Gradle at some point soon.

Google Analytics vs. localhost

Are you using Google Analytics and trying to test it on your pages? Does it appear to do absolutely nothing?

After a lot of playing around, we discovered that Google Analytics appears to check if the URL is for "http://localhost" (any port) and disables itself. No warning, even if you are using the new debug version of the Google Analytics script (which is still minimized and obfusicated, which kind of flies in the face of debugging). The GA script just does nothing.

Re-open your page using "http://127.0.0.1:8080/" and it works like a charm.

There is the briefest of mentions in a comment in their forums that kind of indicates this is how it is supposed to work. Ugh. It should be in bold font: This script disables itself when the accessed page is on localhost.

It does make sense that you don't want to flood GA with spurious page hits while you are developing and testing your application ... but would it kill them to send a warning to the FireBug console?

Update: using the asynchronous API along with the debug version (.../u/ga_debug.js) does appear to work, even on localhost. When I first looked at this a couple of months ago, the debug script was not available.

Monday, October 04, 2010

Back in London at SkillsMatter

I'm back in London for another Tapestry training workshop; I can't wait to see how well the revisions and extensions are going to work with a fresh crew of Tapestry developers.

Thursday, September 30, 2010

What a waste: Kindle and Interactive Fiction

I think its a terrible waste that the Kindle doesn't support Interactive Fiction. I would love the be able to play IF games untethered, and IF is the right game for the Kindle which excels at presenting text and falls flat at anything that requires a real refresh rate (the LCD screen refreshes very, very slowly).

I know the Kindle has a development kit; I wonder what it would take to get IF games playing on it?

Friday, September 17, 2010

New testing lab for Tapestry Workshop @ SkillsMatter (London, Oct 5th)

SkillsMatter Logo I'm once again partnering with SkillsMatter to teach my full Tapestry workshop.

I've just finished up the materials for the new lab that covers testing of Tapestry components and applications. I'm quite pleased with how it came out. I'm really looking forward to this training. According to SkillsMatter, there's still room in the class for a couple of more students ... if you want to get started with Tapestry, or you want to get your Tapestry Ninja Skills going, this is the way to do it, fast!

The class will be taught at SkillsMatter's offices in London, from October 5th through the 8th.

Tapestry at JavaOne 2010

Just a reminder: I'll be presenting Tapestry: State of the Union this coming Monday, Sep 20th at 8:30 PM, in Moscone South 309.

Because of pressing demands and client commitments, I'm only going to be in San Francisco briefly: from Sunday afternoon to Tuesday evening.

Blogger finally checks for spam!

This is a big relief for me, because every time I posted a new entry, I'd get half a dozen spams, in chinese. Their new spam filter seems to work nicely. I may eventually open up comments again for non-registered users.

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:

    @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
        
        expect(stackPathConstructor.constructPathsForJavaScriptStack("core")).andReturn([])
        
        expect(coreStack.getStacks()).andReturn([])
        expect(coreStack.getStylesheets()).andReturn([])
        expect(coreStack.getInitialization()).andReturn(null)
        
        JSONObject expected = new JSONObject("{\"activate\":[\"$expectedFieldId\"]}")
        
        linker.setInitialization(InitializationPriority.NORMAL, expected)
        
        replay()
        
        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor)
        
        cls jss
        
        jss.commit()
        
        verify()
    }
    
    @Test
    void simple_autofocus() {
        
        autofocus_template "fred", { 
            it.autofocus FieldFocusPriority.OPTIONAL, "fred"
        }
    }
    
    @Test
    void first_focus_field_at_priority_wins() {
        autofocus_template "fred", {
            it.autofocus FieldFocusPriority.OPTIONAL, "fred"
            it.autofocus FieldFocusPriority.OPTIONAL, "barney"
        }
    }
    
    @Test
    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.