Tapestry Training -- From The Source

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

Monday, January 14, 2008

Latest batch of features

I've been very busy on Tapestry 5 now, sprinting towards an end goal of a stable release. I'm very psyched about the latest stuff:

  • Improved Error Bubbles: those pop-up error bubbles (for client validaton, in 5.0.7) were just a first pass. I've smartened them up a bit, and now the bubbles for the non-focus field fade out entirely. I like the way it works, as you tab from field to field, the bubbles fade in and fade out appropriately.
  • Memory Management: Tapestry is a bit more careful about instantiating pages now. The first few page instantiations are "free" but past a certain point, Tapestry will wait a few milliseconds for a page instance to free up before instantiating a fresh one. And pages that aren't used for about 10 minutes are freed outright. And it's all configurable.
  • Whitespace Compression: Tapestry now strips out unnecessary whitespace from templates. This is a bit controversial (because the output is bit less readble) but is a win in terms of server side processing and on the client side. This change significantly reduces the number of objects needed in memory to represent a component template and that can't help but be a good thing.
  • Optimized Request Paths: When rendering output, Tapestry will make use of shortened, relative URLs, if those are shorter than an absolute URL. This is another attempt to minimize the content sent to the client, before we get into GZIP filters and so forth.
  • Immediate Response Mode: Miss the default Tapestry 4 request/render cycle? Don't care about book-markable URLs or the user hitting the refresh button and inadvertently re-submitting a form? You can now disable the normal "send a client redirect" behavior and have Tapestry render the HTML immediately. This requirement comes from some particular clients, but honestly I hope it won't be widely used.

Beyond that its been bugs, bugs, bugs and getting the last major features in place; more Ajax support (with still more to come) and generally adding some polish.

I still can't say exactly when this will be ready, but I'm now able to work on Tapestry full time (and then some) so its coming together rapidly.

Thursday, January 10, 2008

Creating new T5 projects the easy way

The Maven archetype for Tapestry projects is really useful for getting up and running quickly.

However, that's not what I use day-to-day. That command line is so long and ugly!

I use the following Ruby script as a wrapper around Maven:

#!/usr/bin/ruby

require 'optparse'

$group = nil
$artifact = nil
$package = nil
$archetypeVersion = nil
$version = "1.0.0-SNAPSHOT"
$offline = false

$opts = OptionParser.new do |opts|
  
  opts.banner = "Usage: new-project.rb [options]"
  
  opts.on("-g", "--group GROUP", "The group id for the new project") do |value|
    $group = value
  end

  opts.on("-a", "--artifact ARTIFACT", "The artifact for the new project") do |value|
    $artifact = value
  end
  
  opts.on("-p", "--package PACKAGE", "The root package for source code in the new project") do |value|
    $package = value
  end
  
  opts.on("-v", "--version VERSION", "The version number of the new project") do |value|
    $version = value
  end
  
  opts.on("-o", "--offline", "Execute Maven in offline mode") { $offline = true }
  
  opts.on("-V", "--archetype-version VERSION", "Version of the Tapestry quickstart archetype") do |value|
    $archetypeVersion = value
  end
  
  opts.on("-h", "--help", "Help for this command") do
    puts opts
    exit
  end
end

def fail(message)
  puts "Error: #{message}"
  puts $opts
  exit
end


begin
  $opts.parse!
rescue OptionParser::InvalidOption
  fail $!
end

fail "Unexpected command line argument" if ARGV.length > 0
fail "Must specify group" unless $group
fail "Must specify artifact" unless $artifact

$package = $package || "#$group.#$artifact"

command = ["mvn"]

command << "-o" if $offline

command << [
  "archetype:create",
  "-DarchetypeGroupId=org.apache.tapestry",
  "-DarchetypeArtifactId=quickstart",
  "-DgroupId=#$group",
  "-DartifactId=#$artifact",
  "-DartifactVersion=#$version",
  "-DpackageName=#$package"]

if $archetypeVersion
  command << "-DarchetypeVersion=#$archetypeVersion"
end

command = command.join ' '

exec command

