<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DkEBQXs_fip7ImA9WhBbFkg.&quot;"><id>tag:blogger.com,1999:blog-4110180</id><updated>2013-05-15T14:50:50.546-07:00</updated><category term="documentation" /><category term="funny" /><category term="clojure" /><category term="derby" /><category term="spock" /><category term="pancake mix" /><category term="netkernel" /><category term="books" /><category term="bugs" /><category term="javadoc" /><category term="cappuccino" /><category term="firebug" /><category term="annotations" /><category term="maven" /><category term="formos" /><category term="selenium" /><category term="google application engine" /><category term="wtf" /><category term="lion" /><category term="linkedin" /><category term="objective-c" /><category term="firefox" /><category term="grails" /><category term="iphone" /><category term="travel" /><category term="testng" /><category term="css" /><category term="git" /><category term="code coverage" /><category term="easymock" /><category term="spring" /><category term="rss" /><category term="haskell" /><category term="spam" /><category term="video" /><category term="javassist" /><category term="servlet" /><category term="performance" /><category term="eclipse" /><category term="science fiction" /><category term="closures" /><category term="cloure" /><category term="training" /><category term="safari" /><category term="cascade" /><category term="scala" /><category term="jsf" /><category term="threads" /><category term="java" /><category term="seam" /><category term="lock" /><category term="security" /><category term="vmware" /><category term="webinar" /><category term="licenses" /><category term="aop" /><category term="leaky abstractions" /><category term="hivedoc" /><category term="monads" /><category term="inform" /><category term="intellij" /><category term="web flow" /><category term="immutability" /><category term="pdx" /><category term="slf4j" /><category term="apachecon" /><category term="concurrency" /><category term="spring web flow" /><category term="tynamo" /><category term="watchmen" /><category term="android" /><category term="javazone" /><category term="groovy" /><category term="mac" /><category term="hackergarten" /><category term="dsl" /><category term="ides" /><category term="configurations" /><category term="release" /><category term="testing" /><category term="ubuntu" /><category term="coffeescript" /><category term="datomic" /><category term="extjs" /><category term="third-party components" /><category term="conferences" /><category term="json" /><category term="prototype" /><category term="svn" /><category term="subversion" /><category term="google" /><category term="screencast" /><category term="ioc" /><category term="ruby" /><category term="articles" /><category term="couchdb" /><category term="trails" /><category term="support" /><category term="javascript" /><category term="vienna" /><category term="nfjs" /><category term="appfuse" /><category term="refcard" /><category term="tomcat" /><category term="codemash" /><category term="concurrence" /><category term="error reporting" /><category term="gorm" /><category term="customizing" /><category term="gradle" /><category term="node" /><category term="guice" /><category term="feedback" /><category term="agile" /><category term="javaforge" /><category term="wicket" /><category term="leopard" /><category term="tapestry5" /><category term="jetty" /><category term="javaone" /><category term="devoxx" /><category term="consulting" /><category term="antlr" /><category term="skillsmatter" /><category term="live reloading" /><category term="off topic" /><category term="byte code" /><category term="mindstorms" /><category term="async" /><category term="jmx" /><category term="hivemind" /><category term="tapestry" /><category term="arduino" /><category term="hibernate" /><category term="idea" /><category term="ant" /><category term="jug" /><category term="oscon" /><category term="tss" /><category term="ajax" /><category term="tutorial" /><category term="synchronized" /><category term="mac os x" /><category term="scriptaculous" /><category term="meta-programming" /><category term="netnewswire" /><category term="hani" /><category term="ie" /><category term="archetype" /><category term="kindle" /><category term="jquery" /><category term="ruby on rails" /><category term="captcha" /><category term="blogger" /><category term="world tour" /><category term="scrum" /><category term="twitter" /><category term="portland" /><category term="functional programming" /><category term="jboss" /><category term="tapx" /><category term="https" /><category term="throughput" /><category term="macports" /><category term="type coercion" /><category term="streambank" /><title>Tapestry Central</title><subtitle type="html">The inside scoop on what's happening with Tapestry ... from the creator of the Apache Tapestry framework.  Plus all the normal, random thoughts on coding and technology.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://tapestryjava.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>763</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/TapestryCentral" /><feedburner:info uri="tapestrycentral" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;DEAER3o6eSp7ImA9WhNaF0s.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-5336318002760111319</id><published>2013-02-01T11:49:00.000-08:00</published><updated>2013-02-01T16:58:26.411-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-02-01T16:58:26.411-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>Crafting Code in Clojure</title><content type="html">&lt;p&gt;The other day, I was working on a little bit of code in Clojure, just touching up some exception reporting, when I was
  suddenly struck by one of the fundamental reasons that Clojure is so enjoyable to code in.  Clojure
  is &lt;em&gt;craftable&lt;/em&gt;: that is, in Clojure you have the option to craft at your code to make it more concise, easier
  to read, and easier to maintain. That is not the case for all, or perhaps even most, programming languages.&lt;/p&gt;

