Tapestry Training -- From The Source

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

Friday, 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:

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.


# 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)

# 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
    if $recursive
      $queue << path

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)
      # Is this file newer?
      if File.mtime(path) > archive_mtime
        return true
  return false

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

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

  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}"


$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

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

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

rescue OptionParser::InvalidOption
    fail $!

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

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

until $queue.empty? 
  search_directory $queue.pop

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


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.