A typical usage is thus: new-project -g com.example -a myapp ... and we're off and running.

Sunday, November 25, 2007

First Tapestry 5 Book

The first book on Tapestry 5, Tapestry 5: Building Web Applications, is available for pre-order. I've been helping out by tech editing the chapters.

Saturday, November 24, 2007

Working on T5 Ajax Support

This is stuff people have waited impatiently for going on six months or more, and it's starting to come together. I'm continuing to work using Prototype/Scriptaculous and put together a simple Autocompleter mixin. I've been working on a more general Ajax operations, wherein a link on the page will trigger a component event and the return value there (usually a Block) will be used to update a portion of the page (demarked by a Zone component).

I'm also simplifying a bunch of APIs that got twisted up for PageTester (Kent Tong's unit testing layer for T5). ActionResponseGenerator is going away which makes a lot of code and testing easier and simpler.

Right now the only thing slowing me down is some soreness in my left hand.

Wednesday, November 21, 2007

IntelliJ Watch: Change Sets

I've started using IntelliJ change sets, another great feature. A change set is very intuitive: a set of related changes. Typically the changes are related to a bug.

IntelliJ tracks those changes as a group: all the changed, added and deleted files. You can then commit those changes as a group, without checking in anything else.

This is just perfect for checking in a quick bug fix in the middle of a larger body of work.

Nice features: You create a comment when you first create the change set, this comment is the default check-in comment when you do commit changes. Further, they didn't skimp ... you can move a change between change sets.

The pattern I'm seeing is that IntellJ has features that cover specific things I've had to do manually and awkwardly in Eclipse. I can definitely remember times I've fixed a small bug and had to carefully pick and choose which files to check in, because I had many unfinished outstanding changes waiting to go. IntelliJ just picks up the slack and does it for me.

I'm not sure how you handle this situation in Mylyn, Eclipse's super-invasive ... tool? Framework? Filter? Nanny? Whatever it is, I guess it can do it, but I doubt it accomplishes the same goal as simply and comprehensibly.

Saturday, November 17, 2007

Another IntelliJ Zealot

Wow that was quick. My first attempt with IntelliJ didn't really take, but I've had more time now to make the adjustment under less pressure.

What a difference. On my Mac at least, it's much faster and more stable than Eclipse. I've been running current versions of Eclipse, with the Maven plugin but very little else, and Mylyn disabled as much as possible ... and Eclipse has been agonizingly slow and very unstable with multiple lock ups and crashes per day.

