Tapestry Training -- From The Source

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

Friday, March 28, 2014

Google Docs Thinks You Offiline? Work around their bug!

... and so suddenly, and without warning, Google Docs stopped responding for me. My spreadsheet (my invoice spreadsheet without which I can't get paid) would load and then immediately tell me "You are offline" (there's an icon for that).

After trying all the obvious things ...

  • Clear my cache
  • Try it in FireFox instead of Chrome
  • Restart my computer
  • Try a different Internet connection

... I started hunting around the web. The problem has been around since at least 2012. This guy had a way to fix it that didn't work for me.

However, it did teach me about the Chrome's built in DNS information page where I could see 0.docs.google.com ERR_NAME_NOT_RESOLVED.

First I tried turning the Internal DNS client off (not on) ... that didn't help.

Then I did a hack. First I pinged docs.google.com and got (due to the vagaries of DNS, you might get a different number).

Then I added a line to my /etc/hosts file: 0.docs.google.com

... which basically says: "don't use DNS, I've got your IP address right here".

Things seem to be working!

My guess is that this represents some kind of load balancing solution implemented by Google Docs ... and either the 0 server has failed, or its configuration got munged, or this is simply a bug in the Google Docs client-side software.

Thursday, February 20, 2014

Gradle: overruling third-party dependencies

We all hate dependency hell, and on any Java project of any size, you'll hit it eventually. My project, for Aviso, is a large Clojure code base, with tons of dependencies on third-party libraries, many of which are Java libraries with their own third-party dependencies. Despite using Gradle as our build tool, we don't get a free pass, we sometimes end up with conflicts.

It often plays out like this: module A as a dependency on library L, which has a transitive dependency on library Q. That's OK, module A has a consistent class path when it builds.

Meanwhile, module B has a dependency on library M, which has a transitive dependency on library Q ... but a different version. That's OK, module B also has a consistent class path when it builds.

However, inside IntelliJ, you see both version of library Q in the "External Libraries" folder of the Project explorer. That's unfortunate and can cause confusion when navigating your code.

Worse yet, in the final application, combining modules A and B, you will be executing one module with a different version of library Q than your tests. That alone makes me a touch nervous.

Fortunately, Gradle provides a quite reasonable way of dealing with this. The hard way would be to just turn off all transitive dependencies. But I consider that throwing out the baby with the bathwater.

Instead, we can selectively override transitive dependency, consistently across all modules. And we can do this in a single place, in our top-level build.gradle:

This one small change affects every child project; we have a single place to maintain and resolve these version conflicts and don't have to chase down which module (among the 37 currently in our overall project) is the culprit for introducing a conflict. When we see a conflict, we add a new mapping to versionOverrides and we are done.

This is a huge example of how powerful Gradle's Groovy DSL is; because the build script is also executable code, there's room to put logic in place that simply can't be defined declaratively.

Our change hooks into the dependency resolution logic associated with each Gradle configuration (a configuration is essentially a way of declaring the class path for compiling, testing, or executing Java code).

Gradle kindly exposes a step inside the overall process of analyzing the dependencies; this code hooks into this step. It sees the requested dependency, and if it's in the override map, forces the version number to a specific value. In fact, this mechanism is powerful enough to replace dependencies, but that's beyond our immediate needs.

This is one of the reasons I use Gradle in preference to Maven: Gradle has the tools to cleanly and easily address my specific problems and particular edge-cases.

Friday, December 06, 2013

Tapestry Quicky: ConditionalComment

Here's a quicky I just put together for a client to generate IE conditional comments. This isn't a feature supported by Tapestry's JavaScriptSupport (for libraries; it is support for CSS files).

Fortunately, this is something that comes together in Tapestry in almost no code:

This is what it looks like in a template:

And here's the markup it produces:

And that's the whole point ... the URLs for the referenced JavaScript libraries are now Tapestry assets, and will have access to Tapestry's asset pipeline as unique resources, including GZip compression and minimization.

It is always important to me that Tapestry not get in the way: frameworks that lock you down and prevent you from accomplishing your goals should not be used. Tapestry has a long history, and there are certainly many areas that could have been implemented differently, or simply not implemented at all, but it's nice that most edge cases, like this one, have a simple solution.

