Tapestry Training -- From The Source

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

Thursday, December 21, 2006

Waiting for VMWare Mac

I'm looking forward to doing more Tapestry 4 training fairly soon, and some Tapestry 5 training in the medium future. One important part of my arsenal is missing: VMWare for Mac. Four months ago they said "open beta real soon now" (i.e., by the end of the year). I hope I don't have to lug along my old Dell Laptop, just to run my VMWare Ubuntu image!

Tuesday, December 05, 2006

Tapestry 101

Warner Onstine's book on Tapestry 4: Tapestry 101 is now available!

Tapestry 101 is a great introduction to Tapestry using real-world examples. It goes beyond just using Tapestry components and dives into integrating Tapestry with Spring and with Hibernate. It's organized around a real application implemented in the small and should go a great way towards filling in the gaps in people's Tapestry knowlege.

It's available right now as a downloadable PDF.

Friday, December 01, 2006

The Difference between Blogs and Support Lines

The difference between Blogs and Support Lines:

Tapestry questions posted as blog comments don't get answered.

If you have a question about Tapestry, the tapestry user mailing list (users@tapestry.apache.org) is the correct starting point. Questions asked there get answered pretty quickly, by the Tapestry community (including the committers).

Failing that, you can contact me or any of the other Tapestry committers. Just remember that for us, Tapestry is a business and answering questions can be a distraction, a drain on resources that needs compensation.

From my perspective, every minute of my time should be spent on Tapestry 5. Any distraction from that, any time I could be working on T5 but end up working on something else, is a net negative for the Tapestry community as a whole.

So if you're one of the folks whose been posting various questions onto the blog as comments (I see all the comments because I moderate them) ... well, now you know why I don't usually answer. This simply isn't the right venue or the right situation.

Tapestry 5 Progress: Localization, Assets

Been rapidly reassembling Tapestry functionality inside the new Tapestry 5 code base.

A first pass at localization is now in place. It's missing a couple of key features and a bunch of bells and whistles, but the basics are in place.

I've also started implementing assets. Currently, only context assets are supported, but I'll be working on classpath assets next.

Both of these things were necessary detours before I could start work on form input validation in earnest.

The code is coming together rapidly and very nicely, very cleanly.

I've been stretching to meet "the principle of least surprise" ... just looking for ways that the framework can cleanly and easily do things automatically. For example, the @Inject annotation is very flexible; it takes into account the type of field when determining how to interpret its value. @InjectAsset would have been easier, but that's one more thing for people to remember.

Also, I'm working on an "automatic" Tapestry stylesheet. I want Tapestry apps to have a good, clean look automatically, by inheriting a base stylesheet from the application itself.

Interestingly, because of the way Tapestry 5 renders (to a simplified DOM), it won't be necessary to have Shell or Body components, as with Tapestry 4. There's a post-processing stage that will be able to navigate the DOM and make selected changes, somewhat like a limited version of SiteMesh. This stage will be able to do things like insert a stylesheet link into the <head>.

In fact, I think there will be significantly fewer components in Tapestry 5, especially because of mixins (which will allow existing components to be used in new ways, rather than forcing the creation of entirely new components).

Onward and upwards. Less is More

Monday, November 27, 2006

Firefox 2.0 stability on Mac OS X

I've found that Firefox 2.0 is simply not stable on Mac OS X (10.4.8 Intel). It was locking up on my multiple times a day. I downgraded to 1.5 and have had virtually no problems since (in the last couple of weeks).

It seemed like it had trouble with heavy JavaScript pages such as Google Mail. Again, 1.5 is fine.

It really is like being between a rock and a hard place; Safari is faster and more stable but doesn't do everything I want it to do (such as the WebDeveloper or FireBug plugins to Firefox). FireFox is less "OS-X-y" (James Duncan Davidson won't run it because it feels like a Windows program to him ... and it sure could use a few more glowing curvy glass buttons!). Some common JavaScript, such as animation effects from Dojo, run incorrectly in Safari but correctly in FireFox. So, what's next ... Opera?

Nope, probably stay with Firefox (1.5, until they admit there are Mac OS X problems and fix them). It's much more likely that my clients will be running FireFox than Safari or Opera (my clients, and my client's client's are almost universally running Windows). I think a shift has occured, where people understand that Firefox is the standard, and that IE may take some extra effort "to get working correctly". I'm not saying people don't expect things to run flawlessly in IE ... just that they understand that it's not automatic, that IE makes things harder.

Sunday, November 26, 2006

The Danger of Beta: Tapestry and HiveMind Blog has Moved

Some day there will be a medical diagnosis for my condition, which may be characterized as an overeagerness to adopt unstable beta software.

In this case, it was a switch to Blogger Beta, which promised all kinds of improvements to the user experience of reading this blog. Alas, the real improvements are of questionable value, such as publishing an Atom feed where the RSS feed used to be (and not writing the correct <link> tags).

Anyway, it became pretty evident that the new, post Google Blogger really wants to host the blog on blogspot.com. Thus the new URL:

http://tapestryjava.blogspot.com

Wednesday, November 22, 2006

Updates to Tapestry @ JavaForge

Well, I've had a busy morning. I've done a ton of work on the projects at Tapestry @ JavaForge.

I've changed the version number of everything to 1.0.0-SNAPSHOT.

Documentation has been updated. Site navigation updated. Look and feel is now aligned with everything else Tapestry.

This was spurred by a comment in the user mailing list about how tapestry-spring doesn't work with prototype beans. It does, but I haven't updated the documentation.

Of course, Maven 2 is letting me down here. It doesn't support site publishing via FTP (just secure FTP and various SSH related options). This adds a manual step to deploying the documentation that I'll roll into a Ruby script next time I publish.

Anyway, tapestry-testng, tapestry-flash and tapestry-spring are release candidates. tapestry-prop may have a few features (already in Tapestry 5) added to it before its final.

Thursday, November 09, 2006

Switched!

I've joined the legions of other No Fluff Just Stuff speakers, excluding (of course), Ted Neward, who are running on Mac. A MacBook Pro (the latest version) with 2 gig of ram, and a 30" cinema display.

Of course, the user experience is fantastic, especially with the 30" display. I don't even own a TV that large. It's so big that I sometimes have to pull windows with small text down to eye level for easy reading. Scary big. Ridiculously big. Love it.

In one respect, it's like coming back home; my first real windowing environment (the first I developed applications for) was NeXTSTEP back in the mid 90's. I developed a big database driven GUI app using AppKit and Enterprise Objects Framework. I remember an Apple rep coming by to show us some weird thing called WebObjects, but that's a different story.