IntelliJ is just ... better. Faster (note that you need to turn off the Mac L&F if you want best performance; I'm using "Alloy").

I've been so thoroughly used to the Eclipse development model, including constant compilation. IntelliJ doesn't compile until you need to run code. It does parse your code constantly, which amounts to the same thing in terms of refactoring and code completion, but IntelliJ is far more forgiving of syntax errors and the like.

Maven integration is better; I chose an option where it synchronizes from Maven on demand (and on startup).

For a multi-module projects, IntelliJ does a better job. Each module really gets its own classpath, whereas Eclipse merges together all the source paths and all the libraries for all modules.

A couple of Tapestry development notes:

  • You do want to make sure that your exported resource patterns (in the Compiler project options) is !?*.java ... otherwise critical Tapestry files don't get copied and made available on the classpath.
  • Also, for IntelliJ you have to perform a make (command-F9) after changing code or templates for those changes to be visible.

Yes, there's a number of nitpicks, especially on a Mac. It's extremely intimidating the first time you launch, with a wealth of confusing options. Documentation is spotty and key concepts are never explained. There's too many modal popup windows. Regardless, I'm already working faster, using the mouse less, and keep finding new features. I'd say I'm using about 20% of what IntelliJ can do right now, and I'm already well ahead of the Eclipse curve.

Apparently, IntelliJ is like snowboarding: you need to give it three full days before you're hooked. Counterpoint: I'm still on skis.

Another analogy: IntelliJ is metric and Eclipse is english. If all you've ever known is feet and inches and pounds and ounces, then metric doesn't seem worth the effort to switch. Metric just seems like a different set of numbers, mixed with disconcerting nuances (such as the difference between mass and weight). On the other hand, once you make the transition, many difficult tasks become easier, since most conversions are multiplying or dividing by a power of ten.

IntelliJ's 30 day evaluation is long enough to get hooked. Give it a try.

Monday, November 12, 2007

A Leopard Upgrade Story

I haven't been having the easiest time with my Leopard upgrade.

After my first attempt to upgrade, I got the dreaded "blue screen hang" on startup. This afflicts some number of systems, where after the install, all you get is a blue screen. After a couple of tries, I did a clean install and that worked better. Fortunately, I didn't follow Ben Galbraith's advice and "pull the switch", and I'm so glad a made a complete backup first!

I imported my settings from my backup and the vast majority of things just worked, including applications (/Application and ~/Applications) and most settings. Not my printer, though.

I'm still trying to figure out how to get svnserve up and running again, which is no fun, as I had just figured it out for Tiger. I'm getting a permissions problem accessing the files as user _svn (Leopoard puts an underscore in front of all system or daemon user ids and groups). I don't understand exactly why.

Other fun ... I decided to move to Mail.app from Thunderbird, since Mail.app can import Thunderbird mailboxes. For some reason, it only imported my older mail, so I have to keep Thunderbird around to access all my mail from 2007. 2006 and earlier imported. I don't get it!

Eclipse has become unstable for me. That's no fun, it's been getting memory access violations and crashing hard. So, this is a new chance to try IntelliJ. I'm already like some things, but I've also already hit a couple of bugs and the shear wealth of options and terminology is overwhelming.

I haven't tried out Time Machine yet, I may give it a whirl tonight.

I'm starting to adjust to using Spaces and have closed up my Mac's screen; with multiple desktops, it's just simpler (and better for my posture) to use a single screen. I may have to get a camera for iChat.

I immediately moved the Dock to the right side of the screen, to turn off the awful, stupid, distracting look they introduced.

Leopard includes the svn suite by default (I'm not sure if Tiger did); using MacPorts to install Hugs and erlang was easy, but ghc is a mess because of something that's changed between tiger and leopard. But who has time to study anything new when they're busy trying to finish Tapestry (and learn IntelliJ)?

Friday, November 09, 2007

SVNServe on Mac OS X

Somewhere along the way in switching from Fink to MacPorts (ostensibly because MacPorts is better supported) I shutdown my local SVN server. Strangely, the SVN for MacPorts doesn't include anything to assist in running a local SVN server.

Fortunately I found this useful snippet and it seems to work great, even after a reboot. Be careful to change the user name (at the bottom), the group name (at the top) and the SVN root repository (towards the top). For my system, I created a www user and www group as the owner of the SVN repository.

It may seem odd to run SVN server on a laptop; but this laptop is my development machine, and it goes with me everywhere, so I'm never in a position where I can't access my repository. Dave Thomas encourages you to store everything in version control, and I do my best.

Over the weekend, I'll be upgrading to Leopard, hopefully this will continue to work. I'm optimistic.

Thursday, October 25, 2007

A few new Tapestry 5 components

Sven Homburg is hosting a Tapestry 5 components site on Google code. Interestingly, it requires Tapestry 5.0.6 which is only available today. They've been riding the bleeding edge, they have!

Big improvement to quickstart archetype

I've figured out the necessary magic incantation that allows you to use an archetype without having to specify the archetype version number. Now anybody with Maven 2.0.7 installed can be up and running a Tapestry application in just a few seconds.

To test it out, I just removed my ~/.m2/repository/org/apache/tapestry folder and used the archetype to create a new Tapestry web application. The listing below gets clipped ... the command is mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart -DgroupId=org.example -DartifactId=myapp -DpackageName=org.example.myapp -Dversion=1.0.0-SNAPSHOT

$ mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart -DgroupId=org.example -DartifactId=myapp -DpackageName=org.example.myapp -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] ----------------------------------------------------------------------------
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] ************************************************************** 
[INFO] Starting Jakarta Velocity v1.4
[INFO] RuntimeInstance initializing.
[INFO] Default Properties File: org/apache/velocity/runtime/defaults/velocity.properties
[INFO] Default ResourceManager initializing. (class org.apache.velocity.runtime.resource.ResourceManagerImpl)
[INFO] Resource Loader Instantiated: org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader
[INFO] ClasspathResourceLoader : initialization starting.
[INFO] ClasspathResourceLoader : initialization complete.
[INFO] ResourceCache : initialized. (class org.apache.velocity.runtime.resource.ResourceCacheImpl)
[INFO] Default ResourceManager initialization complete.
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Literal
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Macro
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Parse
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Include
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Foreach
[INFO] Created: 20 parsers.
[INFO] Velocimacro : initialization starting.
[INFO] Velocimacro : adding VMs from VM library template : VM_global_library.vm
[ERROR] ResourceManager : unable to find resource 'VM_global_library.vm' in any resource loader.
[INFO] Velocimacro : error using  VM library template VM_global_library.vm : org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'VM_global_library.vm'
[INFO] Velocimacro :  VM library template macro registration complete.
[INFO] Velocimacro : allowInline = true : VMs can be defined inline in templates
[INFO] Velocimacro : allowInlineToOverride = false : VMs defined inline may NOT replace previous VM definitions
[INFO] Velocimacro : allowInlineLocal = false : VMs defined inline will be  global in scope if allowed.
[INFO] Velocimacro : initialization complete.
[INFO] Velocity successfully started.
[INFO] [archetype:create]
[INFO] artifact org.apache.tapestry:quickstart: checking for updates from central
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/quickstart/5.0.6/quickstart-5.0.6.jar
11K downloaded
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: quickstart:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.example
[INFO] Parameter: packageName, Value: org.example.myapp
[INFO] Parameter: basedir, Value: /Users/Howard/work
[INFO] Parameter: package, Value: org.example.myapp
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: myapp
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/pom.xml [line 14,column 22] : ${tapestry-release-version} is not a valid reference.
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/pom.xml [line 80,column 26] : ${tapestry-release-version} is not a valid reference.
[INFO] ********************* End of debug info from resources from generated POM ***********************
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/src/main/webapp/Start.tml [line 11,column 34] : ${currentTime} is not a valid reference.
[INFO] Archetype created in dir: /Users/Howard/work/myapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Thu Oct 25 07:47:04 PDT 2007
[INFO] Final Memory: 5M/9M
[INFO] ------------------------------------------------------------------------
~/work
$ cd myapp
~/work/myapp
$ mvn jetty:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] org.apache.maven.plugins: checking for updates from tapestry-snapshots
[INFO] org.codehaus.mojo: checking for updates from tapestry-snapshots
[INFO] artifact org.apache.maven.plugins:maven-compiler-plugin: checking for updates from tapestry-snapshots
[INFO] artifact org.mortbay.jetty:maven-jetty-plugin: checking for updates from tapestry-snapshots
[INFO] artifact org.apache.maven.plugins:maven-war-plugin: checking for updates from tapestry-snapshots
[INFO] ----------------------------------------------------------------------------
[INFO] Building myapp Tapestry 5 Application
[INFO]    task-segment: [jetty:run]
[INFO] ----------------------------------------------------------------------------
[INFO] Preparing jetty:run
[INFO] artifact org.apache.maven.plugins:maven-resources-plugin: checking for updates from tapestry-snapshots
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
3K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
12K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
3K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
1K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-repository/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-snapshot-repository/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
267K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
952K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-repository/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-snapshot-repository/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
4K downloaded
[INFO] [compiler:compile]
[INFO] Compiling 2 source files to /Users/Howard/work/myapp/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: myapp Tapestry 5 Application
[INFO] Webapp source directory = /Users/Howard/work/myapp/src/main/webapp
[INFO] web.xml file = /Users/Howard/work/myapp/src/main/webapp/WEB-INF/web.xml
[INFO] Classes = /Users/Howard/work/myapp/target/classes
2007-10-25 07:47:44.872::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /myapp
[INFO] Tmp directory = /Users/Howard/work/myapp/target/work
[INFO] Web defaults =  jetty default
[INFO] Web overrides =  none
[INFO] Webapp directory = /Users/Howard/work/myapp/src/main/webapp
[INFO] Starting jetty 6.1H.5-beta ...
2007-10-25 07:47:44.011::INFO:  jetty-6.1H.5-beta
2007-10-25 07:47:44.189::INFO:  No Transaction manager found - if your webapp requires one, please configure one.
[INFO] TapestryFilter Startup time: 189 ms to build IoC Registry, 587 ms overall.
2007-10-25 07:47:45.282::INFO:  Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:04 GMT] "GET / HTTP/1.1" 404 765 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:07 GMT] "GET /myapp HTTP/1.1" 302 0 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 459 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:07 GMT] "GET /myapp/ HTTP/1.1" 200 519 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 3 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:08 GMT] "GET /myapp/assets/tapestry/default.css HTTP/1.1" 200 4916 "http://localhost:8080/myapp/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 2 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:12 GMT] "GET /myapp/start HTTP/1.1" 200 519 "http://localhost:8080/myapp/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 4 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:12 GMT] "GET /myapp/assets/tapestry/default.css HTTP/1.1" 304 0 "http://localhost:8080/myapp/start" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"

