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!

Friday, October 20, 2006

Tapestry 5 Screencast #2

This one shows how to use built-in Tapestry components, touches on the exception page, transient and persistent properties, and component event handler methods. Along the way, we see just how great it is to be able to change code on the fly! Tapestry 5 Technology Preview #2 -- 20 Oct 2006

The screencast clocks in at about 9 minutes.

For Tapestry 4 users, the two big things to pay attention to:

  • Action handler methods, not listener methods
  • Action requests send a client refresh, not updated HTML

The latter one is a big change from Tapestry 4, and implies that more data will have to be persistent between requests; the good news is that client property persistence, as well as flash persistence, will be supported fairly soon.

The constant question is: When will it be done? We'll be getting to the point where the other Tapestry developers can join the fun pretty soon, once I get basic forms and input validation working. I can see a useful platform ready in the spring and a final release, with some form of Spring and Hibernate integration, in the summer. Of course, my schedule is anything but fixed and set (I do have to earn a living around all of this!).

By the way, these screencasts are all done in a single take. What you see, complete with me fumbling with System.out.println, was my third full run through.

Page response is really that fast, even in the face of code changes. I'm not editting out pauses or any tricks like that. It's not just fast due to my dual-core monster desktop either; I've seen just as nice results on my more puny laptop. It's going to be hard to work on any other platform where I actually have to restart or redeploy just to see changes to my code!

Wednesday, October 18, 2006

HiveMind now an Apache Top Level Project

Finally ... HiveMind is now a top level project at Apache. This is great news, because it will allow the team to bring in HiveMind extensions from all over and put them under one roof.

I've been neglecting HiveMind for a while now as I concentrate on Tapestry 5, which is a shame, but unavoidable. Certainly, the people working on it now are quite capable. In fact, as often as not, if you see a clever idea in HiveMind, it was suggested or even implemented by James or Knut or one of the others.

Tuesday, October 17, 2006

Tapestry 5 Screencast #1

I've just put together a 6 minute screencast about Tapestry 5. It covers creating a new project (using Maven), setting up dependencies and structure, and creating a page template and seeing changes to the live application when you change a Java class.

Tapestry 5 Technology Preview #1 -- 17 Oct 2006

Class reloading is a key feature of Tapestry 5 and is exceptionally important to the stellar productivity Tapestry 5 will enable. Just as people fastened on to the Tapestry 3/4 templating system, I think many people will fasten on to class reloading. Don't be mistaken; there's a lot more to this enchilada than that one feature. But it's a great feature to hook people early on!

Sunday, October 15, 2006

The Ajax Experience -- Next Week

The Ajax Experience Just a reminder that The Ajax Experience is just over a week away.

Jesse Kuhnert and I will be presenting: Tapestry and Dojo: The Peanut Butter and Jelly of the Ajax World:

Dojo is an open source JavaScript library that provides an improved programming model for JavaScript and a suite of client-side tools and widges. Tapestry is an innovative open source client-side Java framework for building componentized web applications. At first glance, these two look like the Odd Couple, but pull back the covers a bit and you'll see a similar event model and design philosophy that makes these frameworks a cinch to put together. Tapestry 4.1 with Dojo brings about client-side Ajax joy without server-side Java pain.

I just got back from ApacheCon 2006, which was a fun time. I got a chance to talk Tapestry with members of the Shale team and with a few Tapestry users; I even demoed some of the new Tapestry 5 features (resulting in a few dropped jaws). Catch me at The Ajax Experience for more of the same! I also got a bunch of speakers hooked on the Hive boardgame.

It's interesting that one of Tapestry's core concepts, that the controller and view are tightly bound together, is a stumbling block for the Struts/Shale folks. They've been forced to deal with this outlandish, awkward, counter-productive separation between their business logic and their presentation/output for so long you can just see the gears gnashing together when thinking of something else, something that doesn't require tons of tedious coding and configuration. I'm in the process of adding some "Tapestry for Struts Programmers" documentation to the Tapestry 5 site, to try and outline the issues with action frameworks, and the solutions Tapestry offers.

Tuesday, October 10, 2006

Tapestry: A Component-Centric Framework