Monday, November 11, 2013

Improving Clojure Feedback : Stack Traces

Perhaps no subject in software development is as important to me as feedback, which to my mind, is the language, library, or framework providing information back to the developer when things go wrong. A language, library, or framework that provides poor feedback is creating barriers to its own adoption.

There's a possibly apocryphal legend about early FORTRAN compilers. Back in the day (thankfully, even earlier than my personal back in the day), you would code up your FORTRAN code on punch cards and deliver them to an operator. At some convenient time (for the operator), the compiler would be started and fed the stack of cards. Assuming the cards had not been bent, folded, spindled, or mutilated ... and that the program itself contained no errors, you would get a compiled program somewhere. If anything failed, you would simply get the error message "FAIL". Nothing else.

That's bad feedback.

It's important to feel that you are in control of your own tools; that's part of the love/hate relationship so many developers have with their IDEs: the IDEs certainly enable a lot of productivity ... but what you always end up remembering is when the IDE gets in the way of some really critical, really difficult stuff. Or when it crashes mysteriously at a critical moment. Or when it just won't dosomething and won't tell you why not.

Likewise, any tool (or language, library, or framework) that provides bad feedback, especially when things go wrong, is building a perception that it is hard to use overall. We're a fickle bunch ... we'll be off to find the Next Big Thing instead.

Which brings us to my favorite programming language, Clojure. When things are going right in Clojure they are going very, veryright. However, when things go wrong in Clojure it can be very painful.

First of all, Clojure's basic mechanism for any kind of error is to simply throw an exception. Since Clojure is a Lisp, the difference between compile time and runtime is not so easily distinguished ... a running Clojure program is often loading source and compiling code as well as running the application.

And what happens to exceptions? They are thrown in one piece of code and, more often than not, caught, wrapped, and re-thrown in containing stack frames. Ultimately, this whole stack of exceptions gets spewed out to the console.

This isn't quite bad as that FORTRAN compiler; however, even on my big 30" cinema display, a Clojure compilation failure always sends me scrolling backwards in my terminal window to see the actual error and (with more than a pinch of luck) the file name and line number containing the error.

It's much worse when it comes to runtime failures in my own code; it is likely that the exceptions will be nested even deeper than the number of levels intituted by the Clojure compiler and REPL. Beyond that is the issue of laziness ... program execution goes a little topsy-turvey from your code due to laziness, as what code is executing is typically driven by what particular bit of lazily-computed data is needed at any particular instant. That takes a little getting used to for new Clojure developers.

And so, deep in your code, you passed a string when a keyword was expected, or you passed a map when a seq was expected, or any number of other similar situations ... and now you are faced with an exception and its stack trace.

Here's the ugly not-so-secret of Clojure: it's a light and tasty frosting on top of a cake of sawdust and rusty gears: Java. Every Clojure function must be converted into a Java class in order to execute within the JVM. Java places somewhat arbitrary limits on the class and method names: Where Clojure loves dashes in function names, Java doesn't allow them. Likewise, Clojure loves all kind of other punctuation, with functions named "+" or "string?", that Java forbids as well.

Clojure manglesthe Clojure namespace and function names to fit into the Java world, and nothing in the exception reporting chain does anything to make the result any prettier. Here's an example:

All the information is available ... but you have to work to get at it: You need to mentally de-mangle the Java class names back to Clojure namespaces and function names. You have to ignore a lot of stack frames that are really the infrastructure supporting Clojure on top of Java. You have to assemble the meaningful stack trace (the last and deepest one) from the parts it extends off of the prior stack traces. You have to ignore the numeric ids tacked on to the end of names to help ensure uniqueness.

This is not great feedback.

What if we could provide just a little bit better feedback? What if we could do a lot of that name-demangling automatically, and present the same data in a more helpful way? That's what I've been working on as part of the io.aviso:pretty library for Clojure.

Pretty can be used to format that same exception just a little bit nicer:

The exact format is still in-flux because it is currently so wide; fortunate, the interesting stuff is always to the left!

This same approach appears in the related io.aviso:twixt library, modified to take advantage of how much more richly data can be presented in a web page:

However, this is just the start. Providing truly useful feedback is more than just putting a patch on exception reporting. It requires attention to detail throughout the tool. This has been the case for Apache Tapestry, where a significant amount of the framework is about detecting and reporting errors in a useful way.

Twixt has a set of utilities to address this as well; a way of tracking what the application is doing so that exceptions can be reported. The end result is an "operations trace" that can nest to an arbitrary depth and is used to reconstruct how the application arrived at the point of failure.

This extra layer of code can pay big dividends when things go wrong; the nesting of operations can be critical to understanding the underlying problem; the operations trace combines the very local scope of the failure with a larger scope that explains why the failed code was invoked in the first place.

So I'm very excited by the possibilities, but to provide truly universal, useful feedback in Clojure may not be something that can be effectively added in from the outside: it has to be a priority for the core Clojure developers, and bred into the fabric of the Clojure compiler and runtime. That's something I'd love to see ... or help with.

Wednesday, October 16, 2013

Gainesville in February 2014

I'll be spending about three weeks in Gainesville, Florida this coming February (2014). For the wife and kids, its about visiting the grandparents, hiking, fishing, and playing. For me, its mostly about work, sigh. In any case, I'm told that Gainesville has gotten quite the start up culture going recently ... I'd love an opportunity to spread the word about Tapestry, Clojure, or AngularJS (or Spock, or Geb, or any other presentation I have in my bag of tricks). Think of this as a chance to put your "brown bag lunch" on steroids for a day.

Just get in contact with me if you are interested; see my contact information on my home page.

Monday, September 23, 2013

Named Parameters for Clojure

Clojure can simulate named parameters, or a mix of positional and named parameters. This is really old news, dating back to Clojure 1.2 if evidence serves, but I always have trouble finding this exact syntax, and it is not fully obvious.

Say you want to accept a certain number of optional parameters with names, and perhaps, defaults. It turns out you can combine rest parameters (the ones that come after a &) with map destructuring.

In the simple case, you don't care what the possible options are, and you don't have any defaults.

The keys and values you pass to this function, say (named-parameters :foo 1 :bar 2), are collected together as symbol params.

If you don't provide an even number of values (that is, the same number of keys and values), you'll get a reasonable exception, such as java.lang.IllegalArgumentException: No value supplied for key: :bar

Easy-peasey ... but you need to extract values from the params map to use them inside the function, e.g.: (:foo params). It would be nicer to have them as symbols, just like with normal positional parameters. This is also easy, by leveraging more of the features of map destructuring:

The :keys identifies the keywords expected in the map; it works backwards from the symbol name, foo, to the expected keyword, :foo.

There's also a :syms (for when the keys are expected to be symbols) and :strs (for when the keys are expected to be strings).

The :or identifies default values for each symbol. The end result is that we can rely on defaults from :or or provide our own values when invoking the function:

And since this is Clojure, you can combine all of these things together quite easily ... some positional parameters, some named, some identified by keywords, others identified by symbols.

Thursday, July 25, 2013

Tapestry 5.4: An Avalanche of Fixed Issues