It's intimidating ... there's so much new stuff to learn. Hanging around with Stu, Justin, Neal and Dave (among many others) who are so big on mastering your environment and working smart ... well, between Automator, Quicksilver, and everything else ... well, there's a lot to learn, a lot of catching up to do. And of course, Ruby is pre-installed as well.

Dashboard widgets look cool; as I'm trying to flex some JavaScript muscle, that's a definite way to build something useful as well.

So far, the only thing I don't like is the Mighty Mouse; I already ditched mine on Ebay and am using the same Logitech LX7 I use on my Aurora (windows desktop). I also had to pick up a keyboard, since the monitor is so huge I can't get the laptop (and it's keyboard) anywhere near it.

Also, OS X doesn't give you the choice about what to do with your laptop monitor when plugged into an external display. I'd just as soon shut it off but no ... it has to be a desktop extension. Once I get a little more settled in, I'll need to pick up one of those "Hacks" books.

So, exciting and scary. I have so many things to learn.

Thursday, November 02, 2006

Improve Tapestry performance with tapestry-prop

I've just deployed a new version of the tapestry-prop library for Tapestry 4.0. tapestry-prop adds a new binding prefix, "prop:", as an alternative to "ognl:". The actual moving of property values is accomplished using runtime generated bytecode, which performs better than OGNL's use of reflection.

The major new feature is support for property paths. You may now uses a series of property names, i.e., user.name. This vastly extends the usefulness of the "prop:" binding, since is can be used in about 90% of the places you'd normally use "ognl:".

The lack of reflection means that "prop:" peforms almost exactly the same as pure Java code. Based on some cursory performance testing, we're looking at a 27x improvement. In a typical Tapestry page, I suspect this may add up to a millisecond or so per request.

The version number has been changed to 1.0.0-snapshot. tapestry-prop should work with JDK 1.3 (Tapestry's minimum requirement).

If you were paying attention during the screencast, you'll note that an even more powerful version of this code is the default binding prefix in Tapestry 5.

Wednesday, November 01, 2006

Tapestry -- Duke's Choice Photos


Duke's Choice Lineup, originally uploaded by Tapestry Dude.

Finally got around to uploading some shots taken at JavaOne 2006. Here's a slideshow from the Duke's Choice award ceremony.

Friday, October 27, 2006

A step back from the bleeding edge

In an earlier post, I blogged about my gamble with using AspectJ. Well, I've been having Eclipse performance and stability problems for a while now, as I've upgraded Eclipse from 3.1.x to 3.2 to 3.2.1 and kept updating my AspectJ plugins. I've had issues with Maven and AspectJ, especially with Corbetura and AspectJ.

For what? Some clever defensive programming ideas (automatic null checking of parameters), a little bit of concurrency help, and a few additional cases.

Much as I liked the automatic null checking, I found increasingly that I was supressing it (with a special annotation) in many cases. In fact, the most useful place for such checking is in bridge code between user application code and the public Tapestry API ... and that's really not a lot of code.

The concurrency support was nice, but expensive. I found that in most cases I could convert to using copy-on-write, threadsafe collections instead. For the couple of cases where I couldn't I put together a utility class to manage the reentrant read/write lock on behalf of other code.

So ... goodbye AspectJ (for the meantime). Positive improvements: shrunk the size of the JAR by about 30% (657K down to 449K), plus no runtime dependency on AspectJ. Everything's running great. No problems.

Dissapointing. AspectJ still has a raft of very important uses, but the cost (in terms of developer frustrations) outweighs the benefits, at least for nice clean, new code like the Tapestry 5 code base.

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.

Maven Thoughts

As much as I disliked Maven 1 , I've come to enjoy and rely on Maven 2.

It's getting things done for me. It's fast. The Maven plugin for Eclipse (that is, an Eclipse Plugin that support Maven, rather than the Maven plugin that generates Eclipse control files) seems to work well, automatically picking up changes to my pom.xml, as well as automatically downloading dependencies. And I'm using it in a tough way, given that I'm also using AJDT on the same projects.

I also think that the new "almost plain text" format is a godsend; it makes it much eaiser to quickly assemble good documentation. I'm trying to write documentation before writing code for Tapestry 5.

Still, maybe 20% of the time, I don't feel that I'm using a tool so much as appeasing a petty god. There's still a number of things I can do easily in Ant that seem to require writing Maven Mojos and plugins to do in Maven.

Sunday, July 02, 2006

Synchronization Costs

I've been doing a bit of work on the Tapestry 5 code base. I'm really interested in making Tapestry 5 screaming fast, and since the code is based on JDK 1.5, we can use concurrency support. Previously, I've blogged about using an aspect to enforce read and write locks. I decided to write a simple benchmark to see what the relative costs were.

As with any benchmark, its only an approximation. I tried enough tricks to ensure that Hotspot wouldn't get in there and over optimize things, but you can never tell. HotSpot is a devious piece of software.

I got interesting, and strange, results:

For a base line, I executed the code with no synchronization whatsoever (simple). The cost of synchronization (synched) shows that synchronization is pretty darn cheap, just an increment on top of the baseline code. The aspect graph shows the cost of using the @Synchronized aspect to maintain a reentrant read/write lock (that is, shared read lock combined with an exclusive write lock). Finally, the rw graph shows the cost of writing code that maintain the read/write lock in normal code (rather than having it added via the aspect).

Synchronization has some overhead. Using the @Synchronization aspect is about 4x as expensive as just using the synchronized keyword on a method. Strangely, the aspect version operates faster than the pure code version for reasons I can't explain, except it must have something to do with how AspectJ weaves my code (a lot of code I write ends up as private static methods after weaving, which may have some runtime performance advantage).

These results demonstrate an important tradeoff: if your application only occasionally has multiple threads hitting the same methods, then you might want to choose synchronized, since you aren't in danger of serializing your threads. By serializing, we mean that only one thread is running and all other threads are blocked, waiting for that thread to complete a synchronized block. Serialized threads is what causes throughput for a web site to be bad, even though the CPU isn't maxed out ... it's basically, Moe, Larry and Curly fighting to get through a single, narrow door all at the same time (they race to claim the single, exclusive lock).

Tapestry, on the other hand, will have a number of choke points where many threads will try to simultaneously access the same resource (without modifying it). In those cases, a shared read lock (with the occasional exclusive write lock) costs a little more per thread, but allows multiple threads to operate simultaneously ... and that leads to much higher throughput. Here, Moe, Larry and Curly get to walk through their own individual doors (that is, each of them has a non-exclusive read lock of their own).

As with any benchmark, my little test bench is far, far from a simulation of real life. But I think I can continue to make use of @Synchronized without worrying about tanking the application. In fact, just as I predicted Tapestry 4 would out-perform Tapestry 3, I believe Tapestry 5 will out perform Tapestry 4, by at least as much.

Thursday, June 29, 2006

Chris Nelson talks to TSS about Trails

Chris Nelson's interview with TheServerSide has been published. In the interview he talks about Trails, Rails and Tapestry. The interview is full video here. Alas, viewing the interview requires an install of the obnoxious RealPlayer.

Wednesday, June 28, 2006

Ajaxified Javadoc with Tapestry

Just found out about www.javaref.com which is a single site that contains JavaDoc for over 80 open source frameworks. The site itself is built on Tapestry 3, Tomcat and MySQL and uses a lot of slick Ajax effects, including an update-in-place data grid and nice tab views, plus a few kinds of "spring loaded" dialogs. It's always gratifying to see people do something really nice and really slick (and really useful) with Tapestry.

Tuesday, June 20, 2006

Beyond domain specific languages

Most of my friends, growing up in the late 70's and early 80's, have played Zork or some other kind of interactive fiction.

Although the earliest adventures used just a simple VERB NOUN syntax, the Infocom games had much more powerful parsers that could understand more complete and natural inputs such as "take all the rings but the silver one" or "put the red battery inside the lantern".

In high school (say, around 1982), I wrote a very stupid, very simple adventure game, "Obelisk Adventure", in PDP-11 Basic. It had pointless quests, a maze with a big nasty rat (the maze wasn't mappable, because it randomly moved your stuff around). I think I also remember an endless hallway (poorly implemented). I also implemented a few similar adventures on my home computer (first a Challenger 1P, later an Atari 800). This was my claim to fame for a while, as pathetic as it appears in retrospect.

These games didn't go away after Infocom and the other publishers disbanded due to the onslaught of more graphic games (such as Castle Wolfenstein and, eventually, Doom and Quake). The underlying virtual machine was reverse engineered and an open source community grew up around virtual machine implementations, as well as languages and tools that compiled down to that machine: Graham Nelson's Inform.

The Inform language is a mix of general purpose features, and a lot of very domain specific stuff -- specific to the idea of simulating a world of rooms, objects and actors, and specific to the hooks of the natural language parser. Here's some Inform 6 source, stolen from the Wikipedia entry:

Constant Story "HELLO WIKIPEDIA";
Constant Headline "^An Interactive Example^";

Include "Parser";
Include "VerbLib";

[ Initialise;
    location = Living_Room;
    "Hello World";
];

Object Kitchen "Kitchen";
Object Front_Door "Front Door";

Object Living_Room "Living Room"
    with
        description "A comfortably furnished living room.",
        n_to Kitchen,
        s_to Front_Door,
    has light;

Object -> Salesman "insurance salesman"
    with
        name 'insurance' 'salesman' 'man',
        description "An insurance salesman in a tacky polyester 
              suit.  He seems eager to speak to you.",
        before [;
            Listen:
                move Insurance_Paperwork to player;
                "The salesman bores you with a discussion
                 of life insurance policies.  From his
                 briefcase he pulls some paperwork which he
                 hands to you.";
        ],
    has animate;

Object -> -> Briefcase "briefcase"
    with
        name 'briefcase' 'case',
        description "A slightly worn, black briefcase.",
    has container;

Object -> -> -> Insurance_Paperwork "insurance paperwork"
    with
        name 'paperwork' 'papers' 'insurance' 'documents' 'forms',
        description "Page after page of small legalese.";

Include "Grammar";

And you can do a lot with that language, and I've occasionally looked into trying my hand at it. But it never seemed like a good use of my time (if I'm not working on Tapestry, I try to find things that get me away from my computer, and preferably, out of my house).

... so I was stunned and amazed when I saw the new version of Inform, Inform 7. The language above is gone and replaced with a natural language of tremendous power and flexibility. The same example, also off Wikipedia, in the new language:

"HELLO WIKIPEDIA" by A Wikipedia Contributor

The story headline is "An Interactive Example".

The Living Room is a room. "A comfortably furnished living room." The Kitchen is
north of the Living Room. The Front Door is south of the Living Room.

The insurance salesman is a man in the Living Room. The description is "An insurance
salesman in a tacky polyester suit. He seems eager to speak to you." Understand "man"
as the insurance salesman.

A briefcase is carried by the insurance salesman. The description is "A slightly
worn, black briefcase." Understand "case" as the briefcase.

The insurance paperwork is in the briefcase. The description is "Page after page of
small legalese." Understand "papers" or "documents" or "forms" as the paperwork.

Instead of listening to the insurance salesman:
    say "The salesman bores you with a discussion of life insurance policies. 
    From his briefcase he pulls some paperwork which he hands to you.";
    now the player carries the insurance paperwork.

Graham's goal was to allow non-programmers to write interactive fiction. The "code" is readable by human and compiler alike. Many of the examples in the documentation maintain this look, even when doing complex things like adding new verbs to the grammar (though some of the advanced examples are less natural language, and occasionally some Inform 6 code is injected as well).

What's important is that is not COBOL. It's not complex and rigid and verbose ... it is, in fact, very concise and the parser is very, very flexible and adaptive to the user. For example, you can easily talk about related nouns in a pretty random order. By editting the source, you can discuss the briefcase before describing the insurance salesman ... I tried this, juggling the order of the paragraphs in the example and the game still compiled and worked identically.

It also has a lot of cool features, such as instead of reading the letter the third time, say "Re-reading the letter isn't going to change what it says." The language is expressive enough to understand lots of dependent information, such as actions occuring in sequence, related to the "game clock", or repetition. Much of the game logic takes the form of "before" and "instead of" rules around normal actions, built into the language (and its standard library).

This language is partnered with a special purpose IDE and reams of excellent, hyperlinked, integrated documentation and examples. Further, the IDE has built in regression testing capabilities (you can play a game within the IDE and record the responses, then later play back the game after changing the source). The IDE also generates maps and indexes of the game.

Sure, he's been working on this for a long time (Inform 6 is nearly ten years old). But the possibilities of this, taken out of this specific domain, are impressive. Imagine writing complex behaviors in succinct natural language instead of verbose XML. I'm normally against MDA approaches, but I think this kind of natural language deserves a careful assessment to see where it can be used within the enterprise.

Saturday, June 10, 2006

Teamwork Live --- Powered by Tapestry

And yet another very nice Tapestry application: TeamWork Live. This was created in San Francisco by CollectiveSoft and is meant to be a competitor to Basecamp.

Friday, June 09, 2006

Vaisala StrikeNet online

The "little" project that's taken up a lot of my time over the last year, Vaisala StrikeNet is now online. Vaisala is my client in Tucson, AZ that I've occasionally mentioned. It's a bit of a niche application (Vaisala tracks lightning strikes across North America, mostly for use by the insurance industry) but it is written using Tapestry 4 ... in fact, I made improvements to Tapestry 4 based on "rough edges" I found while coding this application.

Monday, June 05, 2006

Whoa ... Spring doesn't lazily instantiate beans?

Just stumbled across a blog about Lazy Bean instantiation in Spring 2.0. This is kind of funny to me ... lazy instantiation is so important, so part of the base line of IoC container functionality, that I just assumed Spring already did this.

Update: Spring has had lazy instantiation since at least 1.2, but it isn't on by default.

For the record: this is one of the essential services provided by HiveMind since day 1. HiveMind lazily instantiates everything it can, and is smart, using a pair of proxies for efficiency.

Why a pair? For threading efficiency. The outer proxy is visible to the world and delegates (initially) to the inner proxy. The inner proxy has its methods synchronized as it is responsible for thread-safe lazy instantiation. Once the service (and its interceptors) are instantiated, the inner proxy replaces itself inside the outer proxy. That is, once the service is instantiated, the outer proxy delegates directly to the service implementation, and the inner proxy is no longer needed (it is released to the garbage collector).

Doing things this way is a great idea, but not my great idea. It was suggested a long ways back by someone in the HiveMind community. I'd like to extend proper credit on this, but my memory is weak.

Using double proxies is a very powerful technique, since it ensures thread-safe, just-in-time instantiation of the service, without paying the cost of synchronizing every method in the outer proxy. It's an example of what an IoC container buys you ... I don't think you'd want to code three implementations of every service interface manually, but if you skimp, you undercut the performance and scalability of your app. Using HiveMind, two of the three implementations (the outer and inner proxies) are just provided for you.

So, that's a new answer to one of the more frequent questions posed to me: Why does Tapestry use HiveMind and not Spring?. A: Spring does not provide all the capabilities that Tapestry requires, such as lazy instantiation. Tapestry consists of about 200 services, but very few of those are needed at startup, and a percentage will not be used in most applications. Lazy instantation is a huge win for Tapestry.

Futher, HiveMind's approach to lazy instantiation and proxies means that you can have mutually dependant services ... services that are injected into each other. The proxies, and the lazy instantiaton, means that you bypass the normal chicken-and-the-egg problem of which service to instantiate first. I've used this technique to break untestable, monolithic code into two halves that can each be properly unit tested.

Back to Spring ... now that it looks like Spring 2.0 has lazy instantiation, I have to question the fact that it defaults to OFF.

Sunday, June 04, 2006

And we're back ...

Just got back from three weeks away from everything: first a week at JavaOne, where Tapestry received a Duke's Choice Award in the open source category. From there, it was two weeks on the island of Hawaii (the "big island" ... it's about the size of Connecticut). That was two weeks where I had virtually no internet connection (certainly not at the cottage I was staying at) and really, really didn't even think about Tapestry. Instead, it's been horseback riding, hiking, snorkeling and boogie boarding. Sun, surf and sand. That's quite a change, and a good break (normally, I close my eyes and start seeing code). This was good for me, I'm feeling just a little less frantic than before ... I've been doing too much for so long that I've forgotten what it means to relax. I'm literally tan, fit and well-rested.

I'm also pretty much done with my work at Vaisala, with that app going live in a couple of days. I'm looking forward to a chance to focus on Tapestry full time for a bit, I have code for Tapestry 5 to write, and updates to labs and examples and documentation, new sessions to write (such as for OSCON 2006). Meanwhile, Jesse and Brian have been kicking ass on Tapestry 4 and Tapestry 3, and Geoff looks ready to get Spindle upgraded to Tapestry 4. So there's a lot going on.

I'm also fired up to get people's attention on Tapestry. We need more articles and more evangelism and more books on Tapestry and I'll have more time to spearhead and motivate people on those lines. I'm already beginning to think of 2006 as the Summer of Tapestry.

Tuesday, May 16, 2006

Tapestry: Duke's Choice!

In something that was a bit of a surprise, even to me, Tapestry won a Duke's Choice award for innovation at this years JavaOne.

They gave me a big, wacky Duke statuette (photos to follow). I mean, I'm going to have to hire a pack mule to get it back to Portland. I also had a chance to chat with James Gosling about Java. I complimented him on the language and told him how much fun I'm having with it, which he seemed to like. On the other hand ... when I mentioned wanting "all the goodness of Java, but a little less typing", ala Ruby, he went a little cold. I didn't realize until this moment that he might have thought I was referring to object typing (an issue for another day) when I was, in fact, talking about keystrokes!

Meanwhile, I had to drop off the statuette in the room, so I've probably missed the Java bloggers meetup at the Thirsty Bear. We'll see if I can catch the end of that.

Been having a lot of fun so far ... maybe tomorrow, I'll even make it to a session!

Monday, May 08, 2006

NFJS Meltdown

I had a "perfect storm" of technical problems at yesterday's No Fluff Just Stuff which, sadly, scared away part of the audience. I had just re-installed Eclipse on my laptop, reorganizing my plugins into multiple extension folders (so make my eventual upgrade to Eclipse 3.2 easier) and I inadventently installed a new version of the Eclipse Jetty Launcher plugin, version 1.4, that is not compatible with Eclipse 3.1, just with 3.2. Ouch!

So, a lot of fumbling there that got in the way, took up time, and put me off my game. But people were still very interested and involved, with good questions. I have to think about the JavaScript stuff though ... it may just be too much for people who haven't seen Tapestry before.

See folks at JavaOne!

Tuesday, May 02, 2006

Synergy

So I find myself with a crowded desk: one laptop, a mouse for the laptop, a keyboard, mouse, and two LCDs for my desktop. It's getting crowded, and it gets annoying to switch between different keyboards.

Worse, I do all my Thunderbird email just off the laptop (though I use GMail on either or both). That's a problem when I need to copy text from the desktop to the laptop so I can mail it.

Solution: Synergy -- an open source tool that links my computers together.

Synergy allows me to use my desktop keyboard and mouse on both systems. The mouse travels off the left side of my desktop screen onto the right side of my laptop screen. Focus follows mouse, at least in terms of system-to-system, so I can type on one system (say, the desktop), move the cursor over to the laptop, and type into the active window there (without clicking). Synergy even updates window decorations.

Better yet ... Synergy supports cut-and-paste of text between the two systems. So I can copy text out of my browser on the desktop and paste it into compose window on the laptop. I had thought about using a KVM switch at one time, and this kicks its ass! In fact, had I discovered this a few weeks ago, I might not have bought a mouse for the laptop.

Not an issue for me, but it's also cross-platform. It supports Windows, Linux and (with some problems) Mac OS X.

Monday, May 01, 2006

Tapestry 5 Class Reloading

One of the big advantages of the scripting approaches to Web development (including JavaServer Pages at one extreme, and Ruby on Rails at the other) is the velocity of change: you can change a source file (a JSP, a Rails template, or even a Rails class) and see the change immediately.

Tapestry has traditionally been more aggressive at caching. Once a template or a XML file has been loaded into memory, it's stuck there. During development, you can use the -Dorg.apache.tapestry.disable-caching=true trick to turn caching off, but this not only slows down every request, but includes a memory leak that will eventually tank the application. It also does not reload classes the way it reloads templates and XML files.

In my quest to create a Java web development environment as compelling as Rails, one of the first things to tackle is this aspect of development: effective and efficient reloading of component classes.

The first pass at that code is now in the repository. Component classes are loaded in their own class loader (this is also necessary for the Tapestry AOP code that transforms classes as they are loaded into memory). When any transformed classes are changed externally, the class loader, as well as all instances derived from those classes, are discarded and a new class loader is created.

This will work quite well, far better than Tapestry 4's development mode. In production, you'll just throttle down how often Tapestry checks to see if underlying files have changed.

What's interesting is that I've been working, off and on, on this code base since mid-February. This compares to how long I spent on the initial prototype of Tapestry way back in January 2000, where I spent a couple of weeks of furious coding just to get to a simple "Hello World". In those couple of weeks, I "invested" Tapestry with many of the features that would grow with it, and support that growth, over the intervening years: the component hiearchy, the original and primitive form of templates, component parameter bindings, page loading, external XML page and component specifications, and other things long forgotten. To me, it was like winding up a spring ... all that upfront effort paid off with a basic design that was "right enough" to keep Tapestry innovative for many years.

The reason I'm starting with a new code base for Tapestry 5 is that it's time to wind up that spring again. The couple of design errors, only apparent after the fact, in Tapestry 4 are all but insurmountable without a fresh start (this primarily includes the requirement to extend base classes, but there are many more subtle things that need addressing). Further, the fact that I've spent a chunk of time getting the basic class loading and AOP support in place, the fact that anything web application is still weeks or months off, is actually a Good Thing. The spring is going to be wound nice and tight, full of energy for years to come.

Wednesday, April 26, 2006

Gambling on AspectJ

Squeezed in with everything else that's going on (the end of one client project and a lot of personal and business travel), I've been working on the start of the code base that will be Tapestry 5.

I think it is going to be a monster. In a good way. More on that later.

Tapestry 5 is targetted at JDK 1.5 and above. That means annotations, and not XML, will be the rule of the day. JDK 1.5 also means the use of Doug Lea's concurrency support, now java.util.concurrent.

Further, this is a chance for me to dip my toes in the Aspect Oriented pool.

I can picture the reaction: "Howard, that's the wrong 'A'! AOP is so ... 2004? Shouldn't you be focused on Ajax?"

However, the AOP support actually aligns nicely with much that I've done with HiveMind, using Javassist. It's been interesting to see the kind of things you can do statically, at build time, using AspectJ and how they compare to what you can do dynamically, at runtime, using HiveMind. Each approach has merit in different circumstances, and I'm trying to find out exactly where the lines of demarkation are.

For example, I've been doing a lot of defensive programming. So I've been creating aspects to help with that. It's been relatively easy to write aspects that inserts null check code into each non-private method. I like the don't accept null, don't return null coding approach, and my aspect allows me to ensure that all my code works that way. Further, for the rare cases where I want to allow null, I can place a @SuppressNullCheck annotation on a class or individual method.

I could do the same thing using HiveMind ... but I'd really be restricted to applying it to HiveMind services, since it would take the form of an interceptor. Tapestry tends to have lots of data and modeling objects in memory, that would benefit from these checks as much, or more so, than the service code. AspectJ is a better fit there.

I'm also doing some interesting work to use annotations to control concurrent read/write access to my classes.

I'm finding the mix of aspects and annotations to be very powerful. I use type annotations to narrow the set of classes to be affected by the aspect, and use method annotations to include or exclude individual methods. In my case, I have a @Synchronized annotation that indicates a class can have some of its method synchronized, and I have @Synchronized.Read and @Synchronized.Write to control which methods require a read lock, and which require the exclusive write lock.

I'm pretty happy from the point of view of getting more functionality (and, more robust functionality) from less code. Aspects are another approach to doing things that I normally associate with components and layering. The gamble part is how I'll feel once I'm trying debug into and through AspectJ-generated code. It becomes a bit tricky to figure out exactly what's going on once you've let AspectJ work things over.

I've also found that AspectJ really wants to work at the method level, when some of the things I want to do (such as validating individual parameters) are done at the individual parameter level. For example, I'd like to create method advice as the combination of a particular method and a particular non-primitive parameter of that method ... but the best I can do is advise the entire method and recieve all the parameters as an object array (whether they are primitive or not).

I have found the AJDT plugins for Eclipse to be a slight bit creeky. You definately want to upgrade your Eclipse to 3.1.2 (the latest and greatest). The visualization is great (when it works), as is the visual annotations of advised methods. There are numerous annoyances when editting aspects and browsing source, and I often have to perform clean builds of my code, something I hardly ever do when not using AJDT.

Thursday, April 20, 2006

Update on Tapestry @ OSCON

Earlier I had blogged about my frustration at not getting to speak at OSCON 2006. Well, word of my little outburst on this blog has trickled out to the key folks at O'Reilly and now both of my sessions are back in. They apologized for the lack of depth in the Java track, citing technical and procedural problems with that aspect of the conference (made that much worse by Daniel Steinberg's personal tragedy this year). They are working to make amends by inviting several prominent Java speakers to fill the gaps.