That's it! Jetty is running, and the application is available at http://localhost:8080/myapp. And of course, I have a Ruby script to make setup even easier.

Tapestry 5.0.6 is available

Yet another preview release, Tapestry 5.0.6 adds a raft of new features , including:

A new DateField component (using a client-side JavaScript calendar).

The Grid component can now be utilized inside a Form.

The BeanEditForm component has been refactored, allowing you to create complex forms with multiple BeanEditors.

There is now a BeanDisplay component, a counterpart to BeanEditor that displays the properties of a bean.

For early adopters upgrading from release 5.0.5, you should be aware that Tapestry template files now have a .tml extension, and are stored in the context root, not under WEB-INF . In addition, the @Inject annotation in tapestry-core has been removed, and the @Inject annotation from tapestry-ioc is now doing double-duty.

In addition, Tapestry 5 now uses SLF4j as its pluggable logging library, replacing commons-logging. This may require that you upgrade to Log4j 1.2.14.

Download Tapestry 5.0.6

Sunday, October 14, 2007

Tapestry 5.0.6 soon

I'm very much hoping to have Tapestry 5.0.6 out soon. I've been squeezing in some more time to work on Tapestry 5 and just added a BeanDisplay component (to complement the BeanEditor component).

The Ajax story for Tapestry 5 is still up in the air ... the specter of writing an abstraction layer has come back. And I still want better, smarter handling of Hibernate ... it seems to me that the BeanEditor should be capable of handling to-one relationships in a smart way. And Spring WebFlow integration would be a snap if I just had a couple of days to work on it.

