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!

Monday, May 31, 2010

Tapestry Happenings: 5.2 beta soon?, New Committers, Dynamic component

There's lots and lots going on with Tapestry right now. We're gearing up to bring Tapestry 5.2 into a beta phase ... hopefully a short one before a final GA release.

We've been busy adding new committers to the team. Not every vote has been successful, but that just shows that the system works.

Lots of people are working on a terrific refresh of the Tapestry home page and documentation. Not just new text, but a new more timely approach (based on Confluence) and a terrific new layout. Just lots of energy going on there. I can't wait for it to be ready ... it really makes Tapestry looks like the first-class citizen it is.

Meanwhile, I've been busy for my clients, writing some useful code.

I've managed to get the nightly builds of the TapX working again ... just in time. I've created an exciting new component: Dynamic.

Dynamic is targeted for use in skinning applications. Tapestry's structure makes skinning an application a little tricky, especially if you want to take a standard application and customize it for different customers. Tapestry templates are expected to be uniform across all instances of a component (in the same way that a classes's methods and fields are fixed across all instances). This makes it tricky to create a component that renders differently for different users, which is the essence of skinning. Often, you end up either simulating a JSP include and injecting a blob of raw markup content into the Tapestry DOM ... or you end up with an evil nest of conditionals and indirection.

The Tapx Dynamic component exists to mix a dynamic template file with live Tapestry components; The dynamic template is any well-formed XML document that can be represented as a Resource; mostly, it is output as-is ... until Tapestry hits an element, such as a <div>, with a special id. When the id is of the form param:block-name, the Dynamic component replaces the dynamic template element with the content of the Block parameter passed to the Dynamic component. We're really weaving Tapestry and the external template together here.

I'm working on a good, simple example here ... but anyone who has struggled with this issue will appreciate what Dynamic accomplishes. It's not unlike running SiteMesh at the Tapestry component level.

Friday, April 30, 2010

Inform 7's Birthday

Long, long ago, some of my first programs were interactive fictions. Really simple stuff, "go north", "kill rat", that kind of thing. While I was hacking that kind of thing together in Basic, others had gone much further: the masters of Infocom. This system, written way back in the late 70's and early 80's, predates Java but has many similar features; including a bytecode-based runtime portable across different operating system and hardware platforms.

Sure games have gone in a different direction with incredible 3d graphics, but there's still a certain joy in playing interactive fiction games; it really is like playing a short story. The games have evolved from classic dungeon crawls into something more, with many of the best games eschewing puzzles and focusing on interaction between the player and the non-player characters.