While I was on the road last week, I never had a chance to blog about a new article on OnJava: Tapestry: A Component-Centric Framework.

As usual, there are a number of things that rub me the wrong way. For example, I never use the class attribute of the page or component specification, I configure Tapestry to find my pages and my classes in appropriate packages.

The formatting of the examples in the article is a bit odd. I'm not sure who this article serves ... the article claims to show "how simple it is to develop a web application in Tapestry" but it doesn't come close. Actually, if I didn't know Tapestry not only would I not be interested in pursuing it because of this article, I would almost certainly be persuaded not to research further.

Nice to see more Tapestry in print, but as they say, if you want something done right ...

You'll be much better of reading Warner Onstine's Tapestry 101 once that's available. I've been doing the technical edits of this book as each chapter is ready and it's much closer to the mark!

eZing Builder -- WYSIWYG Builder for Tapestry

It's very unfortunate that Spindle has stalled, short of adding Tapestry 4 support. Fortunately, it's a big world and others have started stepping in to fill the gaps. On approach is Cognition, which uses a visual builder tied to a data model driven architecture.

Another one just brought to my attention is eZing Builder. From what I can tell (the site's a little short on documentation, and SourceForge is currently down for maintenance), it's a general purpose WYSIWYG editor for web applications, with specific Tapestry extensions. Built on Eclipse 3.2, and open source. Looks nice.

Wednesday, September 27, 2006

Very handy Regular Expression Tool: QuickREx

I was puzzling out how to do some text processing in Tapestry and it came down to some regular expressions. Now, I use these all the time, but I'm not hyper literate in them ... I always need to test them out before I feel confident in them. I had been using an Eclipse plugin for this that went payware, so for I while I was firing up IRB (interactive Ruby).

Fortunately, during a quick thinking break, I decided to see if there was a new Eclipse tool for this ... and there is QuickREx.

It does a very good job ... it allows you to write your regular expression, provide it with sample text, and view matches and groups within matches very nicely. You can set options (such as multiline or caseless) and it even can run for all the major different RE implementations out there (JDK, ORO Perl, ORO Awk, and JRegex). It does live evaluation, which really helps when trying to "tweak" the expression, and has a bunch of other well thought out features, such as helping you to paste the final expression into Java code (escaping the backslashes, and such, for you).

The expression editor includes completion; hitting ctrl-space brings up a menu of different regular expression codes to insert, each with a snippet of documentation. Nice.

The plugin also includes a secondary view, the Regular Expression Library. This contains a library of regular expressions, each with example text to match against and a chunk of documentation.

In fact, there's a few more features that are hard to explain out of context, but I suspect will prove quite useful. This is another example of a finely crafted tool created not as a demonstration of someone's Eclipse plugin coding chops, but created to be used.

Easy install via the update manager http://www.bastian-bergerhoff.com/eclipse/features) and it's run flawlessly since.

Tuesday, September 26, 2006

More Tapestry / Hibernate Integration: Honeycomb

Honeycomb is another integration between Tapestry and Hibernate. What's neat is that it supports Session Per Conversation (somewhat like Seam) and Session Per Request (much more common) right out of the box.

It even includes Maven archetypes to get the project set up quickly. Nice.

Once again, the power of HiveMind is evident here; just placing the Honeycomb JARs on the classpath mixes all the Honeycomb support directly into the application. No additional configuration needed.

On the other hand, there's virtually no documentation on how to use Honeycomb beyond the Javadoc ... and some of that is in German!

Monday, September 25, 2006

Javassist vs. Every Other Bytecode Library Out There

I've been getting a small amount of flack about Tapestry and HiveMind's use of Javassist. Yes, its inside the evil JBoss camp. Yes, it has a wierd MPL/LGPL dual license. Yes, the documentation is an abomination. Yes, the API is so ugly that I always craft an insulation layer on top of it. Yes, there are are other bytecode toolkits out there. So why am I so wedded to Javassist?

Because it's so damn powerful and expressive.

A lot of the magic in HiveMind and Tapestry 4 is due to Javassist, and Tapestry 5 is even more wedded to it.

Much of what HiveMind does could be done using JDK dynamic proxies. HiveMind uses proxies to defer creation of services until just needed ... you invoke a method on the proxy and it will go create the real object and re-invoke the method on that real service object. You code never has to worry about whether the service exists yet or not, it simply gets created as needed.

You can do things like that using JDK proxies, but proxies are not going to be as optimized by Hotspot as real Java classes. The core of dynamic proxies is to use reflection, each method invocation on the proxy turns into a reflective method invocation by the proxy's handler. There's further overhead creating an array of objects to store the parameters.

Simple proxies like that can certainly be written using other toolkits like ASM.

Because these proxies are so common in Tapestry 5, my insulation layer can build the whole proxy as a single call; the insulation layer translates this to Javassist API:

    public void proxyMethodsToDelegate(Class serviceInterface, String delegateExpression,
            String toString)
    {
        addInterface(serviceInterface);

        MethodIterator mi = new MethodIterator(serviceInterface);

        while (mi.hasNext())
        {
            MethodSignature sig = mi.next();

            String body = format("return ($r) %s.%s($$);", delegateExpression, sig.getName());

            addMethod(Modifier.PUBLIC, sig, body);
        }

        if (!mi.getToString())
            addToString(toString);
    }

Here, delegate expression is the name of the variable to read, or the name of the method to execute, that provides a proxy. The only real part of this code that is Javassist is that code snippet: return ($r) %s.%s($$);. The first %s is the delegate expression; the second is the name of the method. Thus this may be something like: return ($r) _delegate.performOperation($$); Javassist has a special cast, ($r) that says “cast to the method's return type, possibly void”. It will unwrap boxed values to primitives, as necessary. The $$ means “pass the list of parameters to the method”.

Thus we can see how quickly we can build up new methods that invoke corresponding methods on some other object.

In Tapestry 5, the real workhorse is the ClassTransformation system which is used, with Javassist, to transform classes as they are loaded into memory. This is how Tapestry 5 hooks into the fields of your class to perform injections and state management. Tapestry 4 did the same thing using abstract properties and a runtime concrete subclass … this is much more pleasant.

Some of the trickiest code relates to component parameters; there are runtime decisions to be made based on whether the parameter is or is not bound, and whether the component is or is not currently rendering, and whether caching is or is not enabled for the parameter. Here’s just part of that logic, as related to reading a parameter.

    private void addReaderMethod(String fieldName, String cachedFieldName,
            String invariantFieldName, boolean cache, String parameterName, String fieldType,
            String resourcesFieldName, ClassTransformation transformation)
    {
        BodyBuilder builder = new BodyBuilder();
        builder.begin();

        builder.addln(
                "if (%s || ! %s.isLoaded() || ! %<s.isBound(\"%s\")) return %s;",
                cachedFieldName,
                resourcesFieldName,
                parameterName,
                fieldName);

        String cast = TransformUtils.getWrapperTypeName(fieldType);

        builder.addln(
                "%s result = ($r) ((%s) %s.readParameter(\"%s\", $type));",
                fieldType,
                cast,
                resourcesFieldName,
                parameterName);

        builder.add("if (%s", invariantFieldName);

        if (cache)
            builder.add(" || %s.isRendering()", resourcesFieldName);

        builder.addln(")");
        builder.begin();
        builder.addln("%s = result;", fieldName);
        builder.addln("%s = true;", cachedFieldName);
        builder.end();

        builder.addln("return result;");
        builder.end();

        String methodName = transformation.newMemberName("_read_parameter_" + parameterName);

        MethodSignature signature = new MethodSignature(Modifier.PRIVATE, fieldType, methodName,
                null, null);

        transformation.addMethod(signature, builder.toString());

        transformation.replaceReadAccess(fieldName, methodName);
    }

That last line, "replaceReadAccess", is also key: it finds every place in the class where existing code read the field, and replaces it with an invocation of the method that contains all the parameter reading logic … a method that was just dynamically added to the class. A typical implementation of a parameter writer method might look like:

private int _$read_parameter_value()
{
  if (_$value_cached || ! _$resources.isLoaded() || ! _$resources.isBound("value")) return _value;
  int result = ($r) ((java.lang.Integer) _$resources.readParameter("value", $type));
  if (_$value_invariant || _$resources.isRendering())
  {
    _value = result;
    _$value_cached = true;
  }
  return result;
}

The point of these examples is this: we’re doing some complex code creation and transformation and Javassist makes it easy to build up that logic by assembling Java-like scripting code. I’m not sure what the equivalents code transformations would look like in, say, ASM but I can’t see it being as straightforward and easy to debug. Javassist lets me focus on Tapestry and not on bytecode and that makes it invaluable.

Saturday, September 23, 2006

Type Coercion in Tapestry 5

I just finished a bit of work I'm very proud of ... a fairly comprehensive type coercion framework for Tapestry 5.

Here's the problem: with the way you bind parameters in Tapestry, you are often supplying a value in one format (say, a String) when the type of the parameter (defined by the variable to which the @Parameter annotation is attached) is of another type, say int.

So ... who'se reponsible for converting that String into an Integer? Tapestry. Get used to that answer, because that's a big theme in Tapestry 5.

At the core of the solution is a simple interface for performing type coercions:

public interface Coercion<S, T>
{
    T coerce(S input);
}

Gussied up inside all that generics goodness is the idea that an object gets passed in, and some operation takes place that returns an object of a different type. Perhaps the input is a String and the output is a Double.

Now, we dress that up with a wrapper that helps Tapestry determine what the Coercion converts from (source/input) and to (target/output):

public class CoercionTuple<S, T>
{
    public CoercionTuple(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercer)
    {
      . . .
    }

    public Coercion<S, T> getCoercion() . . .

    public Class<S> getSourceType() . . .

    public Class<T> getTargetType() . . .
}

My brief look at Haskell influenced the naming ("tuple") and a lot of the overall design.

Now we have a service, TypeCoercer, that can perform the conversions:

public interface TypeCoercer
{
    <S, T> T coerce(S input, Class<T> targetType);
}

The TypeCoercer is seeded with a number of common coercion tuples (thanks to Tapestry IoC, you can contribute in more if you need to). From these tuples, the service can locate the correct coercion.

Now the neat part is that if there isn't an exact match for a coercion, that's not a problem. The service will search the tuple space and build a new coercion by combining the existing ones.

For example, there's a builtin String to Double tuple, and a builtin Number to Long tuple. The TypeCoercer will see that there's no way to convert String (or any of its super classes or extended interfaces) directly to an Integer, so it will start searching among the tuples that do apply.

This all happens automatically. Say you pass in a StringBuffer instead of a String; TypeCoercer will construct the compound coercion Object to String, String to Long, Long to Integer.

Writing this code was very pleasurable; too often the things I work on are too simple: move datum A to slot B, and I get the whole design for such a piece of code all at once and its just a scramble to get it coded (and tested, and documented) before that mental image fades. This time I had to work hard (despite the very small amount of code involved) to really understand the problem space and the algorithm to make it all work ... then back it up with a good number of tests.

Thursday, September 21, 2006

Speaking at PJUG Sept. 26

I'll be doing a fast paced talk on Tapestry 4 at this month's Portland Java User's Group. This will be Tuesday, Sept. 26th at the Adtech II building in Northwest Portland. [map]

The meeting starts at 6:30pm.

Tuesday, September 19, 2006

Upgrading from Eclipse 3.1 to 3.2

I've been using an ever larger number of Eclipse plugins in my development. I'm using Jetty Launcher, Maven, AJDT (AspectJ), Oxygen (XML editor), Subclipse (SVN support), TestNG, and a few lesser ones.

Eclipse is pretty good about backwards compatibility, so I've tried just switching my eclipse folder from 3.1 to 3.2. Should be new JARs against my existing workspace and we're off and running.

No such luck. I was working on that yesterday and I quickly got to a point where I could not convince Eclipse 3.2 to even try and compile my code. It's on the class path, I can see the class files in the package explorer, but no dice on compiling.

My sneaking suspicion is that it's the Maven 0.0.9 plugin (this plugin keeps Eclipse dynamically up to date with your project's pom.xml).