I'm personally anxious to get Tapestry 5 solid and shipped ... but not before it's ready.

Monday, October 01, 2007

TheDailyTube: Powered by Tapestry 5

A posting on the Tapestry mailing list led me to TheDailyTube, a video sharing site powered by ... Tapestry 5. It may be "alpha", but people are putting their faith in Tapestry 5.

The site looks sweet and is plenty responsive. Can't really tell too much else about the site though ... they've turned off the default exception page. Still, this is the kind of high volume, outward facing site that I'm proud to have helped facilitate. Viva Tapestry 5!

Friday, September 28, 2007

One of the greatest Worse-Than-Failures, ever ...

Serializalicious, now on WTF is just one example Steve, Ben and I discovered in this application. The article is somewhat misleading, it makes it look like Steve put that code in there. Nope, Steve, Ben and I were all victims of the missing Architect. Funny, scary stuff.

Wednesday, September 26, 2007

Let there be Nightly Docs!

With just a little bit of tweaking, we now have full, nightly documentation for Tapestry 5. This is great news, as the public web site can start staying static between releases (thus all the docs there will reflect just the latest stable release), and the snapshots and documentation on tapestry.formos.com will also be in sync, reflecting what's currently going on in SVN trunk.

That should ease a lot of confusion, when the docs say something exists but it doesn't in the stable (non-snapshot) versions.