I've been working through the Tapestry issue list over the last couple of days; I'm closing a lot of duplicate and invalid bugs, and fixing some low-hanging fruit. Still, the number of fixed bugs already is impressive:


  • [TAP5-800] - Server side error during provideCompletions event for Autocompleter mixin is not reported on the client side properly
  • [TAP5-860] - JavaScript errors when adding validations to checkboxes
  • [TAP5-986] - A request can fail with an NPE in some cases, when a Tapestry page is acting as the servlet container error page
  • [TAP5-1010] - Fix Finnish validation message translation - corrected file attached
  • [TAP5-1193] - tapestry.js prevents using the back/forward browser cache
  • [TAP5-1200] - Nested Ajax calls result in a call to $T(null)
  • [TAP5-1569] - Grid should implement the ClientElement interface
  • [TAP5-1601] - Sometime a method that references a field with a conduit will not be instrumented, resulting in an NPE accessing the field itself
  • [TAP5-1668] - JavaDoc for @Parameter.value() should be clearer about the empty string
  • [TAP5-1691] - AssetPathConstructorImpl should URL-encode the application version
  • [TAP5-1704] - Localizing the "Today" and "None" labels in the core datefield component
  • [TAP5-1729] - Sometimes YUICompressor can fail with java.util.EmptyStackException
  • [TAP5-1734] - Race condition loading JavaScript libraries with ProgressiveDisplay
  • [TAP5-1735] - Most packages lack package-level javadocs
  • [TAP5-1742] - AfterRender() in Loop component should not short circuit
  • [TAP5-1762] - Some components do not have include a description of their parameters in their JavaDoc pages
  • [TAP5-1765] - PerThread scope is not honored when service is created using autobuild
  • [TAP5-1768] - @ActivationRequestParameter does not encode to be URL friendly
  • [TAP5-1770] - PageTester causes StringIndexOutOfBoundsException for any page request path with query parameter
  • [TAP5-1773] - FormFieldFocus mixin passes control name, not client id, to JavaScriptSupport.autofocus()
  • [TAP5-1779] - Tapestry allows directory listing of assets via client browser
  • [TAP5-1784] - Extra comma in tapestry-messages_de.js causes Internet Explorer to fail to work
  • [TAP5-1785] - Exceptions while compressing JavaScript are not fully reported
  • [TAP5-1787] - TextField should be usable with HTML5 type values (such as "number", "email", etc.)
  • [TAP5-1788] - Service id 'environment' has already been defined by org.apache.tapestry5.services.TapestryModule with Spring 3.1
  • [TAP5-1791] - On some JDKs, the complex regular expression used by ComponentEventLinkEncoderImpl will cause a stack overflow
  • [TAP5-1798] - Grid and BeanDisplay should ignore properties that are actually static fields
  • [TAP5-1822] - LinkSecurity should be public, not internal, as it is used with the public Link interface
  • [TAP5-1825] - Incorrect order of parameter in localization messages within ValidationMessages Italian lang
  • [TAP5-1831] - DelegatingInjectionResources does not pass generic type information to its first delegate
  • [TAP5-1836] - "LocalhostOnly" WhitelistAnalyzer check "0:0:0:0:0:0:0:1%0" ip address instead "0:0:0:0:0:0:0:1"
  • [TAP5-1844] - Datefield not fires onchange event on changes
  • [TAP5-1848] - tapestry-jpa ignores persistence provider from persistence.xml
  • [TAP5-1854] - AjaxComponentEventRequestHandler doesn't handle the case where a response has already be returned, and may append an empty JSON Object to the response
  • [TAP5-1860] - Access to protected component fields does not always reflect in subclasses
  • [TAP5-1868] - SRSCachingInterceptor returns compressed version of asset for all clients once it was compressed for some client
  • [TAP5-1870] - javascript added while in the render phase of a component from an ajax request is never executed
  • [TAP5-1873] - JavaScript execution exception is not logged
  • [TAP5-1880] - GZip compression should be disabled if the request is over http 1.0
  • [TAP5-1881] - TypeCoercion from Number to Boolean returns false for any number that is an even multiple of 256
  • [TAP5-1887] - Client-side JavaScript error if console.info/debug/... is available but not a function
  • [TAP5-1890] - PlaceholderBlock should implement RenderCommand
  • [TAP5-1892] - FormFragment validates non-displayed fragments (reopen)
  • [TAP5-1903] - Client-side exception when a Zone containing a Form with an Upload component is re-rendered
  • [TAP5-1906] - Interaction between client-side validation and submit buttons can result in a server-side error parsing JSON array
  • [TAP5-1907] - Client exception in IE9 when partial page render introduces stylesheets
  • [TAP5-1911] - Tree leaf is still not selectable
  • [TAP5-1926] - PropertyDisplayBlocks Date's display block is not thread safe
  • [TAP5-1929] - High contention in method InternalComponentResourcesImpl.postRenderCleanup() and NamedSet.getValues()
  • [TAP5-1931] - Alerts can not be dismissed in IE8.
  • [TAP5-1933] - Email validation should use a different regular expression
  • [TAP5-1935] - ContextResource uses a deprecated File.toURL
  • [TAP5-1937] - removeCookieValue with custom path
  • [TAP5-1938] - The ValueEncoder for JPA entity types should encode transient instances as null rather than throw an exception
  • [TAP5-1945] - Clojure integration breaks if tapestry-core is on the classpath
  • [TAP5-1949] - Alerts component does not show alerts added from a component that occurs later in the template
  • [TAP5-1950] - ResourceStreamer sends SC_NOT_MODIFIED as an error, but it should be just status
  • [TAP5-1952] - JavaScript Compressor should log warnings as well as errors, and identify the input clearly
  • [TAP5-1956] - Tapestry.ZoneManager creates memory leaks
  • [TAP5-1960] - @javax.annotations.Inject on a field, without @Named, does not inject service resources
  • [TAP5-1963] - Original exception lost in CommitAfterWorker upon abort
  • [TAP5-1964] - In production mode, placeholder timestamp needs to be limited to one-second accuracy
  • [TAP5-1969] - Excessive warnings from YUICompressor
  • [TAP5-1973] - :443 added to URLs when using the Link.toAbsoluteURI(true)
  • [TAP5-1976] - XML Parser adds attributes with default values and produces invalid HTML5 markup
  • [TAP5-1977] - Memory leak (perm gen) in component reloading
  • [TAP5-1979] - Changing the implementation of a method after adding method advice does not work; the original implementation remains
  • [TAP5-1983] - PerThreadManager does not cleanup on shutdown, can lead to memory leaks when application redeployed
  • [TAP5-1984] - Adding alerts doesn't work during ajax rendering
  • [TAP5-1985] - Clicking Alert component's "dismiss all" link scrolls to top of page
  • [TAP5-1988] - Tapestry Security Violations
  • [TAP5-1991] - YUICompressor should be less verbose about common warnings
  • [TAP5-1992] - Switch YUICompressor dependency back to com.yahoo version 2.4.7
  • [TAP5-1995] - Tapestry5 Application can not be deployed as Tomcat7 HotDeploy Package
  • [TAP5-2001] - Race condition while loading javascript file via ajax
  • [TAP5-2004] - Tapestry-Ioc fails to build on Windows (ReloadTest.java)
  • [TAP5-2008] - Serialized object data stored on the client should be HMAC signed and validated
  • [TAP5-2009] - Downgrade bundled Prototype version back to 1.7
  • [TAP5-2010] - Broken links in Javadoc pages
  • [TAP5-2014] - Zone highlight leaves behind an explicit background-color which overrides css background-color
  • [TAP5-2015] - Tomcat .war path is not decoded properly
  • [TAP5-2017] - Triggerfragment Mixin does not work when used within a table element for IE7
  • [TAP5-2025] - Duplicate generated ids
  • [TAP5-2034] - When the application operates with a context path, asset URLs are incorrectly formed
  • [TAP5-2037] - ValidationTracker/Flash persistence race condition with AJAX
  • [TAP5-2040] - TapestryAppInitializer should have access to system properties; this is a reversion in 5.4
  • [TAP5-2041] - Links within subheadings are invisible on Javadoc pages
  • [TAP5-2045] - Set default CSS class for Label component to be "control-label", but allow overrides
  • [TAP5-2047] - ElementWrapper#find jQuery implementation is broken if there is no match
  • [TAP5-2049] - Tapestry should provide locking semantics for attributes stored in the session, to prevent multiple simultaneous requests (due to Ajax) from conflicting
  • [TAP5-2052] - tapestry-ioc has a compile dependency on tapestry-test
  • [TAP5-2053] - Use of 'transient' property in alert options breaks JavaScript processors
  • [TAP5-2054] - `dom.js` implementations break when minified
  • [TAP5-2057] - CSS URL rewriting is incomplete
  • [TAP5-2061] - core_**.properties got wrong encoding
  • [TAP5-2062] - Ajax alerts not rendered after a JS call to dismissOne()
  • [TAP5-2064] - Add 'info' style to info alerts
  • [TAP5-2068] - Use <button>, not <a>, tags for the close buttons on alerts
  • [TAP5-2071] - Tapestry should output valid HTML5 when using the HTML5 doctype in templates
  • [TAP5-2073] - AbstractEventContext always emits "null" regardless of what values are defined by subclass
  • [TAP5-2079] - Tapestry should handle the case where the context path is (incorrectly) "/" (not the empty string)
  • [TAP5-2081] - datefield: cannot blank out date value via text input
  • [TAP5-2082] - When the last alert that is removed is transient, the outer container is not removed
  • [TAP5-2084] - Form should decode its link parameters
  • [TAP5-2087] - Checkboxes in forms always return "on" (considered as "checked" server-side)
  • [TAP5-2089] - JavaScript errors when using Autocomplete mixin
  • [TAP5-2092] - Tapestry-mongodb has a compile dependency on tapestry-test
  • [TAP5-2095] - Module assets should not be sent a far-future expires header
  • [TAP5-2101] - BeanEditor should always provide a new BeanValidationContext (JSR-303)
  • [TAP5-2103] - ElementWrapper#text() jQuery implementation does not work
  • [TAP5-2106] - Tapestry incorrectly rewrites CSS urls in a variety of cases
  • [TAP5-2110] - MinLength and MaxLength validators don't import "t5/core/validation"
  • [TAP5-2119] - Depenencies excluded in Gradle build files are not excluded in the generated Maven POMs
  • [TAP5-2120] - StringIndexOutOfBoundsException computing relative paths for certain Resources
  • [TAP5-2121] - Broken image (for Tapestry Logo) in generated documentation page
  • [TAP5-2122] - visible() on empty <div> is false; prevents valiation of Palette component
  • [TAP5-2124] - events.zone.refresh must be triggered directly on zone element, can fail otherwise
  • [TAP5-2125] - Ajax form submissions via jQuery may pass t:formdata as "t:formdata[]" if there are multiple values
  • [TAP5-2126] - TAP5-2063 fix (multi-valued parameters in Link) conflicts with use of ActivationRequestParameter and PageLink.parameters parameter
  • [TAP5-2132] - Generation of documentation for t5/core/dom should use the split (for jQuery, for Prototype) files, not the combined source file
  • [TAP5-2137] - Tree component renders empty nodes as expandable, AssertionError when expanded
  • [TAP5-2141] - Ajax form submission should ignore fields with no name
  • [TAP5-2142] - Exceptions thrown during Ajax processing or validation force a traditional form submission
  • [TAP5-2148] - VersionUtils should close the stream it opens


  • [TAP5-336] - The Cookies service interface could be simplified using a builder pattern
  • [TAP5-672] - Translation for Vietnamese
  • [TAP5-1008] - The order in which zones are added to the MultiZoneUpdate should be honored on the client side, to allow nested zones to be updated all in a single request
  • [TAP5-1268] - Have Tapestry's core library contribute to the global message catalog; move all validation messages and component catalogs to the single file
  • [TAP5-1394] - Adding method to Cookies api that lets you set path, domain, and maxAge at once.
  • [TAP5-1405] - XHR requests should be easily callable from javascript and not rely on a zone
  • [TAP5-1560] - Remove internal dependancies on deprecated features
  • [TAP5-1570] - Zone elements in the client should trigger events so that the application can react to changes
  • [TAP5-1748] - Alerts component should render informal parameters
  • [TAP5-1756] - Let the asset path prefix be configurable
  • [TAP5-1775] - Improve javascript performance while creating zone events
  • [TAP5-1781] - Improve javascript load time of pages improving onDomLoadedCallback
  • [TAP5-1801] - Component fields should not need to be private, merely non-public
  • [TAP5-1805] - Selectable Tree component look and feel is lacking in user affordances
  • [TAP5-1808] - Change Form to (by default) immediately render markup when there are validation errors, to avoid creating the session
  • [TAP5-1816] - Add CSS rule for DIV.t-exception-container to default.css that sets a very high z-index
  • [TAP5-1824] - New translations for Norwegian BokmÃ¥l
  • [TAP5-1827] - KaptchaField should have a parameter to allow it to operate as a visible text field rather than a password field
  • [TAP5-1832] - Tapestry could do an even better job of filtering unnecessary stack frames from the exception report
  • [TAP5-1840] - Add method Request.isSessionInvalidated()
  • [TAP5-1847] - An extension component template (with a root t:extend element) should allow t:block elements to be nested
  • [TAP5-1849] - The text "Dismiss All" used in client-side Alerts is not localized or localizable
  • [TAP5-1878] - Default for parameter visible of component KaptchaField should change from false to true
  • [TAP5-1889] - Improve integration test execution speed
  • [TAP5-1905] - Not serializable: org.apache.tapestry5.tree.DefaultTreeExpansionModel - can't persist sessions across cluster
  • [TAP5-1914] - Alerts needs a parameter to show/hide "Dismiss all"
  • [TAP5-1940] - Typo in interface LocalizationSetter.setNonPeristentLocaleFromLocaleName()
  • [TAP5-1970] - JSON API improvements
  • [TAP5-1987] - ProgressiveDisplay update parameter as a symbol
  • [TAP5-1990] - Link interface should be more fluid to allow setting of anchor, etc., more concisely
  • [TAP5-1993] - Support returning an URL for an ajax request
  • [TAP5-1996] - Add Severity.SUCCESS enum for alerts
  • [TAP5-2013] - "Parameters" table often too wide in component javadocs
  • [TAP5-2022] - Add PropertyAccess.getAnnotation
  • [TAP5-2035] - Make it easier to choose another slf4j backend
  • [TAP5-2039] - Element.attribute() should treat "class" specially, appending the new value to the old
  • [TAP5-2046] - Should use OperationTracker to track type of request (ajax component event, traditional component event, page render request)
  • [TAP5-2050] - Grid sort icons should be added via CSS instead of rendering an embedded img element
  • [TAP5-2051] - Fix Plastic objects memory leaks
  • [TAP5-2058] - Support X-Forwarded-Proto to identify a Request secured (https)
  • [TAP5-2063] - Add support for multivalued parameters in Link
  • [TAP5-2102] - Allow supplying EntityManager properties via TapestryPersistenceUnitInfo
  • [TAP5-2131] - Update Javadoc CSS to reflect changes in the html generate by JDK7