The modern way to write these games, if you are so inclined, is in the Inform 7 environment. The Inform team try to come up with new releases every year, on the "birthday"; this month is the 17th birthday of the original Inform language (Inform was originally a more C-like objected oriented language that's evolved over the years into its current state).

Here's a getting started screencast:

Inform 7 Introductory Screencast from Aaron Reed on Vimeo.

I've gone a bit deep with Inform in the past, and hope to do more in the future. It's a truly amazing piece of software ... the language is generally a natural language (I call it "the mother of all DSLs") with features combining object oriented, rules based and even aspect oriented programming. In fact, the next release, due shortly, even includes map and reduce operations! The language is very powerful, allowing for concise ways to deal with deep cross-cutting concerns, allowing for human ambiguity

More than that, the IDE is truly full and integrated. It has extensive documentation (both a front to back manual and a cookbook), and error messages include hyperlinks to your code and to the manual pages. It includes hundreds of short examples that can be pasted directly into the editor with a single click. It has built in testing features (shown in the screen cast above). It provides an incredible cross-reference of your project (integrated with the built-in libraries) ... even an automatic map of your game world. It's truly a labor of love, and I wish any of the tools I work with day to day showed so much innovation and usefulness.

If you've ever wanted to write interactive fiction, or just want to play with a really fascinating alternative language, give Inform a try.

Monday, April 19, 2010

Setting up committer access Git for Tapestry 5

Given the problems I'm having, I decided to set up a new local Git repository for futher work. Here's how to do it:

$ git clone git://git.apache.org/tapestry5.git

This sets up a new working folder, tapestry5. It takes it a while to download the necessary Git repository objects.

$ cd tapestry5
$ curl http://git.apache.org/authors.txt -o .git/authors.txt
$ git config svn.authorsfile .git/authors.txt

This fetches the current list of authors so that proper names appear in various Git reports, then configures Git to make use of the file.

$ git svn init --prefix=origin/ --trunk=trunk https://svn.apache.org/repos/asf/tapestry/tapestry5

This tells Git where to sync from and to.

$ git svn rebase

And that finishes things up, ensuring that you have all the most recent revisions.

From here on in, the two commands you need the most are git svn rebase (to pull in repository changes) and git svn dcommit (to push deltas back to Subversion). You should always rebase before a dcommit.

Perhaps that's not quite complete; I generally create local Git topic branches; so I start my work with git co -Blocal to create (or overwrite) my local branch, do my work there as a series of commits, then: git co trunk ; git rebase local to move those commits back over to trunk before git svn dcommit. This helps a lot if you ever have to deal with a merge.

When I'm fixing particular bugs, I often create a branch names after the bug id.

Update: Removed the --tags and --branches arguments ... mostly because of how horribly Git SVN works with branches (don't try it!), and to make the init step nice and fast.

Git & Svn : Not Always A Match Made In Heaven

Apache is stuck using Subversion ... so I've been using the Git/Svn integration built into Git for a while now. The good news is that most of the Git workflow comes with it ... you can create private branches, do local commits to your local repository, and build up a series of changes to dcommit ("delta commit") into SVN.

But that doesn't always work. For reasons I don't understand (given that there have been no other commits to SVN since I started work in my private branch), I keep getting the following error:

Applying: Provide the missing asset request handler for the virtual "stack" folder.
Committing to https://svn.apache.org/repos/asf/tapestry/tapestry5/trunk ...
 M tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImpl.java
 M tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
 M tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
 A tapestry-core/src/test/java/org/apache/tapestry5/services/LibraryMappingTest.java
Committed r935569
W: 86533530aac8673a9e107e323de5201b7187270f and refs/remotes/origin/trunk differ, using rebase:
:040000 040000 9c78596ee3f916f012c51d8927b4aa31d497f17b 8eb2b9b4f28e825e223c736eaa664bb53018258e M tapestry-core
Current branch trunk is up to date.
# of revisions changed  
before:
 07b37e03cbc17012247d2221e795023c564d8228
0830b5f383dc94ae16088185efefac2e1358cf30
0bf378bcafc3f5372b67edc50d7de5bed8713cd0
95c87c5d7a2435df6bfced0d858bfdcb6ff26f22
3cd4ea4d9b225fd5013e1ce72cb9bac6d5b3e5e2
7b95d935099ebbbeb81845c1b8170a89d6ca6421
af310cfa9a5552aab2574c1e345b3beb049fb040
20805630fc67b83b4ca946b942716aeba4c80bef
b3ef5e069942a30e2dce45a35e4be16382c0108d
1be75a15b7f203c927bc2aa34f43dda59ca968e3
555d94ebab122a688b7a1c0af253bf73609f88f5
729757eb3c35e14e126cb6ef16f5032d95d1cc4a
79dcfa32b291454bf9c652d635374d60638b8fb8
304d12f9d7d040f4dc231d213df663fcdf3863b6
0d626a7b0648735ab83bc7a2fd241390eb92e4e2 

after:
 86533530aac8673a9e107e323de5201b7187270f
07b37e03cbc17012247d2221e795023c564d8228
0830b5f383dc94ae16088185efefac2e1358cf30
0bf378bcafc3f5372b67edc50d7de5bed8713cd0
95c87c5d7a2435df6bfced0d858bfdcb6ff26f22
3cd4ea4d9b225fd5013e1ce72cb9bac6d5b3e5e2
7b95d935099ebbbeb81845c1b8170a89d6ca6421
af310cfa9a5552aab2574c1e345b3beb049fb040
20805630fc67b83b4ca946b942716aeba4c80bef
b3ef5e069942a30e2dce45a35e4be16382c0108d
1be75a15b7f203c927bc2aa34f43dda59ca968e3
555d94ebab122a688b7a1c0af253bf73609f88f5
729757eb3c35e14e126cb6ef16f5032d95d1cc4a
79dcfa32b291454bf9c652d635374d60638b8fb8
304d12f9d7d040f4dc231d213df663fcdf3863b6
0d626a7b0648735ab83bc7a2fd241390eb92e4e2 
 If you are attempting to commit  merges, try running:
  git rebase --interactive --preserve-merges  refs/remotes/origin/trunk 
Before dcommitting
~/work/t5-project
$

I did the right things; git co trunk followed by git svn rebase, then git rebase revised-assets-12apr2010. It claimed to replay my branch changes on top of the trunk branch, but regardless, the dcommit failed.

Doing some hunting around with Google, I found a partial explanation, that at least gives me a way forward. I'd still like to know how I got into this predicament.

At this point I just keep blindly entering the command: git reset --hard 705ccfb1e27d303a9db62de755b2fcfcca9a02f6 ; git svn rebase; git svn dcommit and get one Git commit further each time (that's the Git hash code for my final change in my original branch). Joy.

Sunday, April 18, 2010

An extended stay in London

If you were unable to attend my "In the Brain Of" talk last Tuesday, it's now available at SkillsMatter ... voice, video and slides. This was a fun session, even if I was jet lagged to the point of being dizzy.

Meanwhile, I'm still hanging out in London until this at least the end of this week. I expect to work from my hotel room, visit a couple of clients, and maybe have a bit of fun. Drop me a line.

In London, looking for a Tapestry job? I've been contacted by a recruiter who is looking for you! They're building a team of perhaps 20 developers (I'm not yet sure who the actual client is). Drop me a line and I'll hook you up!

Tuesday, April 06, 2010

Meta-Programming Java with Tapestry

A significant amount of what Tapestry does is meta programming: code that modifies other code. Generally, we're talking about adding behavior to component classes, which are transformed as they are loaded into memory. The meta-programming is the code that sees all those annotations on methods and fields, and rebuilds the classes so that everything works at runtime.

Unlike AspectJ, Tapestry does all of its meta-programming at runtime. This fits in better with live class reloading, and also allows for loaded libraries to extend the meta-programming that's built-in to the framework.

All the facilities Tapestry has evolved to handle meta-programming make it easy to add new features. For example, I was doing some work with the Heartbeat enviromental object. Heartbeat allows you to schedule part of your behavior for "later". First off, why would you need this?

A simple example is the relationship between a Label component and a form control component such as TextField. In your template, you may use the two together:

  <t:label for="email"/>
  <t:textfield t:id="email"/>

The for parameter there is not a simple string, it is a component id. You can see that in the source for the Label component:

    @Parameter(name = "for", required = true, allowNull = false, defaultPrefix = BindingConstants.COMPONENT)
    private Field field;

Why does for="email" match agains the email component, and not some property of the page named email? That's what the defaultPrefix annotation attribute does: it says "pretend there's a component: prefix on the binding unless the programmer supplies an explicit prefix."

So you'd think that would wrap it up, we just need to do the following in the Label code:

  writer.element("label", "for", field.getClientId());

Right? Just ask the field for its client-side id and now all is happy.

Alas, that won't work. The Label component renders before the TextField, and the clientId property is not set until the TextField renders. What we need to do is wait until they've both rendered, and then fill in the for attribute after the fact.

That's where Heartbeat comes in. A Heartbeat represents a container such as a Loop or a Form. A Heartbeat starts, and accumulates deferred commands. When the Heartbeat ends, the deferred commands are executed. Also, Heartbeats can nest.

Using the Heartbeat, we can wait until the end of the current heartbeat after both the Label and the TextField have rendered and then get an accurate view of the field's client-side id. Since Tapestry renders a DOM (not a simple text stream) we can modify the Label's DOM Element after the fact.

Without the meta-programming, it looks like this:

    @Environmental
    private Heartbeat heartbeat;

    private Element labelElement;

    boolean beginRender(MarkupWriter writer)
    {
        final Field field = this.field;

        decorator.beforeLabel(field);

        labelElement = writer.element("label");

        resources.renderInformalParameters(writer);

        Runnable command = new Runnable()
        {
            public void run()
            {
                String fieldId = field.getClientId();

                labelElement.forceAttributes("for", fieldId, "id", fieldId + "-label");

                decorator.insideLabel(field, labelElement);          
            }
        };
        
        heartbeat.defer(command);

        return !ignoreBody;
    }

See, we've gotten the active Heartbeat instance for this request and we provide a command, as a Runnable. We capture the label's Element in an instance variable, and force the values of the for (and id) attributes. Notice all the steps: inject the Heartbeat environmental, create the Runnable, and pass it to defer().

So where does the meta-programming come in? Well, since Java doesn't have closures, it has a pattern of using component methods for the same function. Following that line of reasoning, we can replace the Runnable instance with a method call that has special semantics, triggered by an annotation:

    private Element labelElement;

    boolean beginRender(MarkupWriter writer)
    {
        final Field field = this.field;

        decorator.beforeLabel(field);

        labelElement = writer.element("label");

        resources.renderInformalParameters(writer);

        updateAttributes();

        return !ignoreBody;
    }

    @HeartbeatDeferred
    private void updateAttributes()
    {
        String fieldId = field.getClientId();

        labelElement.forceAttributes("for", fieldId, "id", fieldId + "-label");

        decorator.insideLabel(field, labelElement);
    }

See what's gone on here? We invoke updateAttributes, but because of this new annotation, @HeartbeatDeferred, the code doesn't execute immediately, it waits for the end of the current heartbeat.

What's more surprising is how little code is necessary to accomplish this. First, the new annotation:

@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Documented
@UseWith(
{ COMPONENT, MIXIN, PAGE })
public @interface HeartbeatDeferred
{

}

The @UseWith annotation is for documentation purposes only, to make it clear that this annotation is for use with components, pages and mixins ... but can't be expected to work elsewhere, such as in services layer objects.

Next we need the actual meta-programming code. Component meta-programming is accomplished by classes that implement the ComponentClassTransformationWorker interface.

public class HeartbeatDeferredWorker implements ComponentClassTransformWorker
{
  private final Heartbeat heartbeat;

  private final ComponentMethodAdvice deferredAdvice = new ComponentMethodAdvice()
  {
    public void advise(final ComponentMethodInvocation invocation)
    {
      heartbeat.defer(new Runnable()
      {

        public void run()
        {
          invocation.proceed();
        }
      });
    }
  };

  public HeartbeatDeferredWorker(Heartbeat heartbeat)
  {
    this.heartbeat = heartbeat;
  }

  public void transform(ClassTransformation transformation, MutableComponentModel model)
  {
    for (TransformMethod method : transformation.matchMethodsWithAnnotation(HeartbeatDeferred.class))
    {
      deferMethodInvocations(method);
    }
  }

  void deferMethodInvocations(TransformMethod method)
  {
    validateVoid(method);

    validateNoCheckedExceptions(method);

    method.addAdvice(deferredAdvice);

  }

  private void validateNoCheckedExceptions(TransformMethod method)
  {
    if (method.getSignature().getExceptionTypes().length > 0)
      throw new RuntimeException(
          String
              .format(
                  "Method %s is not compatible with the @HeartbeatDeferred annotation, as it throws checked exceptions.",
                  method.getMethodIdentifier()));
  }

  private void validateVoid(TransformMethod method)
  {
    if (!method.getSignature().getReturnType().equals("void"))
      throw new RuntimeException(String.format(
          "Method %s is not compatible with the @HeartbeatDeferred annotation, as it is not a void method.",
          method.getMethodIdentifier()));
  }
}

It all comes down to method advice. We can provide method advice that executes around the call to the annotated method.

When advice is triggered, it does not call invocation.proceed() immediately, to continue on to the original method. Instead, it builds a Runnable command that it defers into the Heartbeat. When the command is executed, the invocation finally does proceed and the annotated method finally gets invoked.

That just leaves a bit of configuration code to wire this up. Tapestry uses a chain-of-command to identify all the different workers (theres more than a dozen built in) that get their chance to transform component classes. Since HeartbeatDeferredWorker is part of Tapestry, we need to extend contributeComponentClassTransformWorker() in TapestryModule:

  public static void contributeComponentClassTransformWorker(
      OrderedConfiguration<ComponentClassTransformWorker> configuration
  {
  
    ...
    
    configuration.addInstance("HeartbeatDeferred", HeartbeatDeferredWorker.class, "after:RenderPhase");
  }      

Meta-programming gives you the ability to change the semantics of Java programs and eliminate boiler-plate code while you're at it. Because Tapestry is a managed environment (it loads, transforms and instantiates the component classes) it is a great platform for meta-programming. Whether your concerns are security, caching, monitoring, parallelization or something else entirely, Tapestry gives you the facilities to you need to move Java from what it is to what you would like it to be.

Tuesday, March 30, 2010

In The Brain Of Howard Lewis Ship

SkillsMatter Logo While I'm in London for three days of Tapestry 5 Training, I'll also be giving an evening In The Brain Of talk ... on Tapestry, because there's not that much else rattling around my brain lately. Whereas Ben's talk was about lessons learned at the tail end of a Tapestry project, my talk gives you a point of reference on what Tapestry is all about, and why you want to start using it.

Swing by, take in the talk, and come on out for a pint or two! The talk is Tuesday, April 13th at 18:30.

Saturday, March 27, 2010

Getting Ready for SkillsMatter Training

Talk about putting too much on your plate ... I'm working to update my existing Tapestry workshop to release 5.2, even though it is an alpha snapshot. The new features, especially live class reloading in the services layer, are just too useful to ignore.

Thursday, March 25, 2010

Improvements to Live Class Reloading in 5.2

I just spent the morning making some improvements and fixes to live class reloading. The umbrella for reloading has now extended to any class that Tapestry instantiates ... whether is by ObjectLocator.proxy() or any variation of Configuration.addInstance(). In all those cases, when Tapestry is doing the instantiating, and there's a service interface, what gets created is a reloadable proxy ... as long as there is a class file available.

The interesting part is that for development you get the live class reloading behavior. In production, you will not, because all the classes will be packaged up in JARs, not on the file system.

Tapestry Training Returns to London: April 14 - 16, 2010

I'll be returning to SkillsMatter to teach my Tapestry workshop. The course runs from April 14th to the 16th. I had a great time teaching the course back in February: SkillsMatter has a great facility, just perfect for a hands-on class like this one.

Monday, March 15, 2010

Procrastination and JavaOne 2010: See you in 2011!

Well, that's what I get for waiting until the last day ... by the time I had a chance to put together a submission or two for JavaOne 2010, the site was down (from about 10pm on). Kind of frustrating, I was looking forward to talking about Tapestry and Clojure (my main speaking staples) in front of another big crowd.

Friday, March 12, 2010

Live Service Reloading in Tapestry 5.2

A common question I get during Tapestry training sessions is: Why can't Tapestry reload my services as well as my pages and components?. It does seem odd that I talk about how agile Tapestry is, with the live class reloading, and how nicely OO it is, what with services ... but when you move common logic to a service, you lose the agility because services do not live reload.

This came up yet again, during my latest training session, in London.

I've considered this before, and I've been opposed to live service reloading for a couple of reasons. First, live reloading requires creating new class loaders, and that causes conflicts with other frameworks and libraries. You get those crazy ClassCastExceptions even though the class name is correct (same name, different class loader, different class). Further, in Tapestry IoC, services can be utilized to make contributions to other services ... changing one service implementation, or one module, can cause a ripple effect across an untraceable number of other services. How do you know what needs to be reloaded or re-initialized?

When I last really considered this, back in the HiveMind days, my conclusion was that it was not possible to create a perfect reloading process: one that would ensure that the live-reloaded Registry (and all of its services with all their internal state) would be an exact match for what configuration you'd get by doing a cold restart.

So I shelved the idea, thinking that simply redeploying the WAR containing the application (and the services and modules) would accomplish the desired effect.

But as they say, The Perfect Is The Enemy Of The Good. One very sharp student, Andreas Pardeike, asked: Why not just reload the service implementations?.

Why not indeed? Why not limit the behavior to something understandable, trackable, and not very expensive. Most of the infrastructure was already present, used for reloading of component classes. What about ClassCastExceptions? In Tapestry, service implementations are already buried under multiple layers of dynamically generated proxies that implement the service interface. The underlying service implementation is never automatically exposed.

A few hours of work later ... and we have live service reloading. Each reloadable service gets its own class loader, just to load the service implementation class. When Tapestry is periodically checking for updated files, it checks each reloadable service. If necessary, the existing instance, class and class loader is discarded and a new class loader created for the updated .class file.

This is going to make a big difference for me, and for most Tapestry developers. Both applications I'm working on have enough Hibernate entities and other clutter to take some time (20 - 30 seconds) to restart, and most functionality is hidden past a login page. Being able to change a service, for example to tweak a Hibernate query, with the same speed with which I can tweak a template or component class, is just one more thing to keep me in the flow and super productive.

Give it a try ... it's one more step towards making Tapestry so compelling, you wouldn't think of using anything else!

Thursday, March 11, 2010

Why Eclipse leaves me wanting

I think I've come to understand why Eclipse leaves me always feeling a bit frustrated. Yes, it is more stable than IDEA, uses less memory, has some documentation, and a lot of acceptance ... but even so, it just leaves me cold (and I was an early adopter, signed up for the beta way back in 2000!).

Keystrokes are not modal

The fact that I can type a common keystroke into an Eclipse window and not know what it will do is painful. How a keystroke is interpreted depends on what perspective is active, what view or editor has focus, and what kind of data is being edited in the editor. That's dead wrong; keystrokes are about muscle memory, and muscle memory remembers motion, not context. The end result is that I get frustrated hitting keystrokes and seeing nothing happening. It doesn't help that I cycle between Mac and a PC on most days.

You can't have it your way

A tool as powerful and extensible as Eclipse walks the tightrope of offering lots of features and customizations without overwhelming the user. Alas, Eclipse is lying in a broken heap fifty feet below that tightrope. Eclipse has an unending set of options and defaults for things I don't care about, but anything I do care about seems to never be presents. Here's a few ideas of the top of my head:

  • Stop running launches when I close the project (I often have to kill them from the command line)
  • Give me a quick way to stop all running launches
  • Why so many steps to implement an interface? It's the second most common thing I do!
  • How about a button to quickly relaunch the current running launch?
  • Why are the available refactorings so paltry and where are the 3rd party ones?

Who's eating their own dog food?

When I used IDEA, I was constantly struck by little details that showed that the IDE developers were also its prime users. For example, it has open-type and open-resource dialogs much like Eclipse ... but each recognizes the keystroke for the other, so that if you mistakenly activate the open-type dialog, you just hit the normal keystroke to switch over to open-resource. Eclipse makes you cancel the dialog first.

Another example: in IDEA if you rename a field, it notices the getter and setter and will offer to rename those as well.

IDEA also has lots of quick fixes everywhere, such as "implement this interface" and lots of other tiny, cool things I miss every single day I use Eclipse. It's been about a year since I gave up on IDEA and I still miss it.

Is it cultural or organizational? Eclipse gives me the impression that day-to-day developers either have no concept of how the IDE gets used (and what rough spots are causing some serious chafing) OR they are somehow prohibited from fixing things that are obviously wrong.

If you love IDEA so much why don't you marry it?

So why don't I use IDEA anymore? Two main reasons: first, it's become very bloated, to the point that unless you go in and shut off a ton of features, it's unusable on my hardware. Merlyn has the same problem doing GWT work on his MacBook Pro ... all the help it gives you comes at a cost in terms of CPU and memory utilization and some instability.

Secondly, I tried (even before IDEA went open source) to use IDEA in my training labs and I hit a stone wall of non-acceptance. Switching to Eclipse was a benefit to my students since, even running in Ubuntu instead of Windows, it was familiar and easy to navigate. It also out-performs IDEA inside my Ubuntu Virtual Machine. I simply lack the ability to switch between the two on a constant basis without getting completely confused and frustrated. I had to choose one, and I chose Eclipse: stable and accepted, even if it is brain dead.

Why call it Ugly?

One thing I don't get is how many people claim Eclipse is "ugly" and IDEA "beautiful". I found IDEA to be overly chock-full of modal dialogs and a number of improperly resized (or non-resizable) dialogs and windows. It's a real dog's breakfast in terms of UI, and has the classically ugly Swing look and feel.

I've always found Eclipse to look sharp and somewhat elegant. You can have a debate about the technical merits of SWT vs. AWT and Swing, or the ability to tune Swing to look like SWT ... but SWT out of the box is simply a better L&F visually.

On a Mac they both suck at keyboard navigation, though.

There, I've vented. See what going cold-turkey from Twitter can do?

Monday, March 08, 2010

Java Champion

You might call it petty, you might call it vain, but I've aspired to be recognized as a Java Champion for the last couple of years. The process by which you are selected for this is a bit secretive, but I've finally gotten the nod and joined the roster.

My larger goal for Tapestry has always been to create a web application platform so compelling that it would draw developers to the Java programming language, just to be able to use it. Of course, that's not so much a goal as it is a journey. Technologically, I think Tapestry has the chops to embrace that goal (or journey) ... and looking at current discussions and developments in the Tapestry world, I think the other critical areas where Tapestry is lagging (namely, Documentation and Marketing) may come around.

Want to do your part? Blog about Tapestry ... what you like, what you don't like, what's missing, and what's hidden.

Monday, February 22, 2010

March of Progress

Or should that be "Late February of Progress". I have to say I'm a bit envious right now of Rich Hickey ... I can see that he's continuing on like a steam roller, extending and improving Clojure. I guess he's having some success in generating Research and Design budget from funding companies. I can see, following his threads, that he's working on yet more concurrency metaphors for Clojure, which is a good thing (though eventually there'll need to be a big book just to describe them all).

I'm on a different track, in that I fund Tapestry out of pocket while doing training and project work. In some cases, those merge, such as when I add specific features to Tapestry for a specific client.

I'm of two minds here: doing project work keeps me grounded in real requirements for Tapestry. I see what works really well, and what needs some polishing. On the other hand, I come up with ideas for new components, improvements, and integrations all the time and barely have enough free time (between clients, ordinary Tapestry maintenance, and this special project) to even document my ideas, never mind implement, test and distribute them.

So, should I set up a funding option like Rich's? Well, that wouldn't help my current clients (I'm committed to getting their apps into production), but it may change how I would look for future work.

Friday, February 19, 2010

Evolving the Meta Programming in Tapestry 5

I've set a goal of removing Javassist from Tapestry 5 and I've made some nice advances on that front. Tapestry uses Javassist inside the web framework layer to load and transform component classes.

All that code is now rewritten to updated APIs that no longer directly expose Javassist technology. In other words, where in the past, the transformer code would write psuedo-Java and add it to a method using Javassist (for example adding value = null; to the containingPageDidDetach() method), Tapestry 5.2 will instead add advice to the (otherwise empty) containingPageDidDetach() method, and the advice will use a FieldAccess object to set the value field to null.

Basically, I've vastly reduced the number of operations possible using the ClassTransformation API. Before, it was pretty much unbounded due to the expressive power of Javassist. Now a small set of operations exist that can be combined into any number of desired behaviors:

  • Add new implemented interfaces to a component Class
  • Add new fields to a Class
  • Initialize the value of a field to a fixed value, or via a per-instance callback
  • Delegate read and write access to a field to a provided FieldValueConduit delegate
  • Add new methods to a component Class with empty implementations
  • Add advice to any method of a class
  • Create a MethodAccess object from a method, to allow a method to be invoked (regardless of visibility)
  • Create a FieldAccess object from a field, to allow the field to be read or updated (regardless of visibility)

What's amazing is that these few operations, combined in different ways, supports all the different meta-programming possible in Tapestry 5.1. There's costs and benefits to this new approach.

Costs

There will be many more objects associated with each component class: new objects to represent advice on methods, and new objects to provide access to private component fields and methods.

Javassist could be brutally efficient, the new approach adds several layers of method invocation that was not present in 5.1.

Incorrect use of method advice can corrupt or disable logic provided by the framework and is hard to debug.

Benefits

We can eventually switch out Javassist for a more stable, more mainstream, better supported framework such as ASM. ASM should have superior performance to Javassist (no tedious Java-ish parse and compile, just raw bytecode manipulation).

The amount of generated bytecode is lower is many cases. Fewer methods and fields to accomplish the same behavior.

The generated bytecode is more regular across different utilizations: fewer edge cases, less untested, generated bytecode

Key logic returns to "normal" code space, rather than being indirectly generated into "Javassist" code space ... this is easier to debug as there's some place to put your breakpoints!

Summary

Overall, I'm pretty happy with what's been put together so far. In the long run, we'll trade instantiation of long lived objects for dynamic bytecode generation. There's much more room to create ways to optimize memory utilization and overall resource utilization and the coding model is similar (closures and callbacks vs. indirect programming via Javassist script). I'm liking it!

Thursday, February 11, 2010

Live reloading of Tapestry services?

During today's Tapestry Training at SkillsMatter, the question about live class reloading for Tapestry services came up.

Now, my normal response is to talk about class loaders, and mysterious class-cast exceptions it would cause, and the need to shut down and restart the container, etc.

But an idea went around ... what about just live reloading of implementation classes? That sparked some thoughts.

See, it seems to me that it should be possible to create a class loader that loads a single class (and, perhaps, inner classes of that single class), much as Tapestry uses a class loader to load pages and components.

In fact, it should be possible to have separate class loader for every implementation class that just performs the reload of that one class. A periodic check of the file modification date stamps could trigger the release of the class loader (and the current instance) and the instantiation of a new class loader, and the loading of the updated class.

You wouldn't be able to change service interfaces this way, or module classes (including contributions and the like) ... but changing a service implementation should be a snap. This would especially be useful for DAOs while creating and tuning database queries.

I think there would be some limitations here: services that are built via builder methods would not work; neither would services that export this (typically, as a listener to events published by another service). However, the vast majority of services could, I think, be automatically reloaded.

This is worth spending some time on ... if I can pull it off, it would be an incredible coup!

The only downside is that some services may need to move from tapestry-core to tapestry-ioc and some of those may, in fact, be public already (but not widely used).

Devoxx: Clojure Talk Now Available

A full video of my Devoxx 2009 talk, Clojure: Functional Concurrency for the JVM is now available:

The talk runs about 40 minutes and does not include the questions and answers from the end. You can see I was just a touch jetlagged and perhaps awed by the size of the rooms at Devoxx ... they are full scale movie theaters, with gigantic screens above, and powerful lights shining directly in your eyes. Worse, I was getting terrible feedback for the first few minutes.

Don't forget to rate the talk at SpeakerRate!

In any case, I've evolved this talk quite a bit since last November, and the current version focuses on the language itself rather than the concurrency features of Clojure. That's the talk I just gave in Paris.

Meanwhile, my first Tapestry training at SkillsMatter continues (tomorrow is day three of three) and it's just been a lot of fun. It's interesting to see that the students are gaining at the extremes: so many people miss basic features like the ?. operator of the property expression language, or how dependencies work in the IoC container ... and the very same people are really interested in the advanced meta-programming and AOP techniques available to Tapestry.

Monday, February 08, 2010

Paris Clojure Talk

I had a terrific time spreading the word about Clojure tonight, followed by some fun and spirited discussions over dinner. People are intrigued by Clojure, even as they struggled with a strategy for bringing it into their organization.

If you attended, please rate my talk at SpeakerRate!

Commited to Tapestry

Quite a few people have commented on Ten Years of Tapestry, many to note some of the many other great projects being built with Tapestry as a foundation.

We keep a list of tutorials and extensions on the Tapestry home page, with many other sites noted on the wiki (here and here).

Meanwhile, a particular comment from Peter Rietzler was so compelling, it deserves to be top level, so here it goes:

Although our web application uses Tapestry we are using all the Tapestry support stuff far beyond the web tier - I thought that it might be interesting to see that the Tapestry framework is pretty useful in other environments too :)

First and most noteworthy Tapestry IOC got our first choice as dependency injection container, both because of its simplicity and the power of contributions and service overrides. We are building a highly modular (and massively unit- and integration- tested) application where Tapestry IOC's concept of modules and contributions has proven a perfect choice for us. I've written a couple of blog entries about this issue about a year ago when we were searching for a light-weight alternative to OSGi: Is OSGi going to become the next EJB ? and How to Design Software for Flexibility, Reusability and Scalability without loosing KISS principles!

We wrote and contributed Tapestry extensions for popular unit testing frameworks: Unitils (included in next release) and the Spock framework.

Additionally we are heavily using Tapestry services (such as pipelines and chains) in our core services.

Even the type coercion infrastructure has proven very useful for us. We developed a quite powerful Groovy DSL for Enterprise Data Mediation which is targeted to non-developers and we use Tapestry type coercion (with some extensions) tightly embedded in our DSL to free our e-business managers from the burden of providing correct types.

Our whole project heavily relies on small contributions of commands that are instantiated in high volumes at runtime and need environmental stuff injected - another point where Tapestry IOC has proven to be very useful.

Cheers and many thanks for your awesome work,

Peter

btw: I've forgot to mention that we presented our module system with Tapestry IOC at the Austrian Enterprise Java User Group meeting along with another talk about Spring DM and OSGi held by Sam Brannen last autumn.