Tuesday, September 25, 2007

Dr. Seuss visits the Tapestry Mailing List

Just noticed this posting by Bill Holloway:

Will you work with JSP?

They mess up my MVC.
I will not work with JSP.

We'll put them in an IDE.

I don't want them in an IDE.
They screw up the MVC.
I will not work with JSP.

We'll put them down inside some struts.
You'll never see them,
In the guts.

You cannot hide them in the guts!
They're in your face when they're in struts!
I don't want them on my IDE.
They screw up our MVC!
I will not work with JSP.

We'll put them in some server faces.
Many use them, many places.
That way the MVC's ok.
Will you work with them today?

The XML of server faces
Bogs down the coders in those places!
You cannot hide them in the guts.
I will not work with them in struts.
Get them off my CRT!
They're $@%#*&ing up my MVC!
Give me back my Tapestry.

Tapestry 5 Nightly Builds

I've finally gotten around to setting up nightly builds for Tapestry 5. It just another build on the Bamboo server. You can get access to it by adding:

  <repositories>
    <repository>
      <id>tapestry-snapshot</id>
      <url>http://tapestry.formos.com/maven-snapshot-repository</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
to your POM.

Eventually, we'll be publishing documentation every night too, but that's waiting on a Maven bug.

Saturday, September 15, 2007

Mac OS X bundles vs. Subversion

If you work on Mac OS X, you may have noticed how cool Macs deal with complex documents, things like Keynote presentations or applications themselves. They're stored as directories. The Finder hides this, making them look and act like individual files. This works nicely, often the contents of a bundle are simple text and XML files ... whereas the equivalent under Windows is either a very proprietary (and potentially fragile) binary format, or multiple files and folders that YOU have to treat as a unit.

Alas, this all breaks down when using Subversion. You can't just check in MyPresentation.key into SVN ... it will create those pesky .svn directories inside the bundle, and those will be destroyed every time you save your presentation.

My solution to this is to convert the bundles into an archive, and check in the archive. The bundle folders are marked as svn:ignore. I guess this reveals that I mostly use SVN as a safe, structured backup.

In any case, manually creating those archives can be a pain. So ... out comes my solution to many problems: Ruby.

The goal here is to find bundles that need to be archived; do it efficiently (only update the archive if necessary) and do it recursively, seeking out bundles in sub-directories.

#!/usr/bin/ruby

# Used to prepare a directory for commit to Subversion. This is necessary for certain file types on Mac OS X because what appear to be files in the Finder
# are actually directories (Mac uses the term "bundle" for this concept). It is useless to put the .svn folder inside such a directory, because it will
# tend to be deleted whenever the "file" is saved.  
#
# Instead, we want to compress the directory to a single archive file; the bundle will be marked as svn:ignore.
#
# We use tar with Bzip2 compression, which is resource intensive to create, but 
# compresses much better than GZip or PKZip.
#
# The trick is that we only want to create the acrhive version when necessary; when 
# the archive does not exist, or when any file
# in the bundle is newer than the archive.

require 'optparse'

# Set via command line options

$extensions = %w{pages key oo3 graffle}
$recursive = true
$dry_run = false

# Queue of folders to search (for bundles)

$queue = []

def matching_extension(name)
  dotx = name.rindex('.')
  
  return false unless dotx
  
  ext = name[dotx + 1 .. -1]
  
  return $extensions.include?(ext)
end


# Iterate over the directory, identify bundles that may need to be compressed and (if recursive) subdirectories
# to search.
#
# path: string path for a directory
def search_directory(dirpath)
  
  Dir.foreach(dirpath) do |name|
    
    # Skip hidden files and directories
    next if name[0..0] == "."
    
    path = File.join(dirpath, name)
        
    next unless File.directory?(path)
                  
    if matching_extension(name)
      update_archive path
      next
    end
    
    if $recursive
      $queue << path
    end
    
  end
  
end