New Feature

  • [TAP5-999] - Implement an agnostic tapestry.js layer + adapters to allow developers to switch from prototype to jquery
  • [TAP5-1168] - Add @Operation annotation that can be used to automatically track a component or service method invocation using the OperationTracker
  • [TAP5-1794] - Allow for configuring Selenium-based integration tests using an annotation
  • [TAP5-1807] - In development mode, Tapestry should include a <meta> tag to identify the active page
  • [TAP5-1809] - Option to render a particular page instance in its current state (without page activation)
  • [TAP5-1833] - Merge functionality of Tynamo.org's tapestry-exceptionpage module with the built-in ExceptionHandler
  • [TAP5-1843] - Allow all of the properties of the Messages services to be accessed
  • [TAP5-1857] - Add a SubmitMode (for Submit and LinkSubmit components) for unconditionally submitting the form
  • [TAP5-1944] - Add basic integration between Tapestry and Clojure
  • [TAP5-2055] - Polish translations
  • [TAP5-2065] - Introduce support for MongoDB access as a service
  • [TAP5-2094] - E-Tags support


  • [TAP5-1518] - Remove ClassFactory / ClassFab / Javassist Dependency
  • [TAP5-1780] - Upgrade Selenium dependency to 2.14.0
  • [TAP5-1790] - Update Tapestry build to create a binary release archive (in addition to a source and javadoc archive)
  • [TAP5-1792] - Upgrade Tapestry/Spring integration to Spring 3.1.0.RELEASE
  • [TAP5-1810] - Remove deprecated "suppress redirects from action" code
  • [TAP5-1826] - Setup the Sonar plugin to let analysis.apache.org analyze the source base
  • [TAP5-1852] - Upgrade Plastic to use ASM 4.0
  • [TAP5-1909] - Update Underscore JavaScript library to latest version 1.3.3
  • [TAP5-1922] - Upgrade Hibernate dependency to latest, Hibernate 4.1.2.Final
  • [TAP5-1965] - Replace use of Request.getContextPath() with a symbol defined at application startup
  • [TAP5-1989] - Upgrade bundled Prototype to version 1.7.1