&lt;p&gt;In my case, I was constructing an error message where I needed to convert the keys of two maps into a comma-seperated
  string (I don't like to say "you guessed wrong" without saying "here's what you could have said").

&lt;p&gt;
  What I want my code to do is easily expressed as an informal recipe:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Extract all the keys from both maps&lt;/li&gt;
  &lt;li&gt;Remove any duplicates&lt;/li&gt;
  &lt;li&gt;Convert the keys to strings&lt;/li&gt;
  &lt;li&gt;Sort the strings into ascending order&lt;/li&gt;
  &lt;li&gt;Build and return one big string, by concatinating all the key strings, using ", " as a seperator&lt;/li&gt;
  &lt;li&gt;Return "&amp;lt;none&amp;gt;" if both maps are empty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I was writing
  this in Java, it would look something like this:&lt;/p&gt;

&lt;script src="https://gist.github.com/4693120.js"&gt;&lt;/script&gt;

&lt;p&gt;There's enough looping and conditionals in this code (along with tip-toeing around Java Generics) that its easier to
  look at its test specifiction (written in &lt;a href="http://docs.spockframework.org/en/latest/"&gt;Spock&lt;/a&gt;) to see what it
  is supposed to do:

&lt;script src="https://gist.github.com/4693138.js"&gt;&lt;/script&gt;

&lt;p&gt;The first pass at a Clojure version is already simpler than the Java version ...&lt;/p&gt;

&lt;script src="https://gist.github.com/4693160.js"&gt;&lt;/script&gt;

&lt;p&gt;I couldn't resist using
  the &lt;code&gt;&lt;a href="http://clojuredocs.org/clojure_core/clojure.string/join"&gt;clojure.string/join&lt;/a&gt;&lt;/code&gt; function,
  rather than building the string directly (which would be slightly tedious in Clojure). In many ways, this is a lot
  like the Java version; we're using &lt;code&gt;let&lt;/code&gt; to create local symbols for each step in the process in just the
  same way that the Java version defines local variables for each step.&lt;/p&gt;

&lt;p&gt;However, there's room for improvement here. Let's start to &lt;em&gt;craft&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;For example, let's assume that both maps being empty is rare, or at least, that the cost of sorting an empty list is
  low (it is!). Our code becomes much more readable if we merge it into one big &lt;code&gt;let&lt;/code&gt;:&lt;/p&gt;

&lt;script src="https://gist.github.com/4693219.js"&gt;&lt;/script&gt;

&lt;p&gt;Now we're getting somewhere. I think this version makes it much more clear what is going on that the prior Clojure
  version, or the Java version.&lt;/p&gt;

&lt;p&gt;However, if you've written enough code, you know one of the basic rules of all programming: &lt;strong&gt;names are
    hard&lt;/strong&gt;. Anything that frees you from having to come up with names is generally a Good Thing. In Java, we have
    endless names: not just for methods and variables, but for classes and interfaces ... even packages. Long years of
    coding Java has made me dread naming things, because names never quite encompass what a thing does, and often become
  outdated as code evolves.&lt;/p&gt;

&lt;p&gt;So, what names can we get rid of, and how? Well, if we look at the structure of our code, we can see that each step
  creates a value that is passed to the next expression as the final parameter.  So &lt;code&gt;all-keys&lt;/code&gt; is passed as
  the last parameter of the &lt;code&gt;(map)&lt;/code&gt; expression, resulting in &lt;code&gt;key-names&lt;/code&gt;, and
  then &lt;code&gt;key-names&lt;/code&gt; is passed as the last parameter of the &lt;code&gt;(sort)&lt;/code&gt; expression. In fact, ignoring
  the empty check for a moment, the &lt;code&gt;sorted-names&lt;/code&gt; value is passed to the &lt;code&gt;(s/join)&lt;/code&gt; expression as
  the last parameter as well.&lt;/p&gt;

&lt;p&gt;This is a very important concept in Clojure; you may have heard people trying to express that you code in Clojure in
  terms of a "flow" of data through a series of expressions. We'll, you've just seen a very small example of this.&lt;/p&gt;

&lt;p&gt;In fact, it is no simple coincidence that the last parameter is so important; this represents a careful and reasoned
  alignment of the parameters of many different functions in clojure.core and elsewhere, to ensure that &lt;em&gt;flow&lt;/em&gt; can
  be passed as that final parameter, because it becomes central to the ability to combine functions and expressions
  together with minimal fuss.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;&lt;a href="http://clojuredocs.org/clojure_core/clojure.core/-%3E%3E"&gt;-&amp;gt;&amp;gt;&lt;/a&gt;&lt;/code&gt; macro
  (pronounced "thread last") to
  rebuild our flow without having to come up with names for each step:

&lt;script src="https://gist.github.com/4693306.js"&gt;&lt;/script&gt;

&lt;p&gt;
  The &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; macro juggles our expressions into an appropriate order; without it we'd have to deeply
  nest our expressions in an unreadable way: &lt;code&gt; (sort (map str (set (concat (keys map1) (keys map2)))))&lt;/code&gt;. Even
  with a short flow of expressions, that's hard to parse and interpret, so &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; is an invaluable and
  frequently used tool in the Clojure toolbox.&lt;/p&gt;

&lt;p&gt;We can continue to craft; the first expression (that builds the set from the keys), can itself be broken apart into a
  few smaller steps. This is really to get us ready to do something a bit more dramatic:&lt;/p&gt;

&lt;script src="https://gist.github.com/4693353.js"&gt;&lt;/script&gt;

&lt;p&gt;This is getting ever closer to our original recipe; you can more clearly see the extraction of keys from the maps
before building the set (which is only used to ensure key uniqueness), before continuing on to convert keys from objects
  to strings, sort them, and combine the final result.&lt;/p&gt;

&lt;p&gt;
  In fact, we're going to go beyond our original brief, and support any number of input maps, not just two:&lt;/p&gt;

&lt;script src="https://gist.github.com/4693377.js"&gt;&lt;/script&gt;

&lt;p&gt;The &lt;code&gt;&lt;a href="http://clojuredocs.org/clojure_core/clojure.core/mapcat"&gt;mapcat&lt;/a&gt;&lt;/code&gt; function is like map,
  but expects that each invocation will create a collection; &lt;code&gt;mapcat&lt;/code&gt; concatinates all those collections together ... just
  what we want to assemble a collection of all the keys of all the input maps.&lt;/p&gt;

&lt;p&gt;
  At this point, we don't have much more to go ... but can we get rid of the &lt;code&gt;sorted-names&lt;/code&gt; symbol?  In fact,
  we can: what if part of our flow replaced the empty list with a list containing just the string "&amp;lt;none&amp;gt;"? It
  would look like this:&lt;/p&gt;

&lt;script src="https://gist.github.com/4693400.js"&gt;&lt;/script&gt;

&lt;p&gt;
... and that's about as far as I care to take it; a clean flow starting with the maps, and going through a series of
expressions to transform those input maps into a final result. But what's really important here is just how fast and
easy it is to start with an idea in Clojure and refine it from something clumsy (such as the initial too-much-like-Java
version) into something elegant and surgically precise, such as the final version.&lt;/p&gt;

&lt;p&gt;That's simply not something you can do in less expressive languages such as Java. For example, Tapestry certainly
  does quite a number of wonderful things, and supports some very concise and elegant code (especially
  in &lt;a href="http://tapestryjava.blogspot.com/2013/01/red-code-green-code-my-code-your-code.html"&gt;green code&lt;/a&gt;) ...
  but that is the result of organizing large amounts of code in service of specific goals. We're talking tons of
  interfaces, a complete Inversion-Of-Control container, and runtime bytecode manipulation to support that level of
  conciseness. That's the hallmark of a quite consequential framework.&lt;/p&gt;

&lt;p&gt;That isn't &lt;em&gt;crafting&lt;/em&gt; code; that's a big engineering effort. It isn't local and invisible, it tends to be
  global and intrusive.&lt;/p&gt;

&lt;p&gt;In Java, your only approach to simplifying code in one place is build up a &lt;em&gt;lot&lt;/em&gt; of complexity somewhere else.&lt;/p&gt;

&lt;p&gt;That is simply not the case in Clojure; by adopting, leveraging, and extending the wonderful patterns already present
  in the language and its carefully designed standard library, you can reach a high level of readability. You are no
  longer coding to make the compiler happy, you are in control, because the Clojure languge gives you the tools you need
  to be in control. And that can be intoxicating.&lt;/p&gt;


&lt;p&gt;The source code for this blog post is available on &lt;a href="https://github.com/hlship/clojure-blog-post-20130201"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;


&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/UzXWMuKdiCQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/5336318002760111319/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=5336318002760111319" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5336318002760111319?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5336318002760111319?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/UzXWMuKdiCQ/crafting-code-in-clojure.html" title="Crafting Code in Clojure" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>12</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2013/02/crafting-code-in-clojure.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk8NQnwzcCp7ImA9WhNaFUU.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-6211615440647514954</id><published>2013-01-30T14:28:00.000-08:00</published><updated>2013-01-30T14:28:13.288-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-01-30T14:28:13.288-08:00</app:edited><title>Red Code, Green Code, My Code, Your Code</title><content type="html">&lt;p&gt;
I had a very odd interchange with my friend Merlyn over lunch; he started talking about red code vs. green code (in the context of supporting both callbacks and promises inside NodeJS).  At first I thought he was referring to code coverage of those lines of code ... the implication being that supporting multiple paths of execution may lead to laziness in testing every possible path (with some code going "red" in the code coverage report).

&lt;p&gt;
But that wasn't it at all: "red" code referred to framework code, "green" code referred to end-user code, leveraging the framework.

&lt;p&gt;
Odder still, Merlyn insisted that he first heard this term from ... &lt;strong&gt;me&lt;/strong&gt;, a few years ago. That's what being in the baby-raising camp can do to you ... I know longer have any idea of what I've said or thought in the past. Actually, this isn't new for me ... I've always had a very vague memory for anything not code.

&lt;p&gt;In any case, this red vs. green terminology is a useful concept ... certainly I move the earth in Tapestry's red code to make end-user's green code as simple as possible.

&lt;p&gt;So, I want to do three things:

&lt;ul&gt;
&lt;li&gt;It's definitely worth reflecting on the fact that all code is &lt;strong&gt;not&lt;/strong&gt; created equal, and that long-lived, reusable code ("red") will inevitably grow in complexity to a level that would not be acceptable in client ("green") code.&lt;/li&gt;
&lt;li&gt;Let's promote this term, because it is so handy!&lt;/li&gt;
&lt;li&gt;If I didn't create the term myself, let's track down the originator and thank them!&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/W01Sg8OjkRs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/6211615440647514954/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=6211615440647514954" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/6211615440647514954?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/6211615440647514954?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/W01Sg8OjkRs/red-code-green-code-my-code-your-code.html" title="Red Code, Green Code, My Code, Your Code" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2013/01/red-code-green-code-my-code-your-code.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUEDSHg-fip7ImA9WhNUEUg.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-3230897634360453718</id><published>2013-01-02T12:11:00.001-08:00</published><updated>2013-01-02T12:14:39.656-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-01-02T12:14:39.656-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="jquery" /><category scheme="http://www.blogger.com/atom/ns#" term="tapestry5" /><title>Tapestry 5.4: jQuery Support now in place</title><content type="html">&lt;p&gt;I've spent the last several months significantly reworking Tapestry 5's client-side JavaScript support, in an effort to move away form the tight binding to Prototype. After all of that refactoring, recoding, repositioning, and just-plain-hacking, I was able to do most of the job of introducing jQuery support in just a few hours, yesterday and today.

&lt;p&gt;
I'll be producing another preview release pretty soon, or you can get the lastest from Tapestry's master branch.

&lt;p&gt;
Currently, the code to switch over from using Prototype to jQuery looks like this (it will get simpler soon):

&lt;script src="https://gist.github.com/1fb6eca0c1c6d28eb2a5.js"&gt;&lt;/script&gt;

&lt;p&gt;The first part overrides the provider to be "jquery" (the default provider is "prototype"). In Tapestry terms, the "infastructure framework" provides the APIs for DOM queries, DOM manipulation, and Ajax request handling.  If you don't like jQuery, you can easily create your own provider for your favorite framework.

&lt;p&gt;
Part of 5.4 is trying to manage all these different compatibility issues at a slightly higher level than configuration symbols; that's the Compatibility service with its Traits. Those two traits disable Scriptaculous support (which isn't needed by jQuery, and won't work without Prototype), and disables support for Tapestry 5.3-style initializers.

&lt;p&gt;
Underneath the covers, the way the switch between Prototype and jQuery works is quite simple:

&lt;script src="https://gist.github.com/e8ef548553e015c5eeec.js"&gt;&lt;/script&gt;

&lt;p&gt;
The ModuleManager service is the code that handles requests for modules from the client. It has a configuration that is used to handle edge cases, such as treating traditional JavaScript libraries as if they were AMD modules.
The code above sets up a server-side override for the module &lt;code&gt;t5/core/dom&lt;/code&gt;.  All the Tapestry client-side modules use &lt;code&gt;dom&lt;/code&gt;; none of them uses Prototype or jQuery directly (except for a couple that access Twitter Bootstrap functionality).

&lt;p&gt;
These contributions ensure that when the client-side requests the &lt;code&gt;t5/core/dom&lt;/code&gt; module, what will be sent back will be either the Prototype-specific implementation, or the jQuery-specific implementation. Without this contribution, we'd see a 404 error when the &lt;code&gt;dom&lt;/code&gt; module was requested.  Instead, the client-side doesn't have any idea that &lt;code&gt;dom&lt;/code&gt; is special.

&lt;p&gt;
The primary job of the &lt;code&gt;dom&lt;/code&gt; module is to wrap DOM elements inside a new object, thereby providing a new API that allows various kinds of manipulation, as well as listening to events, or triggering them. The API is a bit of a mashup between Prototype and jQuery, but leans pretty heavily towards jQuery-style operations and naming.  &lt;code&gt;dom&lt;/code&gt;'s secondary job is to support Ajax requests, plus adding event handlers to the document object.

&lt;p&gt;In practice, this can be very concise and readable (partially, thanks to CoffeeScript):

&lt;script src="https://gist.github.com/4437380.js"&gt;&lt;/script&gt;

&lt;p&gt;
In this listing (which supports some of the behavior of the Tapestry Zone component), &lt;code&gt;dom&lt;/code&gt; holds the export from module &lt;code&gt;t5/core/dom&lt;/code&gt;; it waits for the &lt;code&gt;events.zone.update&lt;/code&gt; event to be triggered; the callback is invoked with &lt;code&gt;this&lt;/code&gt; set to the wrapper around the element where the event was triggered.

&lt;p&gt;The event here is also a wrapper; its a minimal mix of Prototype and jQuery: I like the &lt;code&gt;memo&lt;/code&gt; property from Prototype, so that's present. The event handler triggers a pair of before and after events, and updates the content of the zone. Why the before and after events?  By default they do nothing, but it would be simple to add handlers to perform some kind of animation when content is added.

&lt;p&gt;
In any case, because all Tapestry client-side modules code against this API, they don't know or care whether the page has loaded Prototype, jQuery, or something else. If you are writing Tapestry components for reuse, coding against the &lt;code&gt;dom&lt;/code&gt; API will help ensure that your component will work correctly in all kinds of Tapestry applications.  However, when coding an application,. you reserve the right to choose what the infrastructure framework should be: you should feel free to use the infrastructure framework directly ... unless you find the &lt;code&gt;dom&lt;/code&gt; API to be easier.

 



&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/OSGidNn15vc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/3230897634360453718/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=3230897634360453718" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3230897634360453718?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3230897634360453718?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/OSGidNn15vc/tapestry-54-jquery-support-now-in-place.html" title="Tapestry 5.4: jQuery Support now in place" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2013/01/tapestry-54-jquery-support-now-in-place.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4NRHw_fSp7ImA9WhNREk4.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-4429901984542629390</id><published>2012-11-06T13:22:00.001-08:00</published><updated>2012-11-06T13:23:15.245-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-11-06T13:23:15.245-08:00</app:edited><title>How we can be more secure with mail-in ballots than touch screens</title><content type="html">&lt;p&gt;Yes, it's election day!  I didn't wait in line ... I live in Oregon, where balloting is done by mail. That's a much better system.

&lt;p&gt;Of course, people are suspicious of voting electronically, or by mail.  Somehow the mechanical interaction reassures people.  However, security has been an issue for the entire history of voting. Ballots can be "misplaced", or electronically stored values from modern voting machines can be invisibly tampered with, after the fact. Certainly, as it is election day, we're hearing stories about devices that make it hard to select specific candidates ... and the conspiracy theorists are certain that the fix is in. 

&lt;p&gt;It may be, but the cases I've heard of are simply bad touch screens. I wonder what the conspiracy theorists believe is going on when they attempt to withdraw $25 from an ATM and get $10 instead?

&lt;p&gt;Real attempts to fix the elections don't change what you enter; they change what is stored ... possibly long after you have left the voting booth. 

&lt;p&gt;I would say that having fewer centralized counting locations would be more secure. First of all, we could use (as we do in Oregon), optical scanners ... the same, basic, trusted technology that's used for the SATs.  

&lt;p&gt;Next, the process of opening the secure envelopes and feeding them to the scanners could be more easily monitored if it occurred in a few locations across each state, rather than in every polling location across the entire country. This process is subject to fraud, yes, but is probably more likely to be affected by simple incompetence.

&lt;p&gt;
With physical ballots, it is also possible to do a proper recount. Voting on a touch screen is too ephemeral for a recount to have any meaning. 

&lt;p&gt;Ultimately, there is no way to ensure that your vote is actually counted. However, I think we can come up with a way in which we can at least ensure that your ballot made it to the processing center.

&lt;p&gt;Imagine if the ballot mailed to your home had a unique bar code on it, as well as a "tear off" tab with the corresponding unique number. In addition, there is a section on the ballot where you could enter a four or six digit PIN code of your own choice.

&lt;p&gt;
Later, at home, you visit a web site: you provide your unique, randomly assigned ballot id as well as your own secret, personally-selected, PIN code. The government web site (built using open-source software, of course) could then identify the status of your vote, just like UPS can tell you where your package is.

&lt;p&gt;
Since PIN codes are ultimately guessable, I don't think I'd want the system to do more than confirm the processing of the ballot. However, to someone like me, it would go a lot further towards assuring me that, yes, my vote was counted than anything I've used in the past ... and certainly it would be far more reassuring than the buggy, mis-calibrated, poorly designed touch screen devices in use today.

&lt;p&gt;
Further, I like the idea of the vote taking shape over the course of a few days, or even a week. Vote by mail from a week before "election day". Maybe the ballots accumulate but are not counted until the end of the election itself ... again, something that the ballot status check outlined above would help to ensure.

&lt;p&gt;
The big weakness of this is privacy concern over linking your identity to a specific ballot. Who would be able to view your vote at a later date?  The answer to that should be: nobody; that is not only a sacred tradition, but the fact that your private vote can never be retrieved and used against you in any way at a later date is important to keep voting free and uncoerced.

&lt;p&gt;
So imagine if your ballot arrived with four or five unique id stickers, each five or ten digits long: you chose, say, three of them, in whatever order you like and place them on the ballot. Nothing else on the ballot could link that specific ballot to you ... but that now partially-random ballot id can still be tracked to the central counting location. Only you would have a link between your identity and the ballot. I leave it to someone smarter than me to determine just how many "stickers" are needed to ensure reasonably anonymity (and prevent any potential ballot id conflicts) but I think the basic concept is valid.
&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/88rPiw0JIRg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/4429901984542629390/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=4429901984542629390" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4429901984542629390?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4429901984542629390?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/88rPiw0JIRg/mail-in-ballots.html" title="How we can be more secure with mail-in ballots than touch screens" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/11/mail-in-ballots.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQAQXg5eCp7ImA9WhNTFko.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-4644312908385463401</id><published>2012-10-19T12:52:00.000-07:00</published><updated>2012-10-19T12:52:20.620-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-19T12:52:20.620-07:00</app:edited><title>Zeroing in on Tapestry 5.4</title><content type="html">&lt;p&gt;I've had just a bit of time this week to devote to furthering the work on Tapestry 5.4, and it feels like I'm near the turning point. You can follow the progress in the &lt;a href="https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=log;h=refs/heads/5.4-js-rewrite"&gt;5.4-js-rewrite branch&lt;/a&gt;

&lt;p&gt;I've &lt;a href="http://tapestryjava.blogspot.com/2011/11/tapestry-54-focus-on-javascript.html"&gt;discussed my plans before&lt;/a&gt;; this is a bit of a progress update.

&lt;p&gt;Basically, this release will be all about JavaScript:
&lt;ul&gt;
&lt;li&gt;Removing the hard-dependence on &lt;a href="http://prototypejs.org/"&gt;Prototype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Creating an abstraction layer, allowing &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; to be used instead&lt;/li&gt;
&lt;li&gt;Introducing AMD (&lt;a href="https://github.com/amdjs/amdjs-api/wiki/AMD"&gt;asynchronous module definition&lt;/a&gt;) modules (using &lt;a href="http://requirejs.org/"&gt;RequireJS&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Switching to a more declarative style of JavaScript; leveraging &lt;a href="http://ejohn.org/blog/html-5-data-attributes/"&gt;HTML5 data- attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Prototype vs. jQuery&lt;/h3&gt;

&lt;p&gt;Tapestry has historically used Prototype as what I call its &lt;strong&gt;foundation&lt;/strong&gt; framework. The foundation is responsible for element selection and creation, attaching event handlers, triggering events, and encapsulating Ajax requests.

&lt;p&gt;It is already possible to have both Prototype and jQuery on the same page; I do it regularly.  There's a project for this as well, &lt;a href="http://tapestry5-jquery.com/"&gt;tapestry-jquery&lt;/a&gt;. That being said, there's some costs in having multiple foundation frameworks in place:

&lt;p&gt;First of all is &lt;em&gt;size&lt;/em&gt;; there's a lot of code in each framework (160Kb for Prototype, 260Kb for jQuery -- much less after minification and compression), and tremendous overlap. This includes the fact that both frameworks have a version of the &lt;a href="http://sizzlejs.com/"&gt;Sizzle CSS selector engine&lt;/a&gt;.

&lt;p&gt;There's also some occasional clashes; I had a nasty problem where some &lt;a href="http://twitter.github.com/bootstrap"&gt;Bootstrap&lt;/a&gt; JavaScript was firing a "hide" event when a modal dialog was dismissed. After much tracing and hair pulling, I discovered that jQuery will treat a function attached to an element as an event handler for the event with the matching name.  The "hide" event triggered by jQuery found the &lt;code&gt;hide()&lt;/code&gt; method added by Prototype, and my modal dialog just winked out of existence, rather than animation the way I wanted.

&lt;p&gt;Finally, its not just an either-or world; there's also YUI, ExtJS, MooTools ... over the last few years, every single time I mentioned my desire to introduce an abstraction layer, I've received the question "will it support X?" and "X" was some new framework, that person's favorite, that I had not previously heard of.

&lt;p&gt;
So the abstraction layer will be important for helping ensure a fast download of JavaScript, and to minimize the amount of network and JavaScript execution overhead.

&lt;p&gt;
If you are an application developer, there will be nothing to prevent you from using the native framework of your choice, directly. In many cases, this will be the best approach, and it will enable greater efficiency, access to a more complete API, and access (in jQuery's case) to a huge community of plugins.

&lt;p&gt;
If you are creating reusable components, it is best to try to use the SPI (the service provider interface; the abstraction layer). This allows your library to be widely used without locking your users into any particular foundation framework.

&lt;h3&gt;Modules vs. Libraries&lt;/h3&gt;

&lt;p&gt;Tapestry 5.3 and earlier have provided some great support for JavaScript in library form. The concept of a &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/javascript/JavaScriptStack.html"&gt;JavaScriptStack&lt;/a&gt; makes it possible to bundle any number of individual JavaScript libraries together, along with supporting CSS style sheet files. Even better, Tapestry can create a single virtual JavaScript library by concatenating all the stack libraries together.  Tapestry does this at runtime, and conditionally ... so during development, you can work with many small JavaScript files and in production, they become one combined, minimized JavaScript file.

&lt;p&gt;
However, this JavaScript approach has its own problems. Page initialization logic is very dependent on the stacks (including the foundation frameworks) being present and loaded in a specific order.  No page initialization logic can execute until all libraries have been loaded.  If you have multiple stacks in production, then all the stacks must load before any logic executes.

&lt;p&gt;
All of this affects page load time, especially the time perceived by the end user. Too much is happening sequentially, and too much is happening over-all.

&lt;p&gt;In Tapestry 5.4, the focus is shifting from libraries to modules. Under AMD, a module is a JavaScript function that expresses a dependency on any number of other modules, and exports a value to other modules. The exported value may be a single function, or an object containing multiple functions (or other values).

&lt;p&gt;Here's a snapshot of one of modules, which provides support for the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/Zone.html"&gt;Zone&lt;/a&gt; component:

&lt;script src="https://gist.github.com/3919899.js?file=zone.coffee"&gt;&lt;/script&gt;


&lt;p&gt;The &lt;code&gt;define()&lt;/code&gt; function is used to define a module; the first parameter is an array of module names. The second parameter is a function invoked once all the dependencies have themselves been loaded; the parameters to &lt;em&gt;that&lt;/em&gt; function are the exports of the named modules.

&lt;p&gt;This function performs some initialization, attaching event handlers to the document object; it also defines and &lt;em&gt;exports&lt;/em&gt; a single named function ("deferredZoneUpdate") that may be used by other modules.

&lt;blockquote&gt;
Yes, I have a module named "_" which is &lt;a href="http://underscorejs.org/"&gt;Underscore.js&lt;/a&gt;.  I'll probably add "$" for jQuery. Why not have short names?
&lt;/blockquote&gt;

&lt;p&gt;The RequireJS library knows how to load individual modules and handle dependencies of those modules on others. Better yet, it knows how to do this &lt;em&gt;in parallel&lt;/em&gt;. In fact, in 5.4, the only JavaScript loaded using a traditional &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag is RequireJS; all other libraries and modules are loaded through it.

&lt;p&gt;
At the end of the day, there will be less JavaScript loaded, and the JavaScript that is loaded will be loaded in parallel. I'm still working on some of the details about how module libraries may be aggregated into stacks.


&lt;h3&gt;Declarative JavaScript&lt;/h3&gt;

&lt;p&gt;Even in earlier forms of Tapestry, JavaScript support has been powerful ... but clumsy. To do anything custom and significant on the client-side you had to:

&lt;ul&gt;
&lt;li&gt;Write a JavaScript library for your functionality&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Monkey_patch"&gt;Monkey-patch&lt;/a&gt; a function onto the &lt;code&gt;T5.initializers&lt;/code&gt; namespace&lt;/li&gt;
&lt;li&gt;Package up your initialization data as a &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/json/JSONObject.html"&gt;JSONObject&lt;/a&gt; ... typically, client element ids, URLs, etc.&lt;/li&gt;
&lt;li&gt;Have you Java component code invoke &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/javascript/JavaScriptSupport.html#addInitializerCall(java.lang.String, org.apache.tapestry5.json.JSONObject)"&gt;JavaScriptSupport.addInitializerCall(String, JSONObject)&lt;/a&gt; to link it all together.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tapestry packages up all those &lt;code&gt;addInitializerCall()&lt;/code&gt; initializations into one big block that executes at the bottom of the page (and does something similar for Ajax-oriented partial page updates).

&lt;p&gt;Whew!  To make this works requires that elements have unique ids (which can be a challenge when there are Ajax updates to the page). On top of that, the typical behavior is to create controller objects and attach event handlers directly to the elements; that works fine for small pages, but if there are hundreds (or thousands) of elements on the page, it turns into quite a burden on the JavaScript environment: lots of objects.

&lt;p&gt;
There isn't a specific name for this approach, beyond perhaps "crufty". Let's call it &lt;strong&gt;active&lt;/strong&gt; initialization.

&lt;p&gt;
A more modern approach is more &lt;em&gt;passive&lt;/em&gt;. In this style, the extra behavior is defined primarily in terms of events an element may trigger, and a top-level handler for that event. The events may be DOM related such as "click", "change", or "focus", or application-specific one triggered on the element directly.  The top-level handler, often attached to the document, handles the event when it bubbles up from the element to the top level; it often distinguishes which elements it is interested using a CSS selector that includes references to a data- attribute.

&lt;p&gt;For example, in the core/forms module, we need to track clicks on submit and image buttons as part of the form (this is related to supporting Ajax form submissions).

&lt;script src="https://gist.github.com/3920111.js?file=forms.coffee"&gt;&lt;/script&gt;

&lt;p&gt;That little bit of code attaches a "click" event handler to the document, for any submit or image element anywhere on the page ... or ever added to the page later via Ajax.  Older versions of Tapestry might have put an individual event handler on each such element, but in 5.4, the single event handler is sufficient.

&lt;p&gt;
Inside the event handler, we have access to the element itself, including data- attributes. That means that what may have been done using page initialization in Tapestry 5.3 may, in Tapestry 5.4, be a document-level event handler and data- attributes on the specific element; no JSONObject, no &lt;code&gt;addInitializerCall()&lt;/code&gt;. Less page initialization work, smaller pages, fewer objects at runtime.

&lt;blockquote&gt;
The flip side, however, is that the cost of triggering the extra events, and the cost of all the extra event bubbling, is hard to quantity. Still, the 5.3 approach introduces a lot of memory overhead at all times, whereas the 5.4 approach should at worst, introduce some marginal overhead when the user is actively interacting.
&lt;/blockquote&gt;

&lt;p&gt;
There's another advantage; by attaching your own event handlers to specific elements, you have a way to augment or replace behavior for specific cases. It's kind of a topsy-turvy version of inheritance, where the behavior of an element is, effectively, partially determined by the elements it is contained within.  Kind of crazy ... and kind of just like CSS.

&lt;h3&gt;Compatibility&lt;/h3&gt;

&lt;p&gt;So you've written a fancy application in Tapestry 5.3 and you are thinking about upgrading to 5.4 when it is ready ... what should you be ready for?

&lt;p&gt;On the server side, Tapestry 5.4 introduces a number of new services and APIs, but does not change very much that was already present.

&lt;blockquote&gt;
A number of interfaces and services that were deprecated in the past have been removed entirely; even some dependencies, such as &lt;a href="http://www.jboss.org/javassist"&gt;Javassist&lt;/a&gt;.
&lt;/blockquote&gt;

&lt;p&gt;
All built-in Tapestry components will operate through the SPI; they will work essentially the same regardless of whether you choose to operate using Prototype or jQuery or both.

&lt;p&gt;
The look-and-feel is changing from Tapestry's built-in CSS to Twitter Bootstrap. All of the old "t-" prefixed CSS classes are now meaningless.

&lt;p&gt;
On the other hand, what if you've built some complex client-side code?  Well, if it is based on Prototype directly, that will be fine as well; just keep Prototype in the mix and start migrating your components to use the SPI.

&lt;p&gt;
The devil in the details is all about the Tapestry and T5 namespaces.  The Tapestry object is the very oldest code; the T5 namespace was introduced in 5.2 in an attempt to organize things on the client-side a bit better.

&lt;p&gt;
It is not clear, at this time, just how much of those libraries will be left. Ideally, they will not be present at all unless an explicit compatibility mode is enabled (possibly, enabled by default). That being said, the conflict between the old active/crufty approach, and the new modular and passive/declarative approach is a difficult one to bridge. 

&lt;p&gt;
In fact, the point of this post is partly to spur a discussion on what level of compatibility is required and realistic.

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;I've completely pumped about where Tapestry 5.4 already is, and where it is headed. Although the future of web applications is on the client, there is still significant play for hybrid applications: partly page oriented, partly Ajax oriented &amp;ndash; and it's nice to have a single tool that integrates both approaches.

&lt;p&gt;Beyond that, even for a Tapestry application that is a single "page", what Tapestry brings to the table is still very useful:

&lt;ul&gt;
&lt;li&gt;live class reloading
&lt;li&gt;JavaScript stacks (with minification, Gzip compression, and client-side caching)
&lt;li&gt;the coming runtime CoffeeScript-to-JavaScript support
&lt;li&gt;best-of-breed exception reporting
&lt;li&gt;staggeringly high performance
&lt;/ul&gt;

&lt;p&gt;These benefits are all as compelling in a single page application as they are in a traditional application, if not more so. I'm looking forward to building even more impressive apps in 5.4 than I could accomplish in 5.3, and I hope a lot of you will join me.













 &lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/YuAUEE_o2xk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/4644312908385463401/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=4644312908385463401" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4644312908385463401?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4644312908385463401?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/YuAUEE_o2xk/zeroing-in-on-tapestry-54.html" title="Zeroing in on Tapestry 5.4" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>5</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/10/zeroing-in-on-tapestry-54.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EEQn84eyp7ImA9WhNTFEw.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-5400897592548541030</id><published>2012-10-15T11:07:00.000-07:00</published><updated>2012-10-16T12:26:43.133-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-10-16T12:26:43.133-07:00</app:edited><title>Not going to Clojure/Conj ... need a ticket?</title><content type="html">&lt;p&gt;Unfortunately, I will &lt;strong&gt;not be going to &lt;a href="http://clojure-conj.org"&gt;Clojure/Conj&lt;/a&gt; this year&lt;/strong&gt;.  And it is too late for a refund ... so I'm looking for someone to purchase my ticket at cost.  The good news is the reason I'm not going ... &lt;strong&gt;I have a new project coming that will use Tapestry, Clojure, and perhaps &lt;a href="http://www.datomic.com/"&gt;Datomic&lt;/a&gt;&lt;/strong&gt; and I need to be on-site with them the week of the conference.

&lt;p&gt;So if you are looking to attend the conj, you can take over my ticket, for &lt;strong&gt;$475&lt;/strong&gt;. Please contact me via email.  Thanks!

&lt;p&gt;
&lt;strong&gt;Update: Sorry, the ticket is gone.&lt;/strong&gt;&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/xsjSPwLnKBQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/5400897592548541030/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=5400897592548541030" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5400897592548541030?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5400897592548541030?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/xsjSPwLnKBQ/not-going-to-clojureconj-need-ticket.html" title="Not going to Clojure/Conj ... need a ticket?" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/10/not-going-to-clojureconj-need-ticket.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEAMRXc9fip7ImA9WhNUEk4.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-2803602071108044251</id><published>2012-09-14T10:12:00.001-07:00</published><updated>2013-01-03T09:06:24.966-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-01-03T09:06:24.966-08:00</app:edited><title>Stop sending template engines to the browser!</title><content type="html">&lt;p&gt;This &lt;a href="http://andyet.net/blog/2012/sep/13/stop-sending-template-engines-to-the-browser-a-ret/"&gt;article about client-side templating&lt;/a&gt; by Henrik Joreteg was quite interesting. In summary, it says don't send templates to the browser to be compiled and executed ... convert them to JavaScript functions that can be efficiently transfered to the browser and directly executed there.

&lt;p&gt;Currently, Tapestry does virtually all of its markup rendering on the server ... but with the &lt;a href="http://tapestryjava.blogspot.com/2011/11/tapestry-54-focus-on-javascript.html"&gt;upcoming changes to Tapestry for 5.4,&lt;/a&gt; that may start to shift, with increasing amounts of the presentation being rendered in-browser.

&lt;p&gt;
I'm already mid-way through building a module structure for Tapestry and I'm quite pleased with what I have so far. Part of that infrastructure is the ability to compile CoffeeScript to JavaScript on the fly. The tapestry-core module will provide the basic infrastructure, and a new module will provide the actual runtime compiler.

&lt;blockquote&gt;
I prefer for these kind of compilations and transformations to occur at runtime, rather than build time. It means no special build rules (in Maven or Gradle), no extra step when changing a file and reloading it in the browser, and it makes it easier in many ways for Tapestry to deliver the optimum bundle of compiled, aggregated, minified, and GZip-compressed JavaScript to the browser ... without having awkward conditionals and naming conventions to choose the correct version of each JavaScript file (not to mention ensuring that the minified version is derived from the correct non-minified version). Yes, there's a one-time startup cost, but that's always been a tradeoff I'm willing to make ... balanced by how much of Tapestry is lazy-loaded as needed.
&lt;/blockquote&gt;

&lt;p&gt;
However, the infrastructure is actually more flexible than that; it would be quite reasonable to do something like Henrick's solution: there's no reason why the infrastructure could not be used to read a template file and convert it to JavaScript source that can be exposed on the client as a module. 

&lt;p&gt;
That raises another interesting idea; a template-as-a-module would then be a uniform way to package pure data (a JSON object bundle of properties) with the name of a module that renders that specific data into markup.  That also opens up some terrific ways to have the server efficiently deliver updates to the client, in a data-focused way (rather than the markup-focused way provided by the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/Zone.html"&gt;Zone&lt;/a&gt; component).  Combine that with server-push, and you have something really powerful brewing together!
&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/RwrJZeIoh3k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/2803602071108044251/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=2803602071108044251" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2803602071108044251?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2803602071108044251?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/RwrJZeIoh3k/stop-sending-template-engines-to-browser.html" title="Stop sending template engines to the browser!" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/09/stop-sending-template-engines-to-browser.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8CSXY5cSp7ImA9WhJWEEs.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-2697320099403752447</id><published>2012-08-14T13:47:00.000-07:00</published><updated>2012-08-15T13:01:08.829-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-15T13:01:08.829-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="coffeescript" /><title>CoffeeScript Cautions</title><content type="html">&lt;p&gt;Over the last couple of months, I've coded nearly all my client-side code in &lt;a href="http://coffeescript.org"&gt;CoffeeScript&lt;/a&gt;. I prefer CoffeeScript to JavaScript ... but there are a few things to watch out for.  I thought I'd share some practical experience, rather than the more typical hype for CoffeeScript or the reactionary vibe against it.

&lt;p&gt;My biggest problems with CoffeeScript has been with indentation, which is no surprise, given the basic value proposition of the language. There are places where I've accidentally deleted a single space and completely changed the meaning of my code.

&lt;p&gt;
Here's an adapted example.  We'll start with the correct CoffeeScript source code, and JavaScript translation:

&lt;script src="https://gist.github.com/3352145.js"&gt; &lt;/script&gt;

&lt;p&gt;Now, notice what happens when we delete &lt;em&gt;a single space&lt;/em&gt;. 
  
&lt;script src="https://gist.github.com/3352159.js"&gt; &lt;/script&gt;

&lt;p&gt;The change in indentation confused the CoffeeScript compiler; it ended the call to &lt;code&gt;View.extend()&lt;/code&gt; and created a new expression for an object literal that is created and thrown away.

&lt;p&gt;And remember, this isn't hypothetical; I really did this, and started seeing very odd errors: undefined properties and events not getting triggered. It took me a bit of time, with the debugger, and adding console logging, and eventually some viewing of the generated JavaScript, to realize what had happened.

&lt;p&gt;Part of my solution to this class of issue, in Emacs, is to automatically increase the font size when editing CoffeeScript files. I may have to consider a switch from spaces to tabs as well, something that I've never considered in the past. I've also noticed that some IDEs, including IntelliJ, draw a vertical line to connect code at the same indentation layer. I don't have enough Emacs hacking experience to accomplish that, but I hope someone does.

&lt;p&gt;Another problem I've hit is with function invocation; CoffeeScript style is to omit the parenthesis following the function name, and just immediately start listing function parameters:

&lt;pre&gt;
element.setAttribute "width", "100%"
&lt;/pre&gt;

&lt;p&gt;
However, if your API allows chaining, you may be tempted to:

&lt;pre&gt;
element.setAttribute "width", "100%".setAttribute "color", "blue"
&lt;/pre&gt;

&lt;p&gt;Alas, the &lt;code&gt;.setAttribute&lt;/code&gt; binds to the immediate value, the string:

&lt;pre&gt;
element.setAttribute("width", "100%".setAttribute("color", "blue"));
&lt;/pre&gt;

&lt;p&gt;
One option is to enclose the first call with parenthesis:

&lt;pre&gt;
(element.setAttribute "width", "100%").setAttribute "color", "blue"
&lt;/pre&gt;

&lt;p&gt;I &lt;em&gt;want&lt;/em&gt; to like that option, as it stays consistent with the normal format for function invocation; it just captures the result for chaining.  However, each additional &lt;code&gt;setAttribute()&lt;/code&gt; call will require an additional nesting layer of parenthesis.  This leaves the following as the solution:

&lt;pre&gt;
element.setAttribute("width", "100%").setAttribute("color", "blue")
&lt;/pre&gt;

&lt;p&gt;And now we are stuck having to parse two different syntaxes for function invocation, depending on some very specific context.

&lt;p&gt;
Another issue is that, once you start using the standard function invocation style, which doesn't use parenthesis, you can easily get tripped up:

&lt;script src="https://gist.github.com/3352466.js"&gt; &lt;/script&gt;

Here the &lt;code&gt;is null&lt;/code&gt; bound tightly to the &lt;code&gt;"div"&lt;/code&gt; string literal; the result (true or false) was passed to the &lt;code&gt;find()&lt;/code&gt; method, rather than the expected &lt;code&gt;"div"&lt;/code&gt;.

&lt;p&gt;
Because of these subtleties, I seem to find myself using the live CoffeeScript to JavaScript preview to check that my guess at how the JavaScript will be generated is correct.  Preview is available on the CoffeeScript home page, and also as the CoffeeConsole plugin to Chrome.

&lt;p&gt;
What's interesting is that the above concerns aside, most of the time my first pass at the CoffeeScript does produce exactly what I'd expect, making me feel silly to waste the time to check it! Other times, I find that my first pass is actually too verbose, and can be simplified. Here is an example from some code I've written for Tapestry, a simple DSL for constructing DOM elements on the fly:

&lt;script src="https://gist.github.com/3352278.js?file=v1.coffee"&gt;&lt;/script&gt;

This is invoking the &lt;code&gt;builder()&lt;/code&gt; function, passing a string to define the element type and CSS class, then an object that adds attributes to the element, and lastly some additional values that define the body of the element: an array to define a nested element, and a string that will become literal text.  This is converted to JavaScript:

&lt;script src="https://gist.github.com/3352278.js?file=output.js"&gt;&lt;/script&gt;

&lt;p&gt;
Turns out, we can get rid of the curly braces on the objects without changing the output JavaScript; CoffeeScript is able to figure it all out by context:

&lt;script src="https://gist.github.com/3352278.js?file=v2.coffee"&gt;&lt;/script&gt;

&lt;p&gt;
Even getting rid of the commas is allowed. The commas are equivalent to the line break and indentation that's already present:

&lt;script src="https://gist.github.com/3352278.js?file=v3.coffee"&gt;&lt;/script&gt;


&lt;p&gt;
And that's where I start really liking CoffeeScript, because quite often, the code you write looks very close to a bespoke DSL.

&lt;p&gt;
Then there's the debugging issue; CoffeeScript scores poorly here. First of all, the code has been translated to JavaScript; line numbers at execution time will no longer line up with source CoffeeScript at all, so the first and most useful bit of information in a stack trace is no longer useful. It's very easy to glance back and forth to match the generated JavaScript back to source CoffeeScript, but it still takes some effort.

&lt;p&gt;
Of course, in a production application, you'll likely have minimized your JavaScript, which mangles your JavaScript and your line numbers far more than the CoffeeScript compilation does!

&lt;p&gt;
The other weakness is that all functions in CoffeeScript are anonymous; this takes &lt;em&gt;another&lt;/em&gt; useful bit of information out of stack traces: the local function name. This leaves the only way to map from compiled JavaScript back to source CoffeeScript as involving a lot of processing between your ears, and that's not exactly ideal, given everything else you have to mentally juggle.

&lt;p&gt;
Lastly, I've heard of people who think CoffeeScript is &lt;em&gt;only&lt;/em&gt; for programming using objects and classes. I find this quite odd; it's nice to have some optimized syntax for classes in CoffeeScript even if it comes at some price (the generated JavaScript can become quite verbose adding runtime support for the syntax that allows  super class methods and constructors to be invoked); however, far beyond 95% of the code I write is purely functional, with what state that's present captured in closures of local variables, they way it should be.

&lt;p&gt;
I like Scott Davis' summary: &lt;strong&gt;CoffeeScript will make you a better JavaScript programmer&lt;/strong&gt;.  It is, largely, JavaScript with better syntax, plus a number of new features. You should never think of CoffeeScript as an &lt;em&gt;alternative&lt;/em&gt; to understanding the occasionally ugly nuances of JavaScript; instead I think of it as a "have your cake and eat it too" situation. I certainly don't consider it &lt;strong&gt;noobscript&lt;/strong&gt;, the way many in the Node.js community seem to.

&lt;p&gt;At the end of the day, the translation from CoffeeScript to JavaScript is usually quite predictable, and turns into the JavaScript you probably &lt;em&gt;should&lt;/em&gt; write, but often &lt;em&gt;would not&lt;/em&gt;:  for instance, JavaScript that always evaluates inside a hygienic function, or where every variable is defined at the top of its containing block.

&lt;p&gt;
My final take is that you are trading the very rough edges present in the JavaScript language for some sensible tradeoffs, and the occasional hidden "gotcha".  I think that's a completely reasonable trade, and will continue to write as much as I can, client-side &lt;em&gt;and&lt;/em&gt; server-side, in CoffeeScript. 

&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/7WHG1QxmG7s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/2697320099403752447/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=2697320099403752447" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2697320099403752447?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2697320099403752447?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/7WHG1QxmG7s/coffeescript-cautions.html" title="CoffeeScript Cautions" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>6</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/08/coffeescript-cautions.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkICRn0-fip7ImA9WhJQGEs.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-221399820045087968</id><published>2012-08-01T17:48:00.002-07:00</published><updated>2012-08-01T17:49:27.356-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-01T17:49:27.356-07:00</app:edited><title>Tapestry 5 Book by Igor Drobiasko</title><content type="html">&lt;p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;div style="float: right; margin-left: 10px"&gt;
&lt;iframe src="http://www.indiegogo.com/project/188439/widget/951100" width="224px" height="429px" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
Is the one thing holding you back from embracing Tapestry the lack of a current book?  Well, that excuse is finally going by the wayside; Igor Drobiazko, a Tapestry contributor (second in activity only to me) is getting ready to finish and self-publish his book on Tapestry 5.3: &lt;strong&gt;Tapestry 5: Rapid Web Application Development in Java&lt;/strong&gt;

&lt;p&gt;
Want early access to the book? Join his &lt;a href="http://www.indiegogo.com/tapestry5book"&gt;funding campaign at indiegogo&lt;/a&gt;. 

&lt;p&gt;Igor hit his goal on day 1, but for as little as $25 you can get access to at least 400 pages of content as soon as the campaign ends, and the complete book when it's ready. You can also help assure that the book will be updated for the great improvements already cooking up in Tapestry 5.4.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/5ute1XY2oao" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/221399820045087968/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=221399820045087968" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/221399820045087968?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/221399820045087968?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/5ute1XY2oao/tapestry-5-book-by-igor-drobiasko.html" title="Tapestry 5 Book by Igor Drobiasko" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/08/tapestry-5-book-by-igor-drobiasko.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIGSXY-fSp7ImA9WhJQGEk.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-8390430420869221937</id><published>2012-08-01T09:59:00.001-07:00</published><updated>2012-08-01T10:02:08.855-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-01T10:02:08.855-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="async" /><category scheme="http://www.blogger.com/atom/ns#" term="node" /><title>More async: using auto() for parallel operations</title><content type="html">&lt;p&gt;One of the straw men that people often cite when discussing event-driven programming, ala &lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;, is the fear that complex server-side behavior will take the form of unmanageably complex, deeply nested callbacks.

&lt;p&gt;Although the majority of the code I've so far written with Node is very simple, usually involving only a single callback, my mental image of Node is of a request arriving, and kicking off a series of database operations and other asynchronous requests that all combine, in some murky fashion, into a single response.  I visualize such a request as a &lt;a href="http://en.wikipedia.org/wiki/Pinball"&gt;pinball&lt;/a&gt; 
dropping into some bumpers and bouncing around knocking down targets, until shooting out, back down to the flippers.

&lt;p&gt;Here's an example workflow from the sample application I'm building; I'm managing a set of images used in a slide show; so I have a SlideImage entity in &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt; (using &lt;a href="http://mongoosejs.com/"&gt;Mongoose&lt;/a&gt;), and each SlideImage references a file stored in &lt;a href="http://mongodb.github.com/node-mongodb-native/api-generated/gridstore.html"&gt;Mongo's GridFS&lt;/a&gt;.

&lt;p&gt;
When it comes time to delete a SlideImage, it is necessary to delete the GridFS file as well.  The pseudo-code for such an operation, in a non-event based system, might look something like:

&lt;script src="https://gist.github.com/3228254.js?file=gistfile1.txt"&gt;&lt;/script&gt;

&lt;p&gt;
Inside Node, where all code is event-driven and callback oriented, we should be able to improve on the pseudo-code by doing the deletes of the SlideImage document, and the GridFS file, in parallel. Well, that's the theory anyway, but I'd  normally stick to a &lt;a href="http://tapestryjava.blogspot.com/2012/07/a-little-gotcha-with-asynch-and-streams.html"&gt;waterfall approach&lt;/a&gt;, as tracking when all operations have completed would be tedious and error prone, especially in light of correctly handling any errors that might occur.

&lt;p&gt;
Enter &lt;a href="https://github.com/caolan/async/#autotasks-callback"&gt;async's &lt;code&gt;auto()&lt;/code&gt; function&lt;/a&gt;.  With auto(), you define a set of tasks that have dependencies on each other.  Each task is a function that receives a callback, and a results object. auto() figures out when each task is ready to be invoked.

&lt;p&gt;When a task fails, it passes the error to the callback function.  When a task succeeds, it passes null and a result value to the callback function.  Later executing tasks can see the results of prior tasks in the result object, keyed on the prior tasks's name.

&lt;p&gt;As with &lt;code&gt;waterfall()&lt;/code&gt;, a single callback is passed the error object, or the final result object.

&lt;p&gt;
Let's see how it all fits together, in five steps:
&lt;ul&gt;
&lt;li&gt;Find the SlideImage document&lt;/li&gt;
&lt;li&gt;Open the GridFS file&lt;/li&gt;
&lt;li&gt;Delete the GridFS file&lt;/li&gt;
&lt;li&gt;Delete the SlideImage document&lt;/li&gt;
&lt;li&gt;Send a response (success or failure) to the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The granularity here is partly driven by the specific APIs and their callbacks. 

&lt;p&gt;The code for this is surprisingly tight:

&lt;script src="https://gist.github.com/3228429.js?file=api.coffee"&gt;&lt;/script&gt;

&lt;p&gt;auto() is passed two values; an object that maps keys to arrays, and the final callback.  Each array consists of the names of dependencies for the task, followed by the task function (you can just specify the function if a task has no dependencies, but I prefer the consistency of each entry being an array).

&lt;p&gt;
So &lt;code&gt;find&lt;/code&gt; has no dependencies, and kicks of the overall process. I think it is really significant how consistent Node.js APIs are: the basic callback consisting of an error and a result makes it very easy to integrate code from many different libraries and authors (I think there's a kind of &lt;a href="http://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;Monad&lt;/a&gt; hidden in there). In the code, the callback created by &lt;code&gt;auto()&lt;/code&gt;, and passed to &lt;code&gt;find&lt;/code&gt;, is perfectly OK to pass into &lt;code&gt;findById&lt;/code&gt;. It's all low impedance: no need to write any kind of shim or adapter.

&lt;p&gt;
The later tasks take the additional &lt;code&gt;results&lt;/code&gt; parameter; &lt;code&gt;results.find&lt;/code&gt; is the SlideImage document provided by the &lt;code&gt;find&lt;/code&gt; task.

&lt;p&gt;
The &lt;code&gt;remove&lt;/code&gt; and &lt;code&gt;openFile&lt;/code&gt; tasks both depend on &lt;code&gt;find&lt;/code&gt;: they will run in no particular order after &lt;code&gt;find&lt;/code&gt;; more importantly, their callbacks will be invoked in no predictable order, based on when the various asynchronous operations complete.

&lt;p&gt;
Only once all tasks have executed (or one task has passed an error to its callback), does the final callback get invoked; this is what sends a 500 error to the client, or a 200 success (with a &lt;code&gt;{ "result" : "ok" }&lt;/code&gt; JSON response.

&lt;p&gt;
I think this code is both readable, and concise; in fact, I can't imagine it being much more concise. My brain is starting to really go parallel: part of my brain is evaluating everything in terms of Java code and idioms while the rest is embracing JavaScript and CoffeeScript and Node.js idioms; the Java part is impressed by the degree to which these JavaScript solutions eschew complex APIs in favor of working on a specific "shape" of data; if I was writing something like this in Java, I'd be up to my ears in fluid interfaces and hidden implementations, with a ton of code to write and test.

&lt;p&gt;
I'm not sure that the application I'm writing will have any processing workflows significantly more complex than this bit, but if it does, I'm quite relieved to have auto() in my toolbox.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/XqxA47qqyno" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/8390430420869221937/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=8390430420869221937" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/8390430420869221937?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/8390430420869221937?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/XqxA47qqyno/more-async-using-auto-for-parallel.html" title="More async: using auto() for parallel operations" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/08/more-async-using-auto-for-parallel.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MGSXc9eSp7ImA9WhJQGU8.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-1436237589015843606</id><published>2012-07-30T09:26:00.000-07:00</published><updated>2012-08-02T09:37:08.961-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-08-02T09:37:08.961-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="async" /><category scheme="http://www.blogger.com/atom/ns#" term="node" /><category scheme="http://www.blogger.com/atom/ns#" term="leaky abstractions" /><title>A little Gotcha with asynch and Streams</title><content type="html">&lt;p&gt;I stumbled across a little gotcha using &lt;a href="https://github.com/caolan/async/#waterfall"&gt;async&lt;/a&gt; with Node.js &lt;a href="http://nodejs.org/api/stream.html"&gt;Streams&lt;/a&gt;: you can easily corrupt your output if you are not careful.

&lt;blockquote&gt;
Node.js Streams are an abstraction of Unix pipes; they let you push or pull data a little bit at a time, never keeping more in memory than its needed.  async is a library used to organize all the asynchronous callbacks used in node applications without getting the kind of "Christmas Tree" deep nesting of callbacks that can occur too easily.
&lt;/blockquote&gt;

&lt;p&gt;
I'm working on a little bit of code to pull an image file, stored in &lt;a href="http://mongodb.github.com/node-mongodb-native/api-generated/gridstore.html"&gt;MongoDB GridFS&lt;/a&gt;, scale the image using &lt;a href="https://github.com/rsms/node-imagemagick#readme"&gt;ImageMagick&lt;/a&gt;, then stream the result down to the browser.

&lt;p&gt;
My first pass at this didn't use ImageMagick or streams, and worked perfectly ... but as soon as I added in the use of async (even before adding in ImageMagick), I started getting broken images in the browser, meaning that my streams were getting corrupted.

&lt;p&gt;
Before adding async, my code was reasonable:

&lt;script src="https://gist.github.com/3191108.js"&gt; &lt;/script&gt;

&lt;p&gt;
However, I knew I was going to add a few new steps here to pipe the file content through ImageMagick; that's when I decided to check out the async module.

&lt;p&gt;
The logic for handling this request is a &lt;em&gt;waterfall&lt;/em&gt;; each step kicks off some work, then passes data to the next step via an asynchronous callback.  The async library calls the steps "tasks"; you pass an array of these tasks to &lt;code&gt;async.waterfall()&lt;/code&gt;, along with the end-of-waterfall callback.  This special callback may be passed an error provided by any task, or the final result from the final task.

&lt;p&gt;
With  &lt;code&gt;waterfall()&lt;/code&gt;, each task is passed a special callback function.  If the callback function is passed a non-null error as the first parameter, then remaining tasks are skipped, and the final result handler is invoked immediately, to handle the error.

&lt;p&gt;
Otherwise, you pass null as the first parameter, plus any additional result values.  The next task is passed the result values, plus the next callback.  It's all very clever.

&lt;p&gt;
My first pass was to duplicate the behavior of my original code, but to do so under the async model.  That means lots of smaller functions; I also introduced an extra step between getting the opened file and streaming its contents to the browser.  The extra step is intended for later, where ImageMagick will get threaded in.

&lt;p&gt;The code, despite the extra step, was quite readable:

&lt;script src="https://gist.github.com/3191123.js?file=index.coffee"&gt;&lt;/script&gt;

&lt;p&gt;
My style is to create local variables with each function; so &lt;code&gt;openFile&lt;/code&gt; kicks off the process; once the file has been retrieved from MongoDB, the &lt;code&gt;readFileContents&lt;/code&gt; task will be invoked ... unless there's an error, in which case &lt;code&gt;errorCallback&lt;/code&gt; gets invoked immediately.

&lt;p&gt;
Inside &lt;code&gt;readFileContents&lt;/code&gt; we convert the file to a stream with &lt;code&gt;file.stream(true)&lt;/code&gt; (the &lt;code&gt;true&lt;/code&gt; means to automatically close the stream once all of the file contents have been read from GridFS).

&lt;p&gt;
&lt;code&gt;streamToClient&lt;/code&gt; comes next, it takes that stream and pipes it down to the browser via the &lt;code&gt;res&lt;/code&gt; (response) object.

&lt;p&gt;
So, although its now broken up into more small functions, the logic is the same, as expressed on the very last line:  open the file, read its contents as a stream, stream the data down to the client.

&lt;p&gt;
However, when I started testing this before moving on to add the image scaling step, &lt;strong&gt;it no longer worked&lt;/strong&gt;. The image data was corrupted.  I did quite a bit of thrashing: adding log messages, looking at library source, guessing, and experimenting (and I did pine for a real debugger!).

&lt;p&gt;
Eventually, I realized it came down to this bit of code from the async module:

&lt;script src="https://gist.github.com/3191135.js?file=async.js"&gt;&lt;/script&gt;

&lt;p&gt;The code on line 7 is the callback function passed to each task; notice that once it decides what to do, on line 21 it defers the execution until the "next tick".

&lt;p&gt;
The root of the problem was simply that the "next tick" was a little too late.  By the time the next tick came along, and &lt;code&gt;streamToClient&lt;/code&gt; got invoked, the first chunk of data had already been read from MongoDB ... but since the call to &lt;code&gt;pipe()&lt;/code&gt; had not executed yet, it was simply discarded.  The end result was that the stream to the client was missing a chunk at the beginning, or even entirely empty.

&lt;p&gt;The solution was to break things up a bit differently, so that the call to &lt;code&gt;file.stream()&lt;/code&gt; happens inside the same task as the call to &lt;code&gt;stream.pipe()&lt;/code&gt;.  

&lt;p&gt;
So that's our &lt;a href="http://en.wikipedia.org/wiki/Leaky_abstraction"&gt;Leaky Abstraction&lt;/a&gt; for today; what looked like an immediate callback was deferred just enough to change the overall behavior.  And that, in Node, anything that can be deferred, will be deferred, since that makes the overall application that much zippier.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/Svm6kkMGoU4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/1436237589015843606/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=1436237589015843606" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1436237589015843606?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1436237589015843606?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/Svm6kkMGoU4/a-little-gotcha-with-asynch-and-streams.html" title="A little Gotcha with asynch and Streams" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/07/a-little-gotcha-with-asynch-and-streams.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcCQ34yfCp7ImA9WhJSEkg.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-1624807543117541684</id><published>2012-07-02T10:27:00.001-07:00</published><updated>2012-07-02T10:27:42.094-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-02T10:27:42.094-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="datomic" /><category scheme="http://www.blogger.com/atom/ns#" term="immutability" /><category scheme="http://www.blogger.com/atom/ns#" term="clojure" /><title>You Cannot Correctly Represent Change Without Immutability</title><content type="html">&lt;p&gt;The title of this blog post is a quote by Rich Hickey, talking about the &lt;a href="http://datomic.com/"&gt;Datomic&lt;/a&gt; database. Its a beautiful statement, at once illuminating and paradoxical. It drives at the heart of the design of both Clojure and Datomic, and embraces the difference between identity and state.

&lt;p&gt;
What is change?  That seems like an obvious question, but my first attempt at defining it was "some  &lt;em&gt;change&lt;/em&gt; to a quantifiable set of qualities about some object."  Woops, I used &lt;em&gt;change&lt;/em&gt; recursively there ... that's not going to help.

&lt;p&gt;
In the real world, things change in ways we can observe; the leaf falls from the tree, the water in the pot boils, the minute hand moves ever forward. 

&lt;p&gt;
How do we recognize that things have changed?  We can, in our memories, remember a prior state. We remember when the leaf was green and attached to a branch; we remember when the water came out of the tap, and we remember looking at the clock a few minutes ago. We can hold both states in our mind at the same time, and compare them.

&lt;p&gt;
How do we represent change in traditional, object-oriented technologies?  Well, we have fields (or columnus) and we change the state in place:  
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;leaf.setColor(BROWN).detachFromTree()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPDATE LEAVES SET COLOR = 'BROWN' WHERE ID = ?ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;water.setTemperature(212)&lt;/code&gt;&lt;/li&gt; 
&lt;li&gt;or we see time advancing via &lt;code&gt;System.currentTimeMillis()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Here's the challenge: given an object, how do you ask it about its prior state?  Can you ask &lt;code&gt;leaf.getTreeDetachedFrom()&lt;/code&gt;? Generally, you can't unless you've gone to some herculean effort: the new state overwrites the old state in place.

&lt;p&gt;
When Rich talks about conflating state with identity, this is what he means. With the identity and state conflated, then after the change in state, the leaf will &lt;em&gt;now-have-always-been&lt;/em&gt; fallen from the tree, the water will &lt;em&gt;now-have-always-been&lt;/em&gt; boiled, and the clock will &lt;em&gt;now-eternally&lt;/em&gt; be at 9:49 AM.

&lt;p&gt;
What Clojure does in memory, and Datomic does in the database, is split identity and state.  We end up with &lt;code&gt;leaf&lt;sub&gt;1&lt;/sub&gt;&lt;/code&gt; as &lt;code&gt;{:id "a317a439-50bb-4d37-838a-c8eef289e22f" :color :green :attached-to maple-tree}&lt;/code&gt; and &lt;code&gt;leaf&lt;sub&gt;2&lt;/sub&gt;&lt;/code&gt; as &lt;code&gt;{:id "a317a439-50bb-4d37-838a-c8eef289e22f" :color :brown :on-ground true}&lt;/code&gt;.  The id is the same, but the other attributes can vary.

&lt;p&gt;
With immutability, changes in state are really new objects; a new &lt;em&gt;version&lt;/em&gt;, or "quantifiable set of qualities", that does not affect the original version.  It is possible to compare two different iterations of the same object to see the "deltas". In Datomic, you even have more meta-data about when such state changes occur, what else changed within the same transaction, and who is the responsible party for that transaction.


&lt;p&gt;The essence here is not to think of an object as a set of slots you can put new data into. Instead, think of it as a time-line of different configurations of the object. The fact that late in the time-line, the leaf has fallen from the tree does not affect the fact that earlier on the time-line, the leaf was a bud on a branch. The identity of the leaf transcends all those different states.

&lt;p&gt;
In the past, I've built systems that required some of the features that Datomic provides; for example, being able to reconstruct the state of the entire database at some prior time, and strong auditing of what changes occurred to what entities at a specific time (or transaction). Rich knows that others have hit this class of problem; part of his selling point is to ask "and who really understands that query" (the one that reconstructs prior state). He knows people have done it, but he also knows no one is very happy about its performance, correctness, or maintainability ... precisely because traditional databases don't understand mutability: they live in that eternal-now, and drag your application into the same world view.

&lt;p&gt;
That's why I'm excited by Datomic; it embraces this key idea: &lt;strong&gt;separate identity from state by leveraging immutability&lt;/strong&gt; and from the ensuing design, much goodness is an automatic by-product. Suddenly, we start seeing much of what we take as dogma when developing database-driven applications to be kludges on top of an unstable central idea: mutable state.

&lt;p&gt;For example:  read transactions are a way to gain stable view of interrelated data even as the data is being changed (in place); with Datomic, you always have a stable view of all data, because you operate on an immutable view of the entire database at some instance in time.  Other transactions may add, change, or replace Datoms in the database, but any code that is reading from the database will be completely unaware of those changes, even as they lazily navigate around the entire database.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/sCdeRVuDazA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/1624807543117541684/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=1624807543117541684" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1624807543117541684?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1624807543117541684?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/sCdeRVuDazA/you-cannot-correctly-represent-change.html" title="You Cannot Correctly Represent Change Without Immutability" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>8</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/07/you-cannot-correctly-represent-change.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcAQn0yeip7ImA9WhJTGUQ.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-4439837231461339104</id><published>2012-06-29T10:14:00.000-07:00</published><updated>2012-06-29T10:14:03.392-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-29T10:14:03.392-07:00</app:edited><title>Gradle CoffeeScript Compilation</title><content type="html">&lt;p&gt;As I'm working on &lt;a href="http://tapestry.apache.org/javascript-rewrite.html"&gt;rebooting JavaScript support in Tapestry&lt;/a&gt; one of my goals is to be able to author Tapestry's JavaScript as CoffeeScript. Tapestry will ultimately have a runtime option to dynamically compile CoffeeScript to JavaScript, but I don't want that as a dependency of the core library; that means I need to be able to have that compilation (transpilation?) occur at build time.

&lt;p&gt;Fortunately, this is the kind of thing &lt;a href="http://gradle.org/"&gt;Gradle&lt;/a&gt; does well!  My starting point for this is the &lt;a href="http://code.google.com/p/wro4j/"&gt;Web Resource Optimizer for Java&lt;/a&gt; project, which includes a lot of code, often leveraging Rhino (for JavaScript) or JRuby, to do a number of common web resource processing, including CoffeeScript to JavaScript.  WRO4J has an Ant task, but I wanted to build something more idiomatic to Gradle.

&lt;p&gt;Here's what I've come up with so far.  This is a external build script, separate from the project's main build script:

&lt;script src="https://gist.github.com/3019081.js?file=coffeescript.gradle"&gt;&lt;/script&gt;

&lt;p&gt;The task finds all &lt;code&gt;.coffee&lt;/code&gt; files in the input directory (ignoring everything else) and generates a corresponding &lt;code&gt;.js&lt;/code&gt; file in the output directory.

&lt;p&gt;
The @InputDirectory and @OutputDirectory annotations allows Gradle to decide when the task's action is needed:  If any file changed in the directories provided by these methods then the Task must be rerun.  Gradle doesn't tell us exactly what changed, or create the directories, or anything ... that's up to us.

&lt;p&gt;
Since I only expect to have a handful of CoffeeScript files, the easiest thing to do was to simply delete the output directory and recompile all CoffeeScript input files on &lt;em&gt;any&lt;/em&gt; change.  The @TaskAction annotation directs Gradle to invoke the doCompile() method when inputs (or outputs) have changed since the previous build.

&lt;p&gt;
It's enlightening to note that the &lt;code&gt;visit&lt;/code&gt; passed to the closure on line 34 is, in fact, a &lt;a href="http://gradle.org/docs/current/javadoc/org/gradle/api/file/FileVisitDetails.html"&gt;FileVisitDetails&lt;/a&gt;, which makes it really easy to, among other things, work out the output file based on the relative path from the source directory to the input file.

&lt;p&gt;
One of my stumbling points was setting up the classpath to pull in WRO4J and its dependencies; the &lt;code&gt;buildscript&lt;/code&gt; configuration on line 4 is &lt;em&gt;specific to this single build script&lt;/em&gt;, which is very obvious once you've worked it out. This is actually excellent for re-use, as it means that minimal changes are needed to the &lt;code&gt;build.gradle&lt;/code&gt; that makes use of this build script.  Earlier I had, incorrectly, assumed that the main build script had to set up the classpath for any external build scripts.

&lt;p&gt;
Also note line 54; the CompileCoffeeScript task must be exported from this build script to the project, so that the project can actually make use of it.

&lt;p&gt;
The changes to the project's &lt;code&gt;build.gradle&lt;/code&gt; build script are satisfyingly small:

&lt;script src="https://gist.github.com/3019176.js?file=build.gradle"&gt;&lt;/script&gt;

&lt;p&gt;The &lt;code&gt;apply from:&lt;/code&gt; brings in the CompileCoffeeScript task. We then use that class to define a new task, using the defaults for srcDir and outputDir.

&lt;p&gt;The last part is &lt;em&gt;really interesting&lt;/em&gt;: We are taking the existing processResources task and adding a new input directory to it ... but rather than explicitly talk about the directory, we simply supply the task. Gradle will now know to make the compileCoffeeScript task a dependency of the processResources task, and add the task's output directory (remember that @OutputDirectory annotation?) as another source directory for processResources. This means that Gradle will seamlessly rebuild JavaScript files from CoffeeScript files, and those JavaScript files  will then be included in the final WAR or JAR (or on the classpath when running test tasks).

&lt;p&gt;
Notice the things I didn't have to do:  I didn't have to create a plugin, or write any XML, or seed my Gradle extension into a repository, or even come up with a fully qualified name for the extension.  I just wrote a file, and referenced it from my main build script. Gradle took care of the rest.

&lt;p&gt;There's room for some improvement here; I suspect I could do a little hacking to change the way compilation errors are reported (right now it is just a big ugly stack trace).  I could use a thread pool to do compilation in parallel. I could even back away from the delete-the-output-directory-and-recompile-all approach ... but for the moment, what I have works and is fast enough.

&lt;p&gt;
Luke Daly leaked that there will be an experimental CoffeeScript compilation plugin coming in 1.1 so I probably won't waste more cycles on this. For only a couple of hours of research and experimentation I was able to learn a lot, and get something really useful put together, and the lessons I've learned mean that the next one of these I do will be even easier!&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/Qxs91oOZi2Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/4439837231461339104/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=4439837231461339104" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4439837231461339104?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/4439837231461339104?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/Qxs91oOZi2Y/gradle-coffeescript-compilation.html" title="Gradle CoffeeScript Compilation" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/06/gradle-coffeescript-compilation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEUARXk4fCp7ImA9WhJTGUU.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-6968611000110788753</id><published>2012-06-29T09:09:00.000-07:00</published><updated>2012-06-29T09:10:44.734-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-29T09:10:44.734-07:00</app:edited><title>Getting CrashPlan to work on Mac after JDK 1.7 upgrade</title><content type="html">&lt;p&gt;
After upgrading my Mac to JDK 1.7, I noticed that &lt;a href="http://www.crashplan.com/"&gt;CrashPlan&lt;/a&gt; stopped working.  Given just how much trouble I got in a ways back when I lost a month's worth of pictures and videos of my son I'm now a belt and suspenders kind of backup guy ... the belt may be TimeMachine, but the suspenders are CrashPlan.

&lt;p&gt;
After working it out with their technical staff, it turns out CrashPlan is not compatible with JDK 1.7 on Mac which is rather unfortunate.  And, having installed JDK 1.7, it was trying to use that version!

&lt;p&gt;
Getting it working is pretty easy however; most of the information is available in their &lt;a href="http://support.crashplan.com/doku.php/recipe/stop_and_start_engine"&gt;recipe for starting and stopping the CrashPlan engine&lt;/a&gt;.  The key part is that the com.crashplan.engine.plist file includes an entry where you can specify the path to the Java executable; with a bit of experimentation to find the right path, it appears to be working.

&lt;p&gt;
The only change I made to the file was to change the path; the first entry for the &lt;code&gt;ProgramArguments&lt;/code&gt; key:

&lt;script src="https://gist.github.com/3018800.js?file=com.crashplan.engine.plist"&gt;&lt;/script&gt;

&lt;p&gt;You will, of course, need to &lt;code&gt;sudo&lt;/code&gt; to edit the file and to stop and relaunch the CrashPlan engine.  Now, having set this up (and, coincidentally, renewed by annual subscription) my computer is happily chugging away, making the backups.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/U7CWtoWzM7E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/6968611000110788753/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=6968611000110788753" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/6968611000110788753?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/6968611000110788753?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/U7CWtoWzM7E/getting-crashplan-to-work-on-mac-after.html" title="Getting CrashPlan to work on Mac after JDK 1.7 upgrade" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>4</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/06/getting-crashplan-to-work-on-mac-after.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEQDQXk_cCp7ImA9WhVaEUo.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-9113591888892380960</id><published>2012-06-08T09:16:00.000-07:00</published><updated>2012-06-08T09:19:30.748-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-08T09:19:30.748-07:00</app:edited><title>Latency numbers every programmer should know</title><content type="html">&lt;p&gt;This is interesting stuff; &lt;a href="http://jonasboner.com/"&gt;Jonas Bonér&lt;/a&gt; organized some general some latency data by &lt;a href="http://norvig.com/21-days.html#answers"&gt;Peter Norvig&lt;/a&gt; as a Gist, and others expanded on it.  What's interesting is how, scaling time up by a billion, converts a CPU instruction cycle into approximately one heartbeat, and yields a disk seek time of "a semester in university".

&lt;script src="https://gist.github.com/2843375.js"&gt; &lt;/script&gt;

&lt;img src="https://a248.e.akamai.net/camo.github.com/77f72259e1eb58596b564d1ad823af1853bc60a3/687474703a2f2f692e696d6775722e636f6d2f6b307431652e706e67"/&gt;


&lt;blockquote&gt;
This is easier read on &lt;a href="https://gist.github.com/2843375"&gt;the original Gist page&lt;/a&gt;. Sorry about my blog's narrow formatting.
&lt;/blockquote&gt;

&lt;p&gt;
Another view of this data is &lt;a href="http://prezi.com/pdkvgys-r0y6/latency-numbers-for-programmers-web-development/"&gt;as an animated presentation&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/JHujjTAklFI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/9113591888892380960/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=9113591888892380960" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/9113591888892380960?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/9113591888892380960?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/JHujjTAklFI/latency-numbers-every-programmer-should.html" title="Latency numbers every programmer should know" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/06/latency-numbers-every-programmer-should.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUIGQnozfSp7ImA9WhVaF0Q.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-5959616301150738298</id><published>2012-06-06T11:34:00.001-07:00</published><updated>2012-06-15T13:52:03.485-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-15T13:52:03.485-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tapestry" /><category scheme="http://www.blogger.com/atom/ns#" term="performance" /><category scheme="http://www.blogger.com/atom/ns#" term="synchronized" /><category scheme="http://www.blogger.com/atom/ns#" term="lock" /><category scheme="http://www.blogger.com/atom/ns#" term="throughput" /><category scheme="http://www.blogger.com/atom/ns#" term="concurrency" /><title>Synchronized Considered Harmful</title><content type="html">&lt;p&gt;New flash: concurrency is &lt;strong&gt;hard&lt;/strong&gt;.  Any time you have mutable data and multiple threads, you are just asking for abuse, and &lt;code&gt;synchronized&lt;/code&gt; is simply not going to be your savior ... but the concurrency facilities of JDK 1.5 just might be.

&lt;p&gt;I was recently contacted by a client who was load testing their &lt;a href="http://tapestry.apache.org"&gt;Tapestry 5.3.3&lt;/a&gt; application; they were using Tomcat 6.0.32 with 500 worker threads, on a pretty beefy machine: Intel Xeon X7460 @ 2.66Ghz, OpenJDK 64-Bit Server VM (14.0-b16, mixed mode). That's a machine with six cores, and 16 MB of L2 cache.

&lt;p&gt;For all that power, they were tapping out at 450 requests per second.  That's not very good when you have 500 worker threads ... it means that you've purchased memory and processing power just to see all those worker threads block, and you get to see your CPU utilization stay low. When synchronization is done properly, increasing the load on the server should push CPU utilization to 100%, and response time should be close to linear with load (that is to say, all the threads should be equally sharing the available processing resources) until the hard limit is reached.

&lt;p&gt;Fortunately, these people approached me not with a vague performance complaint, but with a detailed listing of thread contention hotspots.

&lt;p&gt;
The goal with Tapestry has always been to build the code &lt;em&gt;right&lt;/em&gt; initially, and optimize the code later if needed. I've gone through several cycles of this over the past couple of years, optimizing page construction time, or memory usage, or throughput performance (as here). In general, I follow &lt;a href="http://www.briangoetz.com/"&gt;Brian Goetz&lt;/a&gt;'s advice: write simple, clean, code and let the compiler and Hotspot figure out the rest.

&lt;p&gt;
Another piece of advice from Brian is that "uncontested synchronized calls are very cheap".  Many of the hotspots located by my client were, in fact, simple synchronized methods that did some lazy initialization.  Here's an example:

&lt;script src="https://gist.github.com/2883501.js?file=gistfile1.java"&gt;&lt;/script&gt;

&lt;p&gt;In this example, getting the messages can be relatively time consuming and expensive, and is often not necessary at all. That is, in most instances of the class, the &lt;code&gt;getMessages()&lt;/code&gt; method is never invoked.  There were a bunch of similar examples of optional things that are often not needed ... but can be heavily used in the cases where they are used.

&lt;p&gt;
It turns out that "uncontested" really means &lt;b&gt;uncontested&lt;/b&gt;:  You better be sure that no two threads are ever hitting synchronized methods of the same instance at the same time. I chatted with Brian at the Hacker Bed &amp;amp; Breakfast about this, and he explained that you can quickly go from "extremely cheap" to "asymptotically expensive" when there's any potential for contention.  The &lt;code&gt;synchronized&lt;/code&gt; keyword is very limited in one area: when exiting a synchronized block, &lt;em&gt;all&lt;/em&gt; threads that are waiting for that lock must be unblocked, but only one of those threads gets to take the lock; all the others see that the lock is taken and go back to the blocked state.  That's not just a lot of wasted processing cycles: often the context switch to unblock a thread also involves paging memory off the disk, and that's &lt;a href="https://gist.github.com/2844153"&gt;very, very, expensive&lt;/a&gt;. 

&lt;p&gt;
Enter &lt;a href="http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.WriteLock.html"&gt;ReentrantReadWriteLock&lt;/a&gt;: this is an alternative that allows any number of &lt;em&gt;readers&lt;/em&gt; to share a lock, but only a single writer.  When a thread attempts to acquire the write lock, the thread blocks until all reader threads have released the read lock. The cost of managing the ReentrantReadWriteLock's state is somewhat higher than &lt;code&gt;synchronized&lt;/code&gt;, but has the huge advantage of letting multiple reader threads operate simultaneously. That means much, much higher throughput.

&lt;p&gt;
In practice, this means you must acquire the shared read lock to look at a field, and acquire the write lock in order to change the field.

&lt;p&gt;ReentrantReadWriteLock is smart about only waking the right thread or threads when either the read lock or the write lock is released. You don't see the same thrash you would with &lt;code&gt;synchronized&lt;/code&gt;:  if a thread is waiting for the write lock, and another thread releases it, ReentrantReadWriteLock will (likely) just unblock the one waiting thread.

&lt;p&gt;
Using &lt;code&gt;synchronized&lt;/code&gt; is easy; with an explicit ReentrantReadWriteLock there's a lot more code to manage: 

&lt;script src="https://gist.github.com/2883552.js?file=gistfile1.java"&gt;&lt;/script&gt;

&lt;p&gt;I like to avoid nested &lt;code&gt;try ... finally&lt;/code&gt; blocks, so I broke it out into seperate methods.

&lt;p&gt;
Notice the "lock dance":  it is not possible to acquire the write lock if any thread, even the current thread, has the read lock.  This opens up a tiny window where some other thread might pop in, grab the write lock and initialize the messages field.  That's why it is desirable to double check, once the write lock has been acquired, that the work has not already been done.

&lt;p&gt;Also notice that things aren't quite symmetrical: with ReentrantReadWriteLock it is allowable for the current thread to acquire the read lock before releasing the write lock. This helps to minimize context switches when the write lock is released, though it isn't expressly necessary.

&lt;p&gt;
Is the conversion effort worth it?  Well, so far, simply by converting &lt;code&gt;synchronized&lt;/code&gt; to ReentrantReadWriteLock, adding a couple of additional caches (also using ReentrantReadWriteLock), plus some work optimizing garbage collection by expanding the eden space, we've seen some significant improvements; from &lt;strong&gt;450 req/sec&lt;/strong&gt; to &lt;strong&gt;2000 req/sec&lt;/strong&gt; ... and there's still a few minor hotspots to address.  I think that's been worth a few hours of work!&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/1P5p4cIAfgk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/5959616301150738298/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=5959616301150738298" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5959616301150738298?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/5959616301150738298?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/1P5p4cIAfgk/synchronized-considered-harmful.html" title="Synchronized Considered Harmful" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>6</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/06/synchronized-considered-harmful.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMNSHk4eCp7ImA9WhVbGU8.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-1573063213342735034</id><published>2012-06-05T13:16:00.000-07:00</published><updated>2012-06-05T13:18:19.730-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-05T13:18:19.730-07:00</app:edited><title>Things I Learned at Hacker Bed &amp; Breakfast</title><content type="html">&lt;ul&gt;
&lt;li&gt;You can spend any amount of money on Scotch, and it keeps getting better&lt;/li&gt;
&lt;li&gt;A Java instance field that is assigned exactly once via lazy initialization does not have to be synchronized or volatile (as long as you can accept race conditions across threads to assign to the field); this is from Rich Hickey&lt;/li&gt;
&lt;li&gt;Scotch + Random Internet Images + Speakers == &lt;strong&gt;Fun&lt;/strong&gt;; giving off the cuff talks on silly subjects, with random internet images as your "slides"&lt;/li&gt;
&lt;li&gt;No one's that interested in Java anymore (at least, not at Hacker B&amp;B)&lt;/li&gt;
&lt;li&gt;Only ride a zip line backwards if you are prepared to accept the consequences&lt;/li&gt;
&lt;li&gt;The linkage between honey and fried chicken has not yet penetrated into Research Triangle, despite my best efforts&lt;/li&gt;
&lt;li&gt;The cost of using the &lt;code&gt;synchronized&lt;/code&gt; keyword can go asymptotic with enough threads and contention, making ReentrantReadWriteLock much cheaper in comparison&lt;/li&gt;
&lt;li&gt;Stu Halloway's house appears to have been designed by the same team that designed the Tardis, and is also bigger on the inside than the outside&lt;/li&gt;
&lt;li&gt;It is much more efficient to just hand Brian Goetz the $50 buy in, rather than go through the motions of playing poker with him
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/_-5N_zHXthM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/1573063213342735034/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=1573063213342735034" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1573063213342735034?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/1573063213342735034?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/_-5N_zHXthM/things-i-learned-at-hacker-bed.html" title="Things I Learned at Hacker Bed &amp; Breakfast" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/06/things-i-learned-at-hacker-bed.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkQCQXs5fSp7ImA9WhVXGUk.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-2304483264208126257</id><published>2012-04-20T09:46:00.000-07:00</published><updated>2012-04-20T09:46:00.525-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-20T09:46:00.525-07:00</app:edited><title>Yet More Spock Magic: Mocks</title><content type="html">&lt;p&gt;
Spock's built-in mock object capabilities are just a dream to use ... unlike other systems I've used, it doesn't get in your way, or force you to think backwards or inside out. Once again, some listings. These are for tests of Tapestry IoC's &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/services/AspectDecorator.html"&gt;AspectDecorator&lt;/a&gt; service, which is used to create a wrapper interceptor around some other object. The test below shows how a supplied &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/MethodAdvice.html"&gt;MethodAdvice&lt;/a&gt; callback object is invoked by the interceptor, if the advice is associated with the invoked method.

&lt;h3&gt;&lt;a href="http://testng.org/doc/index.html"&gt;TestNG&lt;/a&gt; with &lt;a href="http://www.easymock.org/"&gt;EasyMock&lt;/a&gt; (Java)&lt;/h3&gt;

&lt;script src="https://gist.github.com/2430130.js?file=AspectInterceptorBuilderImplTest.java"&gt;&lt;/script&gt;

Even this example is a bit streamlined, as some of the mock object capabilities, such as methods &lt;code&gt;newMock()&lt;/code&gt;, &lt;code&gt;replay()&lt;/code&gt; and &lt;code&gt;verify()&lt;/code&gt; are being derived from the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/test/TestBase.html"&gt;TestBase&lt;/a&gt; base class.

&lt;h3&gt;Spock&lt;/h3&gt;

&lt;script src="https://gist.github.com/2430133.js?file=AspectInterceptorBuilderImplSpec.groovy"&gt;&lt;/script&gt;

&lt;p&gt;
Spock's wonderful &lt;strong&gt;when: / then:&lt;/strong&gt; blocks organize the behavior into a stimulus followed by a response; using EasyMock, you have to train the mock objects for the response &lt;em&gt;before&lt;/em&gt; introducing the stimulus (the method invocation). Further, with EasyMock there's one API for methods that return a fixed response, a second API for methods that throw an exception, and a third API for methods where the result must be calculated; in Spock the value after the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; operator is either a literal value, or a closure that can do what it likes, such as the one attached to &lt;code&gt;MethodAdvice.advice()&lt;/code&gt; that checks for the expected method name, and then &lt;code&gt;proceed()&lt;/code&gt;s to the delegate mock object's method.

&lt;p&gt;I think that a reasonable developer, even without a thorough understanding of Spock, would get the gist of what this test does (perhaps with a little side note about the interaction system inside the &lt;strong&gt;then:&lt;/strong&gt; block). On the other hand, I've seen when training people with TestNG and EasyMock that it very rarely sinks in immediately, if at all.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/4hJkOJ_9V44" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/2304483264208126257/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=2304483264208126257" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2304483264208126257?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2304483264208126257?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/4hJkOJ_9V44/yet-more-spock-magic-mocks.html" title="Yet More Spock Magic: Mocks" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/04/yet-more-spock-magic-mocks.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQASHk5cSp7ImA9WhVXGEo.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-7189885887745447574</id><published>2012-04-19T16:32:00.000-07:00</published><updated>2012-04-19T16:32:29.729-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-19T16:32:29.729-07:00</app:edited><title>Yet Another Bit of Spock Love</title><content type="html">&lt;p&gt;
I'm gradually converting a back-log of existing tests to Spock ... and some of them convert so beautifully, it hurts. Here's an example:

&lt;h3&gt;Before (Java and TestNG)&lt;/h3&gt;

&lt;script src="https://gist.github.com/2424826.js?file=CronExpressionTest.java"&gt;&lt;/script&gt;

&lt;h3&gt;After (Spock)&lt;/h3&gt;

&lt;script src="https://gist.github.com/2424821.js?file=gistfile1.groovy"&gt;&lt;/script&gt;

&lt;p&gt;
What a difference; the data-driven power of the &lt;strong&gt;where:&lt;/strong&gt; block makes this stuff a bit of a snap, and you can see in once place, at a glance, what's going on. IDEA even lines up all the pipe characters automatically (wow!). It's obvious how the tests execute, and easy to see how to add new tests for new cases. By comparison, the TestNG version looks like a blob of code ... it takes a bit more scrutiny to see exactly what it is testing and how.

&lt;p&gt;
In addition, the propertyMissing() trick means that any property (including public static fields) of Calendar is accessible without qualification, making things look even nicer. This is what they mean by writing an "executable specification", rather than just writing code.

&lt;p&gt;
I can't say this enough: using any other framework for testing Java or Groovy code would &lt;em&gt;simply not be logical&lt;/em&gt;.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/QBvnkiDglIA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/7189885887745447574/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=7189885887745447574" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7189885887745447574?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7189885887745447574?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/QBvnkiDglIA/yet-another-bit-of-spock-love.html" title="Yet Another Bit of Spock Love" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>6</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/04/yet-another-bit-of-spock-love.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEQHR387eSp7ImA9WhVSFE8.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-7974153558319940034</id><published>2012-03-09T11:47:00.000-08:00</published><updated>2012-03-10T15:32:16.101-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-10T15:32:16.101-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="coffeescript" /><category scheme="http://www.blogger.com/atom/ns#" term="node" /><title>Node and Callbacks</title><content type="html">&lt;p&gt;One of the fears people have with &lt;a href="http://nodejs.org/"&gt;Node&lt;/a&gt; is the callback model. Node operates as a single thread: you must never do any work, especially any I/O, that blocks, because with only a single thread of execution, any block will block the entire process. 

&lt;p&gt;Instead, everything is organized around &lt;em&gt;callbacks&lt;/em&gt;:  you ask an API to do some work, and it invokes a callback function you provide when the work completes, at some later time. There are some significant tradeoffs here ... on the one hand, the traditional Java Servlet API approach involves multiple threads and mutable state in those threads, and often those threads are in a blocked state while I/O (typically, communicating with a database) is in progress. However, multiple threads and mutable data means locks, deadlocks, and all the other unwanted complexity that comes with it. 

&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-SUyJ6bmqivY/T1pcpBLaSyI/AAAAAAAAAQw/aYz3Bw7ZqLY/s1600/Pick%2BTwo.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="294" width="400" src="http://3.bp.blogspot.com/-SUyJ6bmqivY/T1pcpBLaSyI/AAAAAAAAAQw/aYz3Bw7ZqLY/s400/Pick%2BTwo.png" /&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;
By contrast, Node is a single thread, and as long as you play by the rules, all the complexity of dealing with mutable data goes away.
You don't, for example, save data to your database, wait for it to complete, then return a status message over the wire:  you save data to your database, passing a callback. Some time later, when the data is actually saved, your callback is invoked, and which point you can return your status message. It's certainly a trade-off: some of the local code is more complicated and bit harder to grasp, but the overall architecture can be lightening fast, stable, and scalable ... as long as &lt;em&gt;everyone&lt;/em&gt; plays by the rules.

&lt;p&gt;
Still the callback approach makes people nervous, because deeply nested callbacks can be hard to follow. I've seen this when teaching Ajax as part of my Tapestry Workshop.

&lt;p&gt;
I'm just getting started with Node, but I'm building an application that is very client-centered; the Node server mostly exposes a stateless, restful API. In that model, the Node server doesn't do anything too complicated that requires nested callbacks, and that's nice. You basically figure out a single operation based on the URL and query parameters, execute some logic, and have the callback send a response.

&lt;p&gt;
There's still a few places where you might need an extra level of callbacks. For example, I have a (temporary) API for creating a bunch of test data, at the URL &lt;code&gt;/api/create-test-data&lt;/code&gt;. I want to create 100 new Quiz objects in the database, then once they are all created, return a list of all the Quiz objects in the database.  Here's the code:

&lt;script src="https://gist.github.com/2007790.js?file=api.js"&gt;&lt;/script&gt;

&lt;p&gt;
It should be pretty easy to pick out the logic for creating test data at the end. This is normal Node JavaScript but if it looks a little odd, it's because it's actually decompiled &lt;a href="http://coffeescript.org/"&gt;CoffeeScript&lt;/a&gt;.  For me, the first rule of coding Node is &lt;strong&gt;always code in CoffeeScript&lt;/strong&gt;!  In its original form, the nesting of the callbacks is a bit more palatable:

&lt;script src="https://gist.github.com/2007799.js?file=api.coffee"&gt;&lt;/script&gt;

&lt;p&gt;
What you have there is a count, &lt;code&gt;remaining&lt;/code&gt;, and a single callback that is invoked for each Quiz object that is saved.  When that count hits zero (we only expect each callback to be invoked once), it is safe to query the database and, in the callback from that query, send a final response.  Notice the slightly odd structure, where we tend to define the final step (doing the final query and sending the response) first, then layer on top of that the code that does the work of adding Quiz objects, with the callback that figures out when all the objects have been created.

&lt;p&gt;
The CoffeeScript makes this a bit easier to follow, but between the ordering of the code, and the three levels of callbacks, it is far from perfect, so I thought I'd come up with a simple solution for managing things more sensibly. Note that I'm 100% certain that this issue has been tackled by any number of developers previously ... I'm using the excuse of getting comfortable with Node and CoffeeScript as an excuse to embrace some Not Invented Here syndrome.  Here's my first pass:

&lt;script src="https://gist.github.com/2007823.js?file=flow.coffee"&gt;&lt;/script&gt;

&lt;p&gt;The Flow object is a kind of factory for callback wrappers; you pass it a callback and it returns a new callback that you can pass to the other APIs.  Once all callbacks that have been added have been invoked, the &lt;em&gt;join callbacks&lt;/em&gt; are invoked after each of the other callbacks have been invoked.  In other words, the callbacks are invoked in parallel (well, at least, in no particular order), and the join callback is invoked only after all the other callbacks have been invoked.

&lt;p&gt;In practice, this simplifies the code quite a bit:

&lt;script src="https://gist.github.com/2007834.js?file=api.coffee"&gt;&lt;/script&gt; 

&lt;p&gt;
So instead of &lt;code&gt;quiz.save (err) -&gt; ...&lt;/code&gt; it becomes &lt;code&gt;quiz.save flow.add (err) -&gt; ...&lt;/code&gt;, or in straight JavaScript: &lt;code&gt;quiz.save(flow.add(function(err) { ... }))&lt;/code&gt;.

&lt;p&gt;So things are fun; I'm actually enjoying Node and CoffeeScript at least as much as I enjoy Clojure; which is nice because it's been years (if ever) since I've enjoyed the actual coding in Java (though I've liked the results of my coding, of course).&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/3uVLfmvsxlg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/7974153558319940034/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=7974153558319940034" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7974153558319940034?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7974153558319940034?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/3uVLfmvsxlg/nodejs-and-callbacks.html" title="Node and Callbacks" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-SUyJ6bmqivY/T1pcpBLaSyI/AAAAAAAAAQw/aYz3Bw7ZqLY/s72-c/Pick%2BTwo.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/03/nodejs-and-callbacks.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YDRXY-fCp7ImA9WhVTFEk.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-3926753538918501440</id><published>2012-02-27T09:36:00.002-08:00</published><updated>2012-02-28T08:06:14.854-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-28T08:06:14.854-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tapestry" /><category scheme="http://www.blogger.com/atom/ns#" term="byte code" /><category scheme="http://www.blogger.com/atom/ns#" term="ioc" /><category scheme="http://www.blogger.com/atom/ns#" term="aop" /><title>Plastic: Advanced Example</title><content type="html">&lt;p&gt;
Plastic is Tapestry's built-in &lt;a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming"&gt;Aspect Oriented Programming&lt;/a&gt; library, which primarily operates at the byte code level, but shields you from most byte code level thinking: normally, your code is implemented in terms of having method invocations or field reads and writes passed to callback objects that act as delegates or filters.

&lt;p&gt;Sometimes, though, you need to get a little more low-level and generate the implementation of a method more directly. Plastic includes a &lt;a href="http://en.wikipedia.org/wiki/Fluent_interface#Java"&gt;fluent interface&lt;/a&gt; for this as well: &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/plastic/InstructionBuilder.html"&gt;InstructionBuilder&lt;/a&gt;.

&lt;p&gt;This is an example from Tapestry's &lt;a href="http://en.wikipedia.org/wiki/Inversion_of_control"&gt;Inversion Of Control&lt;/a&gt; (IoC) container code; the proxy instance is the what's exposed to other services, and encapsulates two particular concerns:  First, the late instantiation of the actual service implementation, and second, the ability to serialize the proxy object (even though the services and other objects are decidedly &lt;em&gt;not&lt;/em&gt; serializable).

&lt;p&gt;In terms of serialization, what actually gets serialized is a ServiceProxyToken object; when a ServiceProxyToken is later de-serialized, it can refer back to the equivalent proxy object in the new JVM and IoC Service Registry.  The trick is to use the magic &lt;code&gt;writeReplace()&lt;/code&gt; method so that when the proxy is serialized, the token is written instead. Here's the code:

&lt;script src="https://gist.github.com/1925436.js"&gt; &lt;/script&gt;

&lt;p&gt;To kick things off, we use the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/services/PlasticProxyFactory.html"&gt;PlasticProxyFactory&lt;/a&gt; service to create a proxy that implements the service's interface.

&lt;p&gt;The callback passed to &lt;code&gt;createProxy()&lt;/code&gt; is passed the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/plastic/PlasticClass.html"&gt;PlasticClass&lt;/a&gt; object. This is initially an implementation of the service interface where each interface method does nothing.

&lt;p&gt;
The basic setup includes making the proxy implement &lt;a href="http://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html"&gt;Serializable&lt;/a&gt; and creating and injecting values into new fields for the other data that's needed.

&lt;p&gt;Next, a method called &lt;code&gt;delegate()&lt;/code&gt; is created; it is responsible for lazily creating the real service when first needed. This is actually encapsulated inside an instance of &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/ObjectCreator.html"&gt;ObjectCreator&lt;/a&gt;; the &lt;code&gt;delegate()&lt;/code&gt; method simply invokes the &lt;code&gt;create()&lt;/code&gt; method and casts the result to the service interface.

&lt;p&gt;
The methods on InstructionBuilder have a very close correspondence to JVM byte codes.  So, for example, loading an instance field involves ensuring that the object containing the field is on the stack (via &lt;code&gt;loadThis()&lt;/code&gt;), then consuming the &lt;em&gt;this&lt;/em&gt; value and replacing it with the instance field value on the stack, which requires knowing the class name, field name, and field type of the field to be loaded. Fortunately, the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/plastic/PlasticField.html"&gt;PlasticField&lt;/a&gt; knows all this information, which streamlines the code.

&lt;p&gt;
Once the ObjectCreator is on the stack, a method on it can be invoked; at the byte code level, this requires the class name for the class containing the method, the return type of the method, and the name of the method (and, for methods with parameters, the parameter types). The result of that is the service implementation instance, which is cast to the service interface type and returned. 

&lt;p&gt;
Now that the &lt;code&gt;delegate()&lt;/code&gt; method is in place, it's time to make each method invocation on the proxy invoke &lt;code&gt;delegate()&lt;/code&gt; and then re-invoke the method on the late-instantiated service implementation. Because this kind of delegation is so common, its supported by the &lt;code&gt;delegateTo()&lt;/code&gt; method.

&lt;p&gt;
&lt;code&gt;introduceMethod()&lt;/code&gt; can access an existing method or create a new one; for the &lt;code&gt;writeReplace()&lt;/code&gt; method, the &lt;code&gt;introduceMethod&lt;/code&gt; call creates a new, empty method. The call to &lt;code&gt;changeImplementation()&lt;/code&gt; is used to replace the default empty method implementation with a new once; again, loading an injected field value, but then simply returning it.

&lt;p&gt;
Finally, because &lt;a href="http://tapestryjava.blogspot.com/2003/08/importance-of-tostring.html"&gt;I feel strongly about including a useful &lt;code&gt;toString()&lt;/code&gt; method in virtually all objects&lt;/a&gt;, this is also made easy in Plastic.

&lt;p&gt;
Once the class has been defined, it's just a matter of invoking the &lt;code&gt;newInstance()&lt;/code&gt; method on the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/plastic/ClassInstantiator.html"&gt;ClassInstantiator&lt;/a&gt; object to instantiate a new instance of the proxy class. Behind the scenes, Plastic has created a constructor to set the injected field values, but another of the nice parts of the Plastic API is that you don't have to manage that: ClassInstantiator does the work.

&lt;p&gt;
I'm pretty proud of the Plastic APIs in general; I think they strike a good balance between making common operations simple and concise, but still providing you with an escape-valve to more powerful (or more efficient) mechanisms, such as the InstructionBuilder examples above. Of course, the deeply-nested callback approach can be initially daunting, but that's mostly a matter of syntax, which may be addressed in JDK 8 with the addition of proper closures to the Java language.

&lt;p&gt;I strongly feel that Plastic is a general purpose tool, that goes beyond inversion of control and the other manipulations that are specific to Tapestry ... and Plastic was designed specifically to be reused outside of Tapestry. It seems like it could be used for anything from implementing simple languages and DSLs, to providing all kinds of middleware code in new domains ... I have a fuzzy idea involving JMS and JSON with a lot of wiring and dispatch that could be handled using Plastic.  I'd love to hear other people's ideas!&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/N1cotcbT4ms" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/3926753538918501440/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=3926753538918501440" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3926753538918501440?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3926753538918501440?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/N1cotcbT4ms/plastic-advanced-example.html" title="Plastic: Advanced Example" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/02/plastic-advanced-example.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YNR344fSp7ImA9WhVTFEk.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-2344829550103924189</id><published>2012-02-23T11:35:00.001-08:00</published><updated>2012-02-28T08:06:36.035-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-28T08:06:36.035-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="testing" /><title>Tests: Auto Launch Application, or Expect Running?</title><content type="html">&lt;p&gt;&lt;em&gt;Does your test suite launch your application or expect it to be running already?&lt;/em&gt; This question came up while working on a client project; I launched the &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt;-based test suite and everything failed ... no requests got processed and I spent some time tracking down why the test suite was failing to launch the application.

&lt;p&gt;
I chatted about this with my client ... and found out that the Selenium test suite expects the application to already be running. It starts up Selenium Server, sure, but the application-under-test should already be running.

&lt;p&gt;
And suddenly, I realized I had a &lt;strong&gt;Big Blind Spot&lt;/strong&gt;, dating back to when I first started using Selenium. For years, my testing pattern was that the test suite would start up the application, run the Selenium tests, then shut it down ... and this makes perfect sense for testing the Tapestry framework, where the test suite starts and stops a number of different mini-applications (and has to run headless on a continuous integration server).

&lt;p&gt;
But an application is different, and there's a lot of advantages to testing against the running application ... including being able to quickly and easily reproduce any failures in the running application. Also, without the time to start and stop the application, the test suite runs a bit faster.  

&lt;p&gt;
Certainly, the command line build needs to be able to start and stop the application for headless and hands-off testing.  However, the test suite as run from inside your IDE ... well, maybe not.

&lt;p&gt;
So ... how do you handle this on your projects?&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/IeYHRwvRw5I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/2344829550103924189/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=2344829550103924189" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2344829550103924189?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/2344829550103924189?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/IeYHRwvRw5I/tests-auto-launch-application-or-expect.html" title="Tests: Auto Launch Application, or Expect Running?" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>5</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/02/tests-auto-launch-application-or-expect.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMGSX89eyp7ImA9WhRUFkU.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-3214990755545065639</id><published>2012-01-27T08:07:00.000-08:00</published><updated>2012-01-27T08:07:08.163-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-27T08:07:08.163-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="linkedin" /><title>LinkedIn Etiquette</title><content type="html">&lt;p&gt;I've used &lt;a href="http://linkedin.com"&gt;LinkedIn&lt;/a&gt; for many years now, long before I joined Facebook ... I liked the concept of never losing contact information with business contacts and technologist. It just seemed like a good idea (though I do sometimes wonder &lt;a href="http://www.youtube.com/watch?v=NcfXij6t4LA"&gt;if LinkedIn has any particular purpose&lt;/a&gt;).
&lt;p&gt;I tend to only connect with people I've met in person, or at least talked to on the phone.
&lt;p&gt;One thing that drives me crazy about LinkedIn is that you aren't forced to customize the message. As far as I'm concerned, the default message is like no message at all, and its a sign that you are just trolling for contacts. Just like you should always write a cover letter for a resume.
&lt;p&gt;So the first rule of LinkedIn is: &lt;strong&gt;always customize the message&lt;/strong&gt;. You chances of a connection go up significantly (at least with me) ... and with the default message, your chances drop down to near zero.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/7DjI04zp4n0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/3214990755545065639/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=3214990755545065639" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3214990755545065639?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3214990755545065639?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/7DjI04zp4n0/linkedin-etiquette.html" title="LinkedIn Etiquette" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/01/linkedin-etiquette.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMFQXo8eyp7ImA9WhRUFk0.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-3557656971524285189</id><published>2012-01-26T10:26:00.000-08:00</published><updated>2012-01-26T10:26:50.473-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-26T10:26:50.473-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tapestry" /><title>Tapestry Advantages</title><content type="html">&lt;p&gt;A summary of a &lt;a href="http://tapestry.1045711.n5.nabble.com/Stuts-VS-Tapestry-I-need-some-ammo-td5432282.html"&gt;discussion about the advantages of Tapestry over Struts&lt;/a&gt;:
&lt;ul&gt;
&lt;li&gt;Exceptional exception reporting&lt;/li&gt;
&lt;li&gt;Significantly less code&lt;/li&gt;
&lt;li&gt;Live class reloading&lt;/li&gt;
&lt;li&gt;Sensible defaults, especially for &lt;a href="http://en.wikipedia.org/wiki/SEO"&gt;SEO&lt;/a&gt;-friendly URLs&lt;/li&gt;
&lt;li&gt;Great community&lt;/li&gt;
&lt;li&gt;Flexibility and customizability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Interestingly, the quality of Tapestry's documentation was mentioned ... favorably! Between the revised &lt;a href="http://tapestry.apache.org"&gt;home page&lt;/a&gt;, and &lt;a href="http://jumpstart.doublenegative.com.au/home.html"&gt;Tapestry JumpStart&lt;/a&gt; (and &lt;a href="http://blog.tapestry5.de/index.php/2011/09/23/publishing-tapestry-5-in-action-book-on-my-own/"&gt;Igor's coming book&lt;/a&gt;), I think we're headed in the right direction in terms of documentation going from a liability to an asset.&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/Rw7zKtUFgjI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/3557656971524285189/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=3557656971524285189" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3557656971524285189?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/3557656971524285189?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/Rw7zKtUFgjI/tapestry-advantages.html" title="Tapestry Advantages" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2012/01/tapestry-advantages.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQCQ3g_fip7ImA9WhRUFU0.&quot;"><id>tag:blogger.com,1999:blog-4110180.post-7447306489952674714</id><published>2012-01-24T16:57:00.000-08:00</published><updated>2012-01-25T07:46:02.646-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-25T07:46:02.646-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tapestry" /><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><title>Tapestry 5.4: Focus on JavaScript</title><content type="html">&lt;div&gt;
  &lt;p&gt;
       Tapestry 5.3.1 is out in the wild ... and if Tapestry is to
       stay relevant, Tapestry 5.4 is going to need to be something
       quite (r)evolutionary.
  &lt;/p&gt;

&lt;blockquote&gt;
  There was some confusion on the Tapestry developer mailing list in
  advance of this blog post; I'd alluded that it was coming, and some
  objected to such pronouncements coming out fully formed, without
  discussion. In reality, this is just a distillation of ideas, a
  starting point, and not a complete, finalized solution. If it's more
  detailed than some discussions of Tapestry's evolution in the past,
  that just means that the mailing list discussion and eventual
  implementation will be that much better informed.
&lt;/blockquote&gt;
  &lt;p&gt;
    In posts and other conversations, I've alluded to my vision for
    Tapestry 5.4. As always, the point of Tapestry is to allow
    developers to &lt;em&gt;code less, deliver more&lt;/em&gt;, and that has been
    the focus of Tapestry on the server side: everything drives that
    point: terseness of code and templates, live class reloading, and
    excellent feedback are critical factors there. Much of what went
    into Tapestry 5.3 strengthened those points ... enhancements to
    Tapestry's meta-programming capabilities, improvements to the IoC
    container, and reducing Tapestry's memory footprint in a number of
    ways. I have one client reporting a 30% reduction in memory
    utilization, and another reporting a 30 - 40% improvement in
    execution speed.
  &lt;/p&gt;

  &lt;p&gt;
    Interestingly, I think that for Tapestry to truly stay relevant,
    it needs to shift much, much, more of the emphasis to the client
    side. For some time, Tapestry has been walking a fine line with
    regards to the critical question of &lt;em&gt;where does the application
    execute?&lt;/em&gt;  Pre-Ajax, that was an easy question: the
    application runs on the server, with at most minor JavaScript
    tricks and validations on the client.  As the use of Ajax has
    matured, and customer expectations for application behavior in the
    browser have expanded, it is no longer acceptable to say that
    Tapestry is page based, with limited Ajax
    enhancements. Increasingly, application flow and business logic
    need to execute in the browser, and the server-side's role is to
    orchestrate and facilitate the client-side application, as well as
    to act as a source and sink of data ultimately stored in a
    database.
  &lt;/p&gt;

  &lt;p&gt;
    As Tapestry's server-side has matured, the client side has not
    kept sufficient pace. Tapestry does include some excellent
    features, such as how it allows the server-side to drive
    client-side JavaScript in a modular and efficient way. However,
    that is increasingly insufficient ... and the tension caused by
    give-and-take between client-side and server-side logic has grown
    with each release.
  &lt;/p&gt;

  &lt;p&gt;
    Nowhere is this more evident than in how Tapestry addresses HTML
    forms. This has always been a tricky issue in Tapestry, because
    the dynamic rendering that can occur needs to be matched by
    dynamic form submission processing. In Tapestry, the approach is
    to serialize into the form instructions that will be used when the
    form is submitted (see the &lt;code&gt;store()&lt;/code&gt; method of the
    &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/FormSupport.html"&gt;FormSupport&lt;/a&gt;
    API). These instructions are used during the processing of the
    form submission request to re-configure the necessary components,
    and direct them to read their query parameters, perform
    validations, and push updated values back into server-side objects
    properties. If you've ever wondered what
    the &lt;code&gt;t:formdata&lt;/code&gt; hidden input field inside every
    Tapestry forms is about ... well, now you know: it's a serialized
    stream of Java objects, GZipped and MIME encoded.

  &lt;/p&gt;
  &lt;p&gt;
    However, relative to many other things in Tapestry, this is a bit
    clumsy and limited. You start to notice this when you see the
    tepid response to questions on the mailing list such as "&lt;em&gt;how
    to do cross-field validation?&lt;/em&gt;" Doing more complicated things,
    such as highly dynamic form layouts, or forms with even marginal
    relationships between fields, can be problematic (though still
    generally possible) ... but it requires a bit too much internal
    knowledge of Tapestry, and the in-browser results feel a bit
    kludgy, a bit clumsy. Tapestry starts to feel like it is getting
    in the way, and that's never acceptible.
  &lt;/p&gt;
  &lt;p&gt;
    Simply put, Tapestry's abstractions on forms and fields is
    both &lt;a href="http://en.wikipedia.org/wiki/Leaky_abstraction"&gt;leaky&lt;/a&gt;
    and insufficient. Tapestry is trying to do too much, and simply
    can't keep up with modern, reasonable demands in terms of
    responsiveness and useability inside the client. We've become used
    to pages rebuilding and reformatting themselves even while we're
    typing.  For Tapestry to understand how to process the form
    submission, it needs a model of what the form looks like on the
    client-side, and it simply doesn't have it. There isn't an
    effective way to do so without significantly restricting what is
    possible on the client side, or requiring much more data to be
    passed in requests, or stored server-side in the session.
  &lt;/p&gt;
  &lt;p&gt;
    The primary issue here is that overall form submission cycle,
    especially combined with Tapestry's need to serialize commands
    into the form (as the hidden &lt;code&gt;t:formdata&lt;/code&gt; field). Once
    you add Ajax to this mix, where new fields and rules are created
    dynamically (on the server side) and installed into the
    client-side DOM ... well, it gets harder and harder to manage.
    Add in a few more complications (such as a mix of transient and
    persistent Hibernate entities, or dynamic creation of sub-entities
    and relationships) into a form, it can be a brain burner getting
    Tapestry to do the right thing when the form is submitted: you
    need to understand exactly how Tapestry processes
    that &lt;code&gt;t:formdata&lt;/code&gt; information, and how to add your own
    callbacks into the callback stream to accomplish just exactly the
    right thing at just exactly the right time. Again, this is not the
    Tapestry way, where things are expected to &lt;em&gt;just work&lt;/em&gt;.
  &lt;/p&gt;
  &lt;p&gt;
    Further, there is some doubt about even the desirability of the
    overall model. In many cases, it makes sense to batch together a
    series of changes to individual properties ... but in many more,
    it is just as desirable for individual changes to filter back to
    the server (and the database) as the user
    navigates. Form-submit-and-re-render is
    a &lt;a href="http://en.wikipedia.org/wiki/IBM_3270"&gt;green screen&lt;/a&gt;
    style of user interaction. Direct interaction is the expectation
    now, and that's something Tapestry should embrace.
  &lt;/p&gt;
  &lt;p&gt;
    What's the solution, then? Well, it's still very much a moving
    target. The goal is to make creating client-side JavaScript
    libraries easier, to make it easier to integrate with libraries such
    as &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; (and its vast library
    of extensions), make things simpler and more efficient on the
    client side, and not sacrifice the features that make Tapestry fun
    and productive in the first place.&lt;/p&gt;

  &lt;h3&gt;Overall Vision&lt;/h3&gt;

  &lt;p&gt;The overall vision breaks down into a number of steps:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Reduce or remove outside dependencies&lt;/li&gt;
    &lt;li&gt;Modularize JavaScript&lt;/li&gt;
    &lt;li&gt;Change page initializations to use modules&lt;/li&gt;
    &lt;li&gt;Embrace client-side controller logic&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;
    Of course, all of these steps depend on the others, so there isn't
    a good order to discuss them.
  &lt;/p&gt;

  &lt;h3&gt;Reducing and removing outside dependencies&lt;/h3&gt;

  &lt;p&gt;
    Tapestry's client-side strength has always been lots of "out of
    the box" functionality: client-side validation, Zones and other
    Ajax-oriented behaviors, and a well-integrated system for
    performing page-level initializations.
  &lt;/p&gt;
  &lt;p&gt;
    However, this strength is also a weakness, since that out of the
    box behavior is too tightly tied to the &lt;a href="http://prototypejs.org/"&gt;Prototype&lt;/a&gt; and
    &lt;a href="http://script.aculo.us/"&gt;Scriptaculous&lt;/a&gt; libraries ... reasonable choices in 2006, but
    out-of-step with the industry today. Not just in terms of the
    momentum behind jQuery, but also in terms of very different
    approaches, such
    as &lt;a href="http://www.sencha.com/"&gt;Sencha/ExtJS&lt;/a&gt; and
    others.&lt;/p&gt;

  &lt;p&gt;
    It was a conscious decision in 2006 to not attempt to create an
    abstraction layer before I understood all the abstractions. I've
    had the intermediate time to embrace those abstractions. Now the
    big problem is momentum and backwards compatibility.&lt;/p&gt;

  &lt;p&gt;
    Be removing unnecessary behaviors, such as animations, we can
    reduce Tapestry's client-side needs. Tapestry needs to be able
    to &lt;strong&gt;attach event handlers&lt;/strong&gt; to elements. It needs to
    be able to easily &lt;strong&gt;locate elements&lt;/strong&gt; via unique ids,
    or via &lt;strong&gt;CSS selectors&lt;/strong&gt;. It needs to be able
    to &lt;strong&gt;run Ajax requests&lt;/strong&gt; and handle the responses,
    including &lt;strong&gt;dynamic updates to elements&lt;/strong&gt;.&lt;/p&gt;
  &lt;p&gt;

    &lt;p&gt;All of these things are reasonable to abstract, and by making
    it even easier to execute JavaScript as part of a page render or
    page update
    (&lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/ajax/AjaxResponseRenderer.html"&gt;something
    already present in Tapestry 5.3&lt;/a&gt;), currently built-in features
    (such as animations) can be delegated to the application, which is
    likely a better choice in any case.&lt;/p&gt;

  &lt;h3&gt;Modularizing JavaScript&lt;/h3&gt;

  &lt;p&gt;
    Tapestry has always been careful about avoiding client-side
    namespace polution.  Through release 5.2, most of Tapestry's
    JavaScript was encapulated in the &lt;code&gt;Tapestry&lt;/code&gt; object. In
    Tapestry 5.3, a second object, &lt;code&gt;T5&lt;/code&gt; was introduced with
    the intention that it gradually replace the
    original &lt;code&gt;Tapestry&lt;/code&gt; object (but this post represents a
    change in direction).&lt;/p&gt;
  &lt;p&gt;
    However, that's not enough. Too often, users have created in-line
    JavaScript, or JavaScript libraries that defined "bare" variables
    and functions (that are ultimately added to the
    browser's &lt;code&gt;window&lt;/code&gt; object). This causes problems,
    including collisions between components (that provide competing
    definitions of objects and functions), or behavior that varies
    depending on whether the JavaScript was added to the page as part
    of a full-page render, or via an Ajax partial page render.&lt;/p&gt;
  &lt;p&gt;
    The right approach is to encourage and embrace some form of
    &lt;a href="http://yuiblog.com/blog/2007/06/12/module-pattern/"&gt;JavaScript
      module architecture&lt;/a&gt;, where there are no explicit global
    variables or functions, and that all JavaScript is evaluated
    inside a function, allowing for private variables and
    functions.&lt;/p&gt;
  &lt;p&gt;
    Currently, I'm thinking in terms of &lt;a href="http://requirejs.org/"&gt;RequireJS&lt;/a&gt; as
    the way to organize the JavaScript. Tapestry would faciliate
    organizing its own code into modules, as well as
    application-specific (or even page-specific) JavaScript
    modules. This would mean that de-referencing the &lt;code&gt;T5&lt;/code&gt;
    object would no longer occur (outside of some kind of temporary
    compatibility mode).&lt;/p&gt;
  &lt;p&gt;
    For example, clicking a button inside some container element
    might, under 5.3, publish an event using Tapestry's client-side
    publish/subscribe system. In the following example, the click
    events bubble up from the buttons (with the &lt;code&gt;button&lt;/code&gt;
    CSS class name) to a container element, and are then published
    under the topic name &lt;code&gt;button-clicked&lt;/code&gt;.
  &lt;/p&gt;
  &lt;script src="https://gist.github.com/1603920.js?file=53pubsubexample.js"&gt;&lt;/script&gt;
  &lt;p&gt;
    Consider this an abbreviated example, as it doesn't explain where
    the &lt;code&gt;element&lt;/code&gt; variable is defined or initialized; the
    important part is the interaction with Tapestry's client-side
    library: the reference to the &lt;code&gt;T5.pubsub.publish&lt;/code&gt; function.
  &lt;/p&gt;
  &lt;p&gt;
    Under 5.4, using the RequireJS &lt;code&gt;require&lt;/code&gt; function, this
    might be coded instead as:
  &lt;/p&gt;
  &lt;script src="https://gist.github.com/1603930.js?file=54pubsubexample.js"&gt;&lt;/script&gt;
  &lt;p&gt;
    Here, the &lt;code&gt;t5/pubsub&lt;/code&gt; module will be loaded by RequireJS and
    passed as a parameter into the function, which is automatically
    executed. So, this supports JavaScript modularization, and
    leverages RequireJS's ability to load modules on-the-fly, 
    as needed.
  &lt;/p&gt;
  &lt;p&gt;
    Notice the difference between the two examples; in the first
    example, coding as a module was &lt;em&gt;optional&lt;/em&gt; (but
    recommended), since the necessary &lt;code&gt;publish()&lt;/code&gt; function
    was accessible either way. In the 5.4 example, coding using
    JavaScript modules is virtually &lt;em&gt;required&lt;/em&gt;: the anonymous
    function passed to &lt;code&gt;require()&lt;/code&gt; is effectively a module,
    but its only through the use of &lt;code&gt;require()&lt;/code&gt; (or
    RequireJS's &lt;code&gt;define()&lt;/code&gt;) that the &lt;code&gt;publish()&lt;/code&gt;
    function can be accessed. &lt;/p&gt;
  &lt;p&gt;
    This is both the carrot and the stick; the carrot is how easy it
    is to declare dependencies and have them passed in to your
    function-as-a-module. The stick is that (eventually)
    the &lt;em&gt;only&lt;/em&gt; way to access those dependencies is by providing
    a module and declaring dependencies.
  &lt;/p&gt;
  &lt;h3&gt;Change page initializations to use modules&lt;/h3&gt;

  &lt;p&gt;
    Tapestry has a reasonably sophisticated system for allowing
    components to describe their JavaScript requirements as they
    render, in the form of
    the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/javascript/JavaScriptSupport.html"&gt;JavaScriptSupport&lt;/a&gt;
    environmental (an environmental is a kind of
    per-thread/per-request service object).  Methods on
    JavaScriptSupport allow a component to request that a JavaScript
    library be imported in the page (though this is most commonly
    accomplished using
    the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/Import.html"&gt;Import&lt;/a&gt;
    annotation), and to request the &lt;em&gt;initialization functions&lt;/em&gt;
    get executed.&lt;/p&gt;
  &lt;p&gt;
    Part of Tapestry's Ajax support is that in an Ajax request, the
    JavaScriptSupport methods can still be invoked, but a completely
    different implementation is responsible for integrating those
    requests into the overall reply (which in an Ajax request is a JSON object, rather
    than a simple stream of HTML).
  &lt;/p&gt;
  &lt;p&gt;
    Here's an example component from the TapX library:&lt;/p&gt;

&lt;script src="https://gist.github.com/1673763.js?file=Expando.java"&gt;&lt;/script&gt;

  &lt;p&gt;
    The @Import annotation directs that a stack (a set of related
    JavaScript libraries, defined elsewhere) be imported into the
    page; alternately, the component could import any number of
    specific JavaScript files, located either in the web application
    context folder, or on the classpath.&lt;/p&gt;
  &lt;p&gt;
    Inside the &lt;code&gt;afterRender()&lt;/code&gt; method, the code constructs
    a &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/json/JSONObject.html"&gt;JSONObject&lt;/a&gt;
    of data needed on the client side to perform the operation. The
    call to &lt;code&gt;addInitializerCall&lt;/code&gt; references a function by
    name: this function must be added to
    the &lt;code&gt;T5.Initializers&lt;/code&gt; namespace object.  Notice the
    naming: &lt;code&gt;tapxExpando&lt;/code&gt;: a prefix to identify the
    library, and to prevent collisions with any other application or
    library that also added its own functions to
    the &lt;code&gt;T5.initializers&lt;/code&gt; object.
  &lt;/p&gt;
  &lt;p&gt;
    The JavaScript library includes the function that will be invoked:
  &lt;/p&gt;
  &lt;script src="https://gist.github.com/1673810.js?file=tapx-core.js"&gt;&lt;/script&gt;
  &lt;p&gt;
    Under 5.4, this would largely be the same except:
  &lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;There will be a specific Java package for each library (or the
      application) to store library modules.&lt;/li&gt;
    &lt;li&gt;The JavaScriptSupport environmental will have new methods to
      reference a function, inside a module, to invoke.&lt;/li&gt;
      &lt;li&gt;Stacks will consist not just of individual libraries, but
 also modules, following the naming and packaging
 convention.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3&gt;Embrace client-side controller logic&lt;/h3&gt;

&lt;p&gt;
  The changes discussed so far only smooth out a few rough edges;
  they still position Tapestry code, running on the server, as driving
  the entire show. 
 &lt;/p&gt;

&lt;p&gt;
  As alluded to earlier; for any sophisticated user interface, the
  challenge is to coordinate the client-side user interface (in terms
  of form fields, DOM elements, and query parameters) with the
  server-side components; this is encoded into the
  hidden &lt;code&gt;t:formdata&lt;/code&gt; field. However, it is my opinion that
  for any dynamic form, Tapestry is or near the end of the road for
  this approach.&lt;/p&gt;
&lt;p&gt;Instead, it's time to embrace client-logic, written in JavaScript,
  in the browser. Specifically, break away from HTML forms, and
  embrace a more dynamic structure, one where "submitting" a form
  always works through an Ajax update ... and what is sent is not a
  simple set of query parameters and values, but a JSON representation
  of what was updated, changed, or created.
&lt;/p&gt;
&lt;p&gt;
  My specific vision is to
  integrate &lt;a href="http://documentcloud.github.com/backbone/"&gt;Backbone.js&lt;/a&gt;
  (or something quite similar), to move this logic solidly to the
  client side. This is a fundamental change: one where the client-side
  is free to change and reconfigure the UI in any way it likes, and is
  ultimately responsible for packaging up the completed data and
  sending it to the server.
&lt;/p&gt;
&lt;p&gt;
  When you are used to
  the &lt;a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/BeanEditForm.html"&gt;BeanEditForm&lt;/a&gt;
  component, this might feel like a step backwards, as you end up
  responsible for writing a bit more code (in JavaScript) to
  implement the user interface, input validations, and relationships
  between fields. However, as fun as BeanEditForm is, the declarative
  approach to validation on the client and the server has proven to be
  limited and limiting, especially in the face of cross-field
  relationships. We could attempt to extend the declarative nature,
  introducing rules or even scripting languages to establish the
  relationships ... or we could move in a situation that puts the
  developer back in the driver's seat.&lt;/p&gt;
&lt;p&gt;
  Further, there are some that will be concerned that this is a
  violation of
  the &lt;a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY
  pricipal&lt;/a&gt;; however I subscribe to different philosophy that
  client-side and server-side validation are fundamentally different
  in any case; this is discussed in an excellent
  &lt;a href="http://blog.ianbicking.org/2011/03/30/js-on-server-and-client-is-not-a-big-deal/"&gt;blog
  post by Ian Bickling&lt;/a&gt;.

&lt;p&gt;
  Certainly there will be components and services to assist with this
  process, in term of extracting data into JSON format, and converting
  JSON data into a set of updates to the server-side objects. There's
  also a number of security concerns that necessitate careful
  validation of what comes up from the client in the Ajax request.
  Further, there will be new bundled libraries to make it easier to
  build these dynamic user interfaces.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;
  In this vision of Tapestry's future, the server-side framework
  starts to shift from the focus of all behavior to
  the &lt;em&gt;facilitator&lt;/em&gt;: it paints the broad stokes on the server,
  but the key interactions end up working exclusively on the
  client. &lt;/p&gt;
&lt;p&gt;
  I'm sure this view will be controversial: after all, on the surface,
  what the community really wants is just "jQuery instead of
  Prototype". However, all of the factors described in the above
  sections are, I feel, critical to keeping Tapestry relevant by
  embracing the client-side in the way that the client-side
  demands.&lt;/p&gt;

&lt;p&gt;
  I think this change in focus is a big deal; I think it is also
  necessary for Tapestry to stay relevant in the medium to long
  term. I've heard from many individual developers (not necessarily
  Tapestry users) that what they really want is "just jQuery and a
  restful API"; I think Tapestry can be that restful API, but by
  leveraging many of Tapestry's other strengths, it can be a lot more.
  Building something right on the metal feels empowering ... until you
  hit all the infrastructure that Tapestry provides, including
  best-of-class exception reporting, on-the-fly JavaScript aggregation
  and minimization, and (of course) live class reloading during
  development.
java&lt;/p&gt;  
  &lt;p&gt;
    I'm eager to bring Tapestry to the forfront of web application
  development ... and to deliver it fast!  Monitor the Tapestry
  developer mailing list to see how this all plays out.&lt;/p&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TapestryCentral/~4/mlpJ4i8ShSA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://tapestryjava.blogspot.com/feeds/7447306489952674714/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=4110180&amp;postID=7447306489952674714" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7447306489952674714?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4110180/posts/default/7447306489952674714?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TapestryCentral/~3/mlpJ4i8ShSA/tapestry-54-focus-on-javascript.html" title="Tapestry 5.4: Focus on JavaScript" /><author><name>Howard Lewis Ship</name><uri>https://plus.google.com/117875267367337641160</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-bHfrlWJkyI8/AAAAAAAAAAI/AAAAAAAAAAA/3ItMOpiIgco/s512-c/photo.jpg" /></author><thr:total>12</thr:total><feedburner:origLink>http://tapestryjava.blogspot.com/2011/11/tapestry-54-focus-on-javascript.html</feedburner:origLink></entry></feed>