def needs_update(bundle_path, archive_path)
  
  return true unless File.exists?(archive_path)
  
  archive_mtime = File.mtime(archive_path)
  
  # The archive exists ... can we find a file inside the bundle thats newer?
  # This won't catch deletions, but that's ok.  Bundles tend to get completly
  # overwritten when any tiny thing changes.
  
  dirqueue = [bundle_path]

  until dirqueue.empty?
    
    dirpath = dirqueue.pop
    
    Dir.foreach(dirpath) do |name|
      
      path = File.join(dirpath, name)
      
      if File.directory?(path)
        dirqueue << path unless [".", ".."].include?(name)
        next
      end
      
      # Is this file newer?
      
      if File.mtime(path) > archive_mtime
        return true
      end
      
    end
    
  end
  
  return false
end

def update_archive(path)
  archive = path + ".tar.bz2"
  
  return unless needs_update(path, archive)

  if $dry_run
    puts "Would create #{archive}"
    return
  end

  puts "Creating #{archive}"
    
  dir = File.dirname(path)
  bundle = File.basename(path)
    
  # Could probably fork and do it in a subshell
  system "tar --create --file=#{archive} --bzip2 --directory=#{dir} #{bundle}"

end

$opts = OptionParser.new do |opts|
  
  opts.banner = "Usage: prepsvn [options]"

  opts.on("-d", "--dir DIR", "Add directory to search (if no directory specify, current directory is searched)") do |value|
    $queue << value
  end

  opts.on("-e", "--extension EXTENSION", "Add another extension to match when searching for bundles to archive") do |value|
    $extensions << value
  end
  
  opts.on("-N", "--non-recursive", "Do not search non-bundle sub directories for files to archive") do
    $recursive = false
  end
  
  opts.on("-D", "--dry-run", "Identify what archives would be created") do
    $dry_run = true
  end
  
  opts.on("-h", "--help", "Help for this command") do
    puts opts
    exit
  end
end

def fail(message)
    puts "Error: #{message}"
    puts $opts
end

begin
    $opts.parse!
rescue OptionParser::InvalidOption
    fail $!
end

# If no --dir specified, use the current directory.

if $queue.empty?
  $queue << Dir.getwd
end

until $queue.empty? 
  search_directory $queue.pop
end

I do love Ruby syntax, it is so minimal, and lets me follow my personal mantra less is more.

I'm sure there's some edge cases that aren't handle well, such as spaces in path names and maybe issues related to permissions. But it works for me.

You do need to have tar installed, in order to build the archives. I can't remember if that's built in to Mac OS X (probably) or whether I obtained it using Fink.

In any case, you need to remember to execute prepsvn in your workspace, to spot file bundles that need archiving, before you check in. It would be awesome if Subversion supported some client-side check-in hooks to do this automatically.

Saturday, September 08, 2007

Itch scratching: Even better feedback for all thumbs typists

I was reading through Matt's rundown of Struts 2 (does it suck?) and he strayed into one of my most passionate areas: feedback.

He gave an example of an incorrect property name, and how that would be reported to the user. He showed examples from all the major frameworks, and the Tapestry 4 version, even without its proper CSS styles, won hands-down.

However, as I was reading and responding, it struck me that while other framework can barely tell you what you've done wrong, Tapestry 5 should be telling you how to fix it. In this case, advising you about the possible properties you could have specified, which I've added as TAPESTRY-1737 and fixed.

Here's the result. Not bad for ten minutes work. And remember: property names in Tapestry 5 are already case insensitive, which wipes out probably 50% of your typical typing errors anyway.

Thursday, September 06, 2007

Biled!

Well, Hani has finally Biled the Bearded One. But what a cheap and meaningless shot ... he's upset about a switch from commons-logging to SLF4J? What is it with Hani and commons-logging? Did commons-logging steal his prom date? Did it kick sand in his face at the beach? Maybe it touched him inappropriately as a child. In any case, he's strangely obsessive about anything that touches on the subject.

On a serious note ... people have come out of the woodwork to defend the use of JDK logging, especially inside Websphere (God help them), which does justify a pluggable approach. Again, Log4J does not quite fit all, and because it is based on classes, not interfaces, it gets in the way of mock testing.