I'll have two sessions this year:

Wed 10:45 8588 - Metaprogramming Java with HiveMind and Javassist
Wed 2:35 8904 - Building Java Web Applications with Tapestry

This is great news. OSCON is a fun convention and it's about two miles from where I live. Maybe I'll get to ride the Segway again!

Wednesday, April 12, 2006

Java isn't Open-Sourcey Enough for O'Reilly

Let's just break the cardinal rule: don't blog when you are upset.

Both my sessions for this year's OSCON were rejected. Not just an improved, updated and Ajax-ey Building Web Applications with Tapestry, but a very differently scoped Metaprogramming Java with HiveMind and Javassist.

For O'Reilly ... if it doesn't say Ruby, it's not open source enough! This is doubly dissapointing, because last year's session went well (and turned out to be lucrative), and OSCON is held in Portland, OR ... just 10 minutes away by MAX.

Wednesday, April 05, 2006

Tapestry @ JavaOne Sessions

There are three sessions at JavaOne this year that will be of interest to the Tapestry community. All three will be held in Esplanade 307-310 (which was, I believe, where last year's Web Frameworks Smackdown took place).

Wed 17th 4pm RAD Frameworks Web Tier
Wed 17th 7:30pm The Component Edge: Creating and Using Components With Tapestry (BOF)
Thu 18th 10:30pm Trails BOF

I'm only involved in the Tapestry BOF; Chris Nelson (creator of Trails) will be presenting at the other two sessions.

I'll be arriving in San Francisco on the 14th and departing on the 19th. See everyone then!

Tuesday, April 04, 2006

Dissing Subversion is a bad call

Elliotte Rusty Harold:

The server side of Subversion needs a serious rethink, though. Currently you almost have to have a dedicated Subversion server and a fulltime Subversion administrator. That end needs to get about one thousand times simpler.

Howard Lewis Ship: Huh? I've had exactly one problem with Subversion in over a year of heavy use, that was solved entirely by upgrading to the latest recommended version of the server. You start it up, do a minimal configuration (a passwd file), and let it run. I also use Subclipse with repositories on javaforge.com and on apache.org. I've almost never used the svn command line, either.

Kind of dissapointing that ERH would bash a really great product like Subversion without even bothering to justify his erroneous conclusions. But he's bashed other cool software before.

Monday, April 03, 2006

tapestry-testng

This is another small library at Tapestry @ JavaForge. Unlike many of the others, this library is used during testing, not during deployment.

If you are used to using EasyMock 1.1 in combination with the HiveMindTestCase base class, then much of this library will be familiar ... except that it uses EasyMock 2.0, TestNG, and a lot of JDK 1.5 generics and annotations to keep the code short and simple.

As with the rest of Tapestry @ JavaForge, this stuff is still very alpha ... but also very useful.

Saturday, April 01, 2006

New Tapestry bug fix releases out!

Two new bug fix releases of Tapestry are now available: Tapestry 3.0.4 and Tapestry 4.0.1. Not only did tons of work happen, including documentation fixes and other great stuff but ... I didn't do any of it! I pretty much haven't updated my own workspace since Tapestry 4.0 was released ... I've been working on new demo apps, new side projects at Tapestry @ JavaForge, some prototypes of Tapestry 5, moving Tapestry to a TLP, as well as client project work and my No Fluff Just Stuff presentations. But I haven't been working on the Tapestry code base itself.

The system works! If there was any doubt that Tapestry is more, much more, than Howard's pet project, the energy and vitality that Jesse Kuhnert, Brian K. Wallace, and the many contributors from the community have demonstrated should put an end to that doubt.

I can now, officially, walk in front of a bus. Tapestry will continue.

Wednesday, March 29, 2006

Cognition: Tapestry 4 + Hibernate + Spring + Eclipse

Cognition is a new open source product similar in spirit to Trails. Both are approaches using Tapestry and Hibernate to streamline the process of creating a "typical" web application. Trails is very domain driven, creating the database schema from the object model. Cognition includes graphical editors inside Eclipse to build the schema, and derives the object model from the schema. There's a really impressive viewlet showing how to create a simple app really, really fast.

I can't wait for some time to open up in my schedule so I can give Rails, Trails and Cognition a spin.

Tuesday, March 28, 2006

HTML A tag vs. POST

Just an observation. If it is so damned important that GET operations be non-state changing and idempotent (blah blah caching, design of HTTP, firewalls, etc.) ... if it is so damned important that you never change anything expect using POST (and then send a redirect afterwards) ... tell me why the <a> tag does not support a METHOD attribute, the way <form> does. I mean, think back a few years, at all the crazy tags Netscape and Microsoft put into their browsers, and they miss this? Still?

I mean, this hasn't changed in years, and has consistently held web development hostage to the use of JavaScript to keep any non-trivial page from featuring a big old submit button. Apparently, the Powers That Be are afraid that without a big old submit button, we won't realize that hitting the "delete" key is going to delete something.

Sure JavaScript is all pervasive now, and you can force a form to submit using JavaScript ... and the use of Ajax will eventually mean that any non-trivial operation will be coded using XmlHttpRequest (which will do a post). Someday. For users with JavaScript enabled. But I just want to be able to write:

<a href=". . ." method="post">delete this report</a>

Monday, March 27, 2006

Tapestry Support Network Live Again

Woops. I accidentally let the domain registration for the Tapestry Support Network site lapse while I was on the road. All fixed now! Time to put some new content up there.

Sunday, March 26, 2006

New version of tapestry-spring

I've built out a new and improved version of tapestry-spring yesterday and today. This is a tiny module that properly initializes Tapestry to allow Spring beans to be injected into Tapestry pages.

This one works first off! I now run an integration test to prove this; it starts up a Jetty instance and injects a Spring bean into a Tapestry page.

More importantly, this version addresses the prototype issue. Using the standard @InjectObject annotation messes up when the bean being injected is not a singleton. Non-singleton Spring beans, aka prototypes, must be re-acquired from the BeanFactory in order to get the correct, new version.

There's now an @InjectSpring annotation that does that; reading the property will, beneath the covers, always go back to the BeanFactory to obtain a fresh copy. No caching at any layer, no problems.

I just realized that I forgot to update the documentation; that will come shortly.

I also bumped the version number up to 0.1.2 to celebrate the fact that this code works. But it is still alpha quality!

I did some trickery (I believe) so that the non-annotation classes are compiled for JDK 1.3, and the annotation classes are compiled for JDK 1.5. Took a bit of a struggle with Maven, but it works. If you aren't using annotations, you need to use a bit of XML:

<inject property="myProperty" type="spring" object="mySpringBean"/>

Static imports and Java language evolution

It's interesting to see the differences between how you expect to use a new language feature and how you end up really using it.

For example, of all the new features in 1.5, the idea of static imports seemed to be the least useful. From the outside, it looks like you get to say "min()" rather than "Math.min()". Big woop.

But what I've found when coding using static imports is a new clarity to my code. Once you strip away the class names from static methods the part that's left starts looking a lot like new and extensible language keywords.

For example, here's a test case from some code I'm working on.

    @Test
    public void test() throws Exception
    {
        Method m = ComponentFixture.class.getMethod("getSpringBean");

        EnhancementOperation op = newMock(EnhancementOperation.class);
        IComponentSpecification spec = newMock(IComponentSpecification.class);
        Location l = newMock(Location.class);
        Capturer c = newCapturer(InjectSpecification.class);

        spec.addInjectSpecification(capture(c));

        replay();

        new InjectSpringAnnotationWorker().performEnhancement(op, spec, m, l);

        verify();

        InjectSpecification is = c.getCaptured();

        assertEquals(is.getObject(), "someBeanName");
        assertEquals(is.getProperty(), "springBean");
        assertEquals(is.getType(), "spring");
        assertSame(is.getLocation(), l);
    }

What I've found as I've been coding is that I make extensive use of static imports. First of all, Eclipse makes it easy to do (Ctrl-Shift-M on top of a static method converts it to a static import). Secondly, my personal coding style has been to use a goodly number of static methods (if I can define a new feature in terms of the existing public API of an object, and I don't expect there to be a need for subclasses to override, then I create a static method that takes the object as a parameter).

Those assert calls at the end are actually static methods on an Assert class. Some of the other methods, newCapture() and capture(), are also static. The newMock() calls are inherited from a base class. It actually looks better inside Eclipse, because the static methods are italicized.

My point is ... the code is looking more expressive to me, as some clutter falls away. Autoboxing reduces some more clutter. So does the new for loop, and variable argument lists. Generics are still something of a wash (you get more self-describing code by adding lots of clutter, but you gain by removing explicit casts). The end result follows my motto, though: Less is More.

Has this transformed the language? No, and perhaps that's the point. It's some incremental change, but it's makes for some small steps in the right direction. It's not quite the fluid flow of static class methods in Ruby (used for all kinds of meta-programming), but it's still nice.

On the other hand, Sun is much less aggresive in adopting changes to Java than, say, Microsoft. I hate to say it, but innovation (where innovation is defined as adopting features that other languages have had for a decade or so) seems to be occuring in C# before Java. In some cases, it is occuring in other languages that execute on the Java VM. The end result is the same sad story of Sun chasing Microsoft's tail.

I'd like to see some real improvements to the language. I want Ruby blocks and continuations, C# autotype variables, built-in Aspect support and, hey, maybe something I don't know about that will make my programming life better (maybe a pervasive hot class reloader). In this respect, Java always feels like a perpetual bridesmaid, never a bride.

Saturday, March 25, 2006

NFJS Examples available

I've now been able to build and deploy the examples from my talks at No Fluff Just Stuff. The version number is still 0.0.1 and probably will be for some time. You can obtain a pre-compiled WAR, or a source distribution (with out without the PowerPoint slides) from http://howardlewisship.com/repository/com/howardlewisship/nfjs-tapestry/0.0.1/.

Huzzah! Subversion woes fixed.

I was just starting to panic. I was unable to check out my NFJS example code onto my desktop (it's stored using SVN, using my laptop as the SVN server). I've had some problems with Subversion recently, but thought I had resolved them.

From Eclipse, and from the command line, I was getting a wierd error:

bash-3.00$ svn update
svn: Can't read from connection: Software caused connection abort

From what I can tell, this occured when pulling down some large PowerPoint presentation files. Of course, there's no way to determine exactly what file the failure occured on, or why.

No output in the Subversion.log, or the svnserve command line, or any useful output from svn command line (or the Subclipse tool). Feedback anyone?

I tried updating to the latest Subclipse, 0.9.108. No help.

Finally, I tried upgrading my SVN server to 1.3.0 and ... things are working again!. Supposedly, I could have stayed with a Berkley DB repository using 1.3.0, but I've gone through the work of converting to a file system repository (as one of my attempts to resolve this problem), and I can't see the advantage of moving back. In fact, if anything, SVN seems faster or more responsive using the file system.

I should finally be able to get those NFJS examples up on the web site.

Friday, March 24, 2006

Back from the road! / tapestry-spring plans

Huzzah! Watch out Portland ... I'm back.

I actually had an exit row and room to work on the flight back from Phoenix. I decided to take another look at tapestry-spring.

First off, please remember that it is alpha code! So the fact that it completely doesn't work shouldn't be a total surprise. I had packaged the critical hivemodule.xml file under WEB-INF, not META-INF. Chalk it up to force of habit ... that's what you do for Tapestry applications, but this is a standalone library.

The reason I didn't catch that in my integration tests was ... I'm a lazy, lazy man. I need to learn how to write proper integration tests for tapestry-spring using Maven. I've actually had some good luck starting up Jetty inside a (TestNG) test case and that's the likely course I'll pursue.

So, that's half the story. The other half is dealing with Spring's prototype beans. This was someting that completely surprised me about Spring, once people started to complain. If you define a bean as having the prototype lifecycle, it means that you get a new instance everytime you ask the BeanFactory for it.

I really dislike that approach, because it puts the client code in charge of managing the lifecycle of the bean.

By contrast, HiveMind covers similar functionality quite differently. When you get a service from the Registry (the equivalent of getting a bean from the BeanFactory), you are going to get a proxy. Same service interface, different implementation (brewed up on the spot). The lifecycle of the service is hidden inside this proxy. You can share this proxy around, pass it between threads, even serialize and deserialize it (I think ... there's a few issues around that) and it just works.

The rough equivalent of Spring's non-singleton/prototype beans are HiveMind services with the threaded lifecycle. When you invoke a method on the proxy, it finds or creates a per-thread instance of the service. The per-thread instance is cleaned up and discarded at the end of the request (it's effectively built around a servlet request/response cycle).

This is used all over the place inside Tapestry. What's nice is that a piece of code can treat a proxy to a normal service, and a proxy to a per-thread service identically. That concern (in the AOP sense) is buried inside the proxy. Makes testing easier too.

Now, back to Tapestry's @InjectObject annotation. It's just broken for Spring prototype beans, because it's built around HiveMind's design, not Spring's. So it gets the object out of HiveMind just once and holds onto it, passing it into pages and components via a constructor (this is part of Tapestry's enhanced subclass approach to AOP).

For prototype beans, that means that we get the bean just once per component, and are stuck with it for any instances of that component we instantiate. The same bean instance, once aquired from Spring, will be passed into each new instance of a given page or component.

So ... I'm expanding tapestry-spring to add a specialized annotation, just for Spring. @InjectSpring will create a synthetic getter method that always gets a fresh instance of the named bean from the BeanFactory. That should solve the problem in its entirety.

I just need to stuggle with Maven a little bit more; I want to create a single JAR, where part of the code is compiled with JDK 1.3, but the annotation part is compiled with JDK 1.5. I think I can do this, by having multiple source roots (so src/main and src/annotations) and some extra compile executions.

Ultimately, this code may move under the Tapestry top-level project at Apache, once that gets set up. In the meantime, living at JavaForge is perfectly ok.

But, in the meantime, I need to catch up on the new Doctor Who, waiting for me on my Tivo. I've been out of town that long.

Sunday, March 19, 2006

NFJS code and slides ... just a little longer

It turns out I won't be able to post the updated slides and code from my No Fluff Just Stuff sessions because I left a critical password behind on my desktop. I'm on the road still (a long trip!) and will see about getting this stuff in place this coming weekend. Sorry for any inconvienience.

Friday, March 17, 2006

Zillow.com on The Boston Globe

Just noticed an article in The Boston Globe about Zillow.com. Zillow is a real-estate property values search application; at its core is a Flash application, but all the necessary infrastructure around that is Tapestry 4. I'm sure Ben and Dion are wondering why it's Flash and not Ajax.

Bitten by IE once again

If there was ever a single piece of software I hated with a passion, it would be Internet Explorer. I had let my seething resentment for this abomination settle for a while, as I do all my browing and development using FireFox.

However, with my project developer hat on, I have to support IE. My client, of course, is interested in IE support (given their user base, IE use will likely predominate). On just one page of my application, we hit the Operation Aborted error. The symptoms of this is that a page paritially loads, then a modal popup announces "Operation Aborted", and when you click OK, you are sent to the "This page cannot be displayed" page. I'm still tracking down the documentation, but it appears to be about a race condition where JavaScript modifies the DOM before IE is ready for it.

Now, I would have thought Tapestry would be safe from this, because of its approach to JavaScript ... the fact that JavaScript goes in proper places, at the top of the document, or at the bottom, rather than scattered throughout the document.

Doesn't matter; this page uses a DatePicker component, nested within a few tables for layout (a new reason why tables for layout is a bad approach) and apparently that's enough to trigger this bug in IE.

Side note: I talk a lot about the importance of Feedback, that tools should clearly identify problems and guide you to solutions. On a grading scale of A - F, IE would receive the grade take out back and put down like a rabid dog on this issue. And many others.

I have a couple of leads on this bug:

I'm going to see if using the Tacos DatePicker will work better than the built-in Tapestry DatePicker.

Update: The Tacos DatePicker did the job. Better yet, the latest DatePicker (from Tacos 4 beta 2) is simply stunning from both a visual and a useability perspective.

Tuesday, March 14, 2006

Wednesday, March 08, 2006

From the fanciful ideas category ...

I was just thinking about a kind of half-measure between normal Tapestry useage and Trails.

One of the tedious aspects of building a lot of web apps (using Tapestry) isn't the managing of data in and out of Hibernate or JDO, it's just the repetition of building a table, inside a form, with each row containing a FieldLabel and a TextField (or PropertySelection, or whatever).

Wouldn't it be nice if I could just plop the following into the middle of my form?

<span jwcid="@edit:EditObject" object="ognl:pojo"/>

And this magic EditObject component could build the rest for me? This, certainly, would leverage Trails code, or Trails-like code. I'm sure there would be additional parameters to control CSS, and to control which properties were to be editted. And, of course, some set of annotations to define the validation of those properties. Maybe even so carefully named Block components to provide row overrides? Again, very Trails.

I think this logic would kick ass when building prototypes.

I was just sending a reply to Matt Raible about Java web framework sweet spots. At the core of my response to why Ruby on Rails is gaining so much mind share is because its represents a solution, not a tool. The Java space, especially the open source crowd, has gotten really good a churning out extremely useful tools. However, we tend to leave the solutions to the motivated students. The lesson of Ruby on Rails is for us tool-makers to get fired up about creating solutions.

This is just the opposite of how Tapestry has evolved, which (of course) parallels the evolution of how my coding and design skills has evolved. Earliest Tapestry was a very pure tool, focused entirely on "animating" HTML tags, as well as the transient and persistent state management. Over time, the components evolved from a focus on a single tag to many tags, to entire behaviors (including early precursors to Ajax techniques). That's great, but it's been left to the imagination of the user to see how those tools fit together to allow you to create a useful application.

This is more than just a question of examples, it's really about the emphasis of the overall framework. "Here's how you edit your object (in one line)" should be the first thing new users see and learn ... giving the users the ability to exactly control the HTML should be lesson three or five or ten. That's where Trails is getting things very, very right.

Subversion: ouch!

So, all of a sudden, I'm having massive problems with my Subversion server. Basically, nothing works. I can't seem to check new files in (it appears to crash svnserve, resulting in broken pipe exceptions) and I also see problems checking files out.

I'm in the middle of a full backup of my laptop (before my big trip to Boston and then Tucson). I think, after that, I'll dump out my repository, and rebuild it as a file system (not Berkeley DB) version. I've heard people claim that SVN in server mode doesn't work well with Berkeley DB, but I had thought that was only when you have multiple users pounding on it.

This is frustrating, and bad timing. I'm concerned that I'll have data, or at least revisions, in SVN I can't access. We'll see. It's always something.

Anyway, this is why I haven't had a chance to update the code for the NFJS project to include the latest code and presentations. I can't get things into SVN so that I can properly build and deploy.

Update: It's possible that I have a bad sector on my hard drive (Acronis has found an unreadable sector ... it's impossible to say if this bad sector has anything to do with my Subversion repository). Either way, reading (if possible) the repository to a dump format, and building a new file-system repository, should be a reasonable approach.

Wednesday, February 22, 2006

Update to tapestry-flash

I've updated the build for tapestry-flash to compile for JDK 1.3. I haven't changed the version number, but Maven 2 should pull it down anyway, since the time stamp and MD5 have changed.

Gearing up for NFJS

If I've been a bit unresponsive lately, it's because I've been working full time++ for a client, and putting together new versions of my presentations for two upcoming No Fluff Just Stuff engagements: St. Louis and Boston.

I'm completely retooling my existing presentations on Tapestry and Tapestry components, and writing a brand new one about unit testing with EasyMock (which isn't about Tapestry per-se, but may include some details about testing Tapestry components).

The new examples will be available via my Maven 2 repository. I may even move the source code from my private SVN over to JavaForge at some point.