So I downgraded to Eclipse 3.1. Guess what? No dice there either.

As you might imagine, this put me in a bit of a panic. I tried deleting my project and checking it back out of the repository. Still no dice. The panic level increased again.

My final solution was drastic: delete my workspace entirely. Eclipse stores considerable meta data about your project outside the folder itself. In the shuffling up to 3.2 and back to 3.1, some amount of that has been corrupted.

It's not so bad ... it's now spring cleaning time as I'm starting from an entirely fresh Eclipse install and even re-downloading just the essential plugins that I need.

Friday, September 15, 2006

Tapestry at The Ajax Experience

Jesse Kuhnert and I will be presenting at The Ajax Experience this year on Tapestry and Dojo.

Dojo is an open source JavaScript library that provides an improved programming model for JavaScript and a suite of client-side tools and widges. Tapestry is an innovative open source client-side Java framework for building componentized web applications. At first glance, these two look like the Odd Couple, but pull back the covers a bit and you'll see a similar event model and design philosophy that makes these frameworks a cinch to put together. Tapestry 4.1 with Dojo brings about client-side Ajax joy without server-side Java pain.

BeanForm Component

One of the compelling features in Rails is the ease with which forms for creating/editting/updating/deleteting objects can be created. Tapestry has a lot of power under the hood with respect to forms, but it still doesn't come cheap enough out of the box. A centerpiece of Trails is a component that builds a full form for editting an arbitrary object. This idea has resurfaced as a new standalone component, BeanForm.

Just plug the following into your page's HTML template:

<span jwcid="@bf:BeanForm" bean="ognl:pojo" save="listener:save" delete="listener:delete"/>

BeanForm will build a complete form from this, adapting to each individual property's type. If you are using EJB3 or Hibernate annotations, BeanForm will pick up those annotations to build out appropriate client- and server-side validations.

And its extremely extensible and customizable even beyond that. Cudos to Daniel Gredler for putting this together.

Monday, August 28, 2006

Tapestry 5 Progress: Class Reloading

Hit one of my first major hurdles for Tapestry 5 this morning: class reloading. Tapestry 5 periodically scans .class files to see if they have changed, and will clear caches and discard class loaders so that it can process the changes. I've finally gotten the code base to a point where I can demonstrate this and it works. And it's fast!

This is a huge productivity win: you change your classes and see the changes immediately. No restart, no redeploy. The same logic works for templates and other resources (you make the change, you see the change).

Speed is excellent; obviously, I have only a tiny fraction of Tapestry 5 implemented, and the page I'm using is very trivial (just a single component). However, even on my laptop, and with all debugging output enabled, refresh is instant.

By the way, here's my page template:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">    
    <head>
        <title>First Tapestry 5 Page</title>
    </head>
    <body> 
        <p>
            This is the <span style="color:green">First Tapestry 5 Page, ever!</span>.
        </p> 
        <p>
            Output from HelloWorld component:  <t:comp id="foo" type="HelloWorld"/>                
        </p>
    </body>`
</html>

And here's the HelloWorld component:

package org.apache.tapestry.integration.app1.components;

import org.apache.tapestry.MarkupWriter;
import org.apache.tapestry.annotations.ComponentClass;
import org.apache.tapestry.annotations.RenderTag;

@ComponentClass
public class HelloWorld
{
    @RenderTag
    void renderMessage(MarkupWriter writer)
    {
        writer.write("I Am HelloWorld");
    }
}

I need to do more experimentation; my environment is Eclipse 3.1.2 + Jetty (4) + JettyLauncher plugin. There's ample opportunity for the servlet container to screw us, in terms of their class loaders getting in the way (not showing file changes). Lots of people seem to use the Sydeo Tomcat plugin as well, so I need to check that.

Sunday, August 20, 2006

Update to tapestry-testng

A new version of tapestry-testng has been upload. The version number (1.0.0-SNAPSHOT) has not changed, but there's some new features:

  • Uses TestNG 5.1, the latest version
  • Supports running tests in parallel

The previous versions of tapestry-testng uses a simple instance variable to store the EasyMock control. This is insufficient for TestNG, which creates a single instance of a test case class, and then will invoke test methods on it from multiple threads. This new vesion of the library stores the EasyMock control in a thread local variable.

However, I'm still not 100% certain this will work properly due to how TestNG operates ... it doesn't seem to invoke cleanup methods from the correct thread. See this forum posting. If TestNG is, in fact, broken, then we'll have to wait for a TestNG 5.2 that fixes the issue.

Thursday, August 03, 2006

New version of tapestry-testng

I quietly released a nearly-final version of tapestry-testng a couple of days ago. The new version number is 1.0.0-SNAPSHOT.

The only significant change is that I changed the TestBase base class to extend from org.testng.Assert, so you can access all the assertion methods without doing a static import.

Wednesday, July 26, 2006

Metaprogramming Java with HiveMind

Holy time management, Batman! I'm going to get a bad rep at OSCON for running over time (last year, though, it was due to a late start).

Basically, I was pacing myself for a 60 minute session, not the 35 they give you. Way too many slides, too much intro, kept me from the cool stuff at the end of the session. Also, there were lots of good, informed questions.

My goal for the Tapestry session, later today, is to run under. But I don't see that happening.

Tapestry for PHP?

PRADO is a PHP framework expressly inspired by Tapestry. As you might expect, all I've had a chance to do is glance over the documentation, but it looks very nice, and their site is very professional and slick.

I suspect the relationship between PRADO and Tapestry is the same as between Tapestry and WebObjects ... the precursor "proves" the space is viable, but I doubt the implementation is all that similar, given the different languages involved.

Still, best of luck ... but don't expect any framework, on any platform, to keep up with Tapestry 5!

Monday, July 24, 2006

Tapestry 5 Updates

Even during OSCON, I've been churning out code for Tapestry 5.

The new Tapestry IoC container is rapidly coming together. The idea of replacing XML with Java classes (and naming conventions and annotations) is working out wonderfully. I'm busy putting together a version of configurations about now.

What I'm finding, as I code and also predict how the final Tapestry code will use the container, is some steep improvements.

In Tapestry IoC, injection isn't into your beans via property setters or constructors, the way it is in HiveMind and Spring. Instead, injection is via parameters to your builder methods. Injection via method parameters will also occur into decorator methods (the replacement for service interceptor factories) and into contribution methods (which contribute values into service configurations).

For example, in Tapestry IoC:

package org.example.myapp.services;

import org.apache.tapestry.ioc.annotations.Id;

@Id("myapp")
public class MyAppModule
{
  public Indexer buildIndexer()
  {
    return new IndexerImpl();
  }
}
The above defines a service "myapp.Indexer" as an instance of IndexerImpl. The equivalent in HiveMind would be :
<module id="myapp" version="1.0.0" package="org.example.myapp.services">
  <service-point id="Indexer">
    <create-instance class="IndexerImpl"/>
  </service-point>
</module>
That's already an improvement. By the time you start talking about dependencies, things get even better:
  public Indexer buildIndexer(String serviceId, Log serviceLog, 
    @InjectService("JobScheduler") JobScheduler scheduler, 
    @InjectService("FileSystem") FileSystem fileSystem)
  {
    IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem);
      
    scheduler.scheduleDailyJob(serviceId, indexer);

    return indexer;
  }

What's worthy of note here is how dependencies get injected in (as method parameters), and that lifecycle concerns about the IndexerImpl are separate from its implementation. We don't have to inject the scheduler into IndexerImpl in order for the Indexer to be scheduled by the scheduler ... that concern can be implemented only in the builder method.

To accomplish that kind of separation in either HiveMind or Spring would require quite a bit of hackery; say, creating a new, specialized kind of ServiceImplementationFactory (in HiveMind) that understood about talking to the scheduler. Lots of work for something that can be expressed so easily in a short, simple, testable Java method.

I think that we'll see this approach bear fruit in the form of fewer services to accomplish the same goals. It will allow for non-service objects to easily receive injected services ... such objects can be created "on the side" by builder methods (or contributor methods).

This is the theme for all of Tapestry 5: Simpler, easier, faster, more understandable, more powerful. Avoid XML. Improve productivity. Make the framework adapt to your classes and your methods, rather than the other way around.