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, December 26, 2008

Exception Reporting: The Why

When things go wrong in a complicated system, I have one question: why?

First off: if you are building in Java you are building a complicated system. The reason people cling to Java nowadays is because of the 10+ years of libraries that have evolved. All those libraries are strung together using raw code, or Spring, or Guice, or Tapestry 5 IoC. In an add-hoc, just-in-time, per-thread, abstractions-R-us world, knowing Why a particular operation was invoked is often more use than knowing what in particular failed.

Today's example: I'm working on better integrating Tapestry and Spring as a first step towards Spring Web Flow integration.

I'm mid way through and things started breaking. Now, a normal system can output an exception:

[ERROR] ContextLoader Context initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'upcase' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.example.testapp.services.StringTransformer]: : No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:591)
 at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:193)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:925)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:835)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:440)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
 at java.security.AccessController.doPrivileged(Native Method)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429)
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
 at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
 at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
 at org.apache.tapestry5.internal.spring.SpringModuleDef$1$1$1.invoke(SpringModuleDef.java:60)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.internal.spring.SpringModuleDef$1$1.createObject(SpringModuleDef.java:56)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator$1.invoke(OperationTrackingObjectCreator.java:45)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator.createObject(OperationTrackingObjectCreator.java:49)
 at org.apache.tapestry5.ioc.internal.SingletonServiceLifecycle.createService(SingletonServiceLifecycle.java:29)
 at org.apache.tapestry5.ioc.internal.LifecycleWrappedServiceCreator.createObject(LifecycleWrappedServiceCreator.java:52)
 at org.apache.tapestry5.ioc.internal.InterceptorStackBuilder.createObject(InterceptorStackBuilder.java:56)
 at org.apache.tapestry5.ioc.internal.RecursiveServiceCreationCheckWrapper.createObject(RecursiveServiceCreationCheckWrapper.java:60)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator$1.invoke(OperationTrackingObjectCreator.java:45)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator.createObject(OperationTrackingObjectCreator.java:49)
 at org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator.createObject(JustInTimeObjectCreator.java:65)
 at $ConfigurableWebApplicationContext_11e7601a600.delegate($ConfigurableWebApplicationContext_11e7601a600.java)
 at $ConfigurableWebApplicationContext_11e7601a600.getBeansOfType($ConfigurableWebApplicationContext_11e7601a600.java)
 at org.apache.tapestry5.internal.spring.SpringModuleDef$2.provide(SpringModuleDef.java:121)
 at org.apache.tapestry5.internal.spring.SpringModuleDef$3$1$1.invoke(SpringModuleDef.java:170)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.internal.spring.SpringModuleDef$3$1.provide(SpringModuleDef.java:164)
 at org.apache.tapestry5.ioc.internal.services.MasterObjectProviderImpl.provide(MasterObjectProviderImpl.java:38)
 at $MasterObjectProvider_11e7601a5f7.provide($MasterObjectProvider_11e7601a5f7.java)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.getObject(RegistryImpl.java:626)
 at org.apache.tapestry5.ioc.internal.ObjectLocatorImpl.getObject(ObjectLocatorImpl.java:49)
 at org.apache.tapestry5.ioc.internal.util.InternalUtils.calculateInjection(InternalUtils.java:208)
 at org.apache.tapestry5.ioc.internal.util.InternalUtils.access$000(InternalUtils.java:42)
 at org.apache.tapestry5.ioc.internal.util.InternalUtils$2.invoke(InternalUtils.java:255)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.util.InternalUtils.calculateParameters(InternalUtils.java:259)
 at org.apache.tapestry5.ioc.internal.ModuleImpl.constructModuleBuilder(ModuleImpl.java:380)
 at org.apache.tapestry5.ioc.internal.ModuleImpl.access$1000(ModuleImpl.java:36)
 at org.apache.tapestry5.ioc.internal.ModuleImpl$5$1.invoke(ModuleImpl.java:313)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.ModuleImpl$5.run(ModuleImpl.java:308)
 at org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier$2.invoke(ConcurrentBarrier.java:198)
 at org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier$2.invoke(ConcurrentBarrier.java:196)
 at org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier.withWrite(ConcurrentBarrier.java:138)
 at org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier.withWrite(ConcurrentBarrier.java:204)
 at org.apache.tapestry5.ioc.internal.ModuleImpl$6.invoke(ModuleImpl.java:323)
 at org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier.withRead(ConcurrentBarrier.java:83)
 at org.apache.tapestry5.ioc.internal.ModuleImpl.getModuleBuilder(ModuleImpl.java:331)
 at org.apache.tapestry5.ioc.internal.ServiceResourcesImpl.getModuleBuilder(ServiceResourcesImpl.java:137)
 at org.apache.tapestry5.ioc.internal.ServiceBuilderMethodInvoker.createObject(ServiceBuilderMethodInvoker.java:47)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator$1.invoke(OperationTrackingObjectCreator.java:45)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator.createObject(OperationTrackingObjectCreator.java:49)
 at org.apache.tapestry5.ioc.internal.SingletonServiceLifecycle.createService(SingletonServiceLifecycle.java:29)
 at org.apache.tapestry5.ioc.internal.LifecycleWrappedServiceCreator.createObject(LifecycleWrappedServiceCreator.java:52)
 at org.apache.tapestry5.ioc.internal.InterceptorStackBuilder.createObject(InterceptorStackBuilder.java:56)
 at org.apache.tapestry5.ioc.internal.RecursiveServiceCreationCheckWrapper.createObject(RecursiveServiceCreationCheckWrapper.java:60)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator$1.invoke(OperationTrackingObjectCreator.java:45)
 at org.apache.tapestry5.ioc.internal.InvokableToRunnable.run(InvokableToRunnable.java:36)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.run(OperationTrackerImpl.java:48)
 at org.apache.tapestry5.ioc.internal.OperationTrackerImpl.invoke(OperationTrackerImpl.java:89)
 at org.apache.tapestry5.ioc.internal.PerThreadOperationTracker.invoke(PerThreadOperationTracker.java:68)
 at org.apache.tapestry5.ioc.internal.RegistryImpl.invoke(RegistryImpl.java:869)
 at org.apache.tapestry5.ioc.internal.OperationTrackingObjectCreator.createObject(OperationTrackingObjectCreator.java:49)
 at org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator.createObject(JustInTimeObjectCreator.java:65)
 at $ServletApplicationInitializer_11e7601a5f6.delegate($ServletApplicationInitializer_11e7601a5f6.java)
 at $ServletApplicationInitializer_11e7601a5f6.initializeApplication($ServletApplicationInitializer_11e7601a5f6.java)
 at org.apache.tapestry5.TapestryFilter.init(TapestryFilter.java:91)
 at org.mortbay.jetty.servlet.FilterHolder.start(FilterHolder.java:71)
 at org.mortbay.jetty.servlet.WebApplicationHandler.initializeServlets(WebApplicationHandler.java:310)
 at org.mortbay.jetty.servlet.WebApplicationContext.doStart(WebApplicationContext.java:509)
 at org.mortbay.util.Container.start(Container.java:72)
 at org.mortbay.http.HttpServer.doStart(HttpServer.java:708)
 at org.mortbay.util.Container.start(Container.java:72)
 at org.apache.tapestry5.test.JettyRunner.createAndStart(JettyRunner.java:140)
 at org.apache.tapestry5.test.JettyRunner.(JettyRunner.java:65)
 at org.apache.tapestry5.test.AbstractIntegrationTestSuite.setup(AbstractIntegrationTestSuite.java:261)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:585)
 at org.testng.internal.MethodHelper.invokeMethod(MethodHelper.java:580)
 at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:416)
 at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:154)
 at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:88)
 at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:167)
 at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:104)
 at org.testng.TestRunner.runWorkers(TestRunner.java:720)
 at org.testng.TestRunner.privateRun(TestRunner.java:590)
 at org.testng.TestRunner.run(TestRunner.java:484)
 at org.testng.SuiteRunner.runTest(SuiteRunner.java:332)
 at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:327)
 at org.testng.SuiteRunner.privateRun(SuiteRunner.java:299)
 at org.testng.SuiteRunner.run(SuiteRunner.java:204)
 at org.testng.TestNG.createAndRunSuiteRunners(TestNG.java:864)
 at org.testng.TestNG.runSuitesLocally(TestNG.java:830)
 at org.testng.TestNG.run(TestNG.java:748)
 at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:73)
 at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:124)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:613)
 at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:622)
 at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:584)
 ... 137 more

... but that's not exactly helpful. Why was it trying to build the Spring ApplicationContext at that time?

That's where Tapestry 5.1 comes in; it carefully tracks what is going on in the IoC container, using a kind of nested diagnostic context:

[ERROR] Registry Error creating bean with name 'upcase' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.example.testapp.services.StringTransformer]: : No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean
[ERROR] Registry Operations trace:
[ERROR] Registry [ 1] Realizing service ServletApplicationInitializer
[ERROR] Registry [ 2] Invoking org.apache.tapestry5.services.TapestryModule.buildServletApplicationInitializer(Logger, List, ApplicationInitializer) (at TapestryModule.java:1031)
[ERROR] Registry [ 3] Constructing module class org.apache.tapestry5.services.TapestryModule
[ERROR] Registry [ 4] Determining injection value for parameter #1 (org.apache.tapestry5.ioc.services.PipelineBuilder)
[ERROR] Registry [ 5] Resolving object of type org.apache.tapestry5.ioc.services.PipelineBuilder using MasterObjectProvider
[ERROR] Registry [ 6] Resolving Spring bean of type org.apache.tapestry5.ioc.services.PipelineBuilder
[ERROR] Registry [ 7] Realizing service ApplicationContext
[ERROR] Registry [ 8] Invoking org.apache.tapestry5.internal.spring.SpringModuleDef$1$1@2cb491
[ERROR] Registry [ 9] Creating Spring Application Context
[ERROR] ApplicationContext Construction of service ApplicationContext failed: Error creating bean with name 'upcase' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.example.testapp.services.StringTransformer]: : No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean
org.apache.tapestry5.ioc.internal.OperationException: Error creating bean with name 'upcase' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.example.testapp.services.StringTransformer]: : No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.example.testapp.services.StringTransformer] is defined: Unsatisfied dependency of type [interface org.example.testapp.services.StringTransformer]: expected at least 1 matching bean

That's a bit clearer, isn't it, as long as you realize what the MasterObjectProvider is ... and that we've contributed into it some code to resolve dependencies against the Spring ApplicationContext. In fact, what happened is that the test case failed because I haven't implemented a way for a Spring bean to receive a Tapestry service as an injection, and because Spring does not lazily initialize its beans by default, we see that failure immediately the first time we try to calculate an injection.

Off topic: You Aint Got No Pancake Mix!

I have a new hero:

Taking on intolerance (religious or otherwise) with humor is a great tactic.

Friday, December 12, 2008

Tapestry 5.0 Final Release - 5.0.18

After nearly three years of development, the final release of Apache Tapestry 5.0 is now available for download.

Apache Tapestry 5 is a total rewrite of the Tapestry web application framework, bringing forward Tapestry's core concepts: reusable components, true encapsulation, readable templates, well thought-out localization/internationalization, and easy management of server-side state.

Tapestry 5 builds on top of this with a host of new features:

  • True POJO component classes: no base classes to extend, no interfaces to implement.
  • Live class reloading: no need to redeploy to see code changes.
  • XML templates with namespaces.
  • Minimal configuration via naming conventions and annotations.
  • Integrated Ajax support, built on top of Prototype and Scriptaculous.
  • Automatic client-side form input validation.
  • High performance via pooled objects (and by avoiding the use of reflection).
  • Automatic REST-style URLs.
  • Built-in integration with Hibernate and Spring.
  • Best-of-breed exception reporting.
  • Built-in extensible mega-components: BeanEditForm, BeanDisplay and Grid (to edit and display any JavaBean or collection of JavaBeans).

Tapestry organizes your application into pages, and components within pages; pages and components are ordinary POJOs: not singletons (like servlets). Tapestry combines pages, page templates, components, component templates, and other resources together for you, managing server-side state, the creation of URLs and the dispatch of incoming requests. You build your application in terms of the methods and properties of your objects, not in terms of URLs or the Servlet API.

Tapestry features great exception reporting to keep you on track, and live class reloading to keep you agile. Tapestry templates are XML documents, using a namespace for Tapestry-specific elements. Tapestry is designed to be easy to develop, using any standard IDE with an XML editor.

Tapestry is simple, sensible and fun. It keeps you productive by freeing you from the boring, mechanical aspects of web application development. You can stay focused on what makes your application interesting and unique, and let Tapestry handle all the ugly plumbing.

Tapestry is made available under the Apache Software License 2.0. Tapestry is free to download, free to use, free to redistribute and free to modify.

Tapestry 5.0.18

Clojure: The Hundred Year Language

Back in 2003, Paul Graham gave a keynote speech entitled "The Hundred Year Language" (he later expanded this for his book "Hackers and Painters"). He envisioned what an early 22nd century programming language would look like ... what would control the flying cars and robot spaceships.

To my eyes, it looks a lot like Clojure.

He talks about languages that can be highly expressive, yet can be tuned for better performance. He explicitly references Lisp, Clojure's very-close cousin. He's concerned with parallel computation (which is to say, concurrency) ... which is one of Clojure's strong suits.

In any case, check it out ... and see if you are seeing what I'm seeing.

Monday, December 08, 2008

What's on the doorstep?

I haven't even quite moved into my new house, but what was waiting for me on the porch? Programming in Scala. Of course, I seem to be straying towards Clojure in terms of where-to-go-on-the-JVM-after-Java. But we'll see. Certainly reading about any new and powerful language will give you ideas even if you don't adopt it for day-to-day usage.

Sunday, December 07, 2008

ANTLR and code generation

In between packing (I'm moving across town) I'm doing a bit of work for Tapestry 5.1, TAP5-79: Improve Tapestry's property expression language to include OGNL-like features. People really miss being able to do a few cool things in OGNL, such as create lists and maps on the fly ... this is not uncommon when creating a page activation context.

The 5.0 code was based on regular expressions and hand parsing because it only supported a very limited number of options. For 5.1, the grammar will grow considerably, adding options for list and map creation, method invocation (with parameters), and perhaps property projection and list filtering. Hand-tooled parsers aren't going to keep up, so it was time to switch to a more complete solution.

I ended up choosing ANTLR because it seems well supported, has a book and good online documentation, and a set of supporting tools. ANTLR is used elsewhere as well, for example by Hibernate to parse HQL.

There is even decent support for ANTLR with Maven (while Tapestry still builds with Maven, something I hope to address soon). Because of this, I only check my grammar files into SVN, not the generated files; on the continuous integration server, the ANTLR plugin generates the lexer and parser code fresh for each build.

The only real down-side is the runtime dependency ... about 113K and problematic if Tapestry is ever combined with some other tool that has a dependency on a different and incompatible version. Hibernate (for better or worse) uses the ANTLR2 runtime library, which uses different package names.

My first step was to re-create Tapestry 5.0's behavior on top of ANTLR. Because of some complexity in the lexical part of the grammar (that darn ".." operator!) it took quite a bit of head bashing. I did eventually figure it out, and did what any self-respecting coder should do ... leave a simple, useful, documented example for the next poor slob.

Now I'm back into the side of code generation; Tapestry's property expression grammar is converted directly into bytecode; the intermediate language is Javassist, which is a significant subset of Java. So I parse the property expressions into a AST (abstract syntax tree), then generate what looks like Java code from that, which gets compiled in-process and turned directly into instantiable classes.

How would you test something like that? At one time, I would try to unit test that the generated code was correct. Eventually I hit some bugs where my tests passed, but the generated code was incorrect.

With code generation, there is no such thing as a unit test, it's always an integration test. You can try and limit the scope, but there's too many moving parts for a unit test to useful or credible.

Instead, I test my parsing and code generation logic by testing the generated objects' behavior. So I feed in a large number of expressions and objects to have expressions evaluated upon, and check that the results I get by reading and setting property expressions is correct. If I get the right results, I know the generated code is good.

Monday, November 24, 2008

Goodbye Vienna, Hello NetNewsWire

Seems like the Mac has a huge number of RSS readers. For a while I was using Vienna, but it stopped working after a recent update (no blogs ever updated!). So I just switched to NetNewsWire. So far, I like it quite a bit!

I was even able to keep my old subscriptions by dragging them (alas, one at a time) from the Vienna window the the NNW window.

I'm sure everyone has their favorite, and its not like I did a systematic survey; just a quick couple of Google searches and decided I liked it.

Friday, November 21, 2008

Maven: Displaying the version number in the generated site

It took a lot of ferreting out, but I eventually learned that you can put a <version/> tag in your site.xml, and Maven will put the project version number into the generated site. By default, it appears at the top, next to the (default position of) the publish date. <version/> can have a position attribute, as with <publishDate/>.

Begin rant.

And by ferreting, I mean about an hour of following broken links and scanning undocumented code, jumping to random places in the Maven SVN, trying experiments, and other forms of unnecessary guesswork. As usual, it would have been nice if it was documented; likewise it would be nice if the Maven brats ate their own fucking dog food when it comes to site generation.

As is, if you want to figure this stuff out, your have to guess and hunt around to figure out which of the bajillion plugins is responsible, then you have to guess where the site for that plugin is generated (or try a Google search and hunt around), and then guess where the source code is in the repository (since the documentation is usually fucking wrong and entirely missing). Fucking project comprehension my ass.

The Maven crew has always been about slopping out undocumented, broken shit, rather than actually producing useful tools. I can't wait to get off of it forever.

End rant. I still like the concept of the repository and the transitive dependencies; that aspect of Maven is worthwhile, but as a build tool, it sucks up far more time and energy than it saves. Possibly an order of magnitude more.

Wednesday, November 19, 2008

Ready for 5.0.17?

Looks like 5.0.16 will not be the final release, there will be a very modest 5.0.l7 that addresses a couple of annoyances that didn't have good work-arounds.

Sunday, November 16, 2008

Tapestry 5.0.16 (Release Candidate) it OUT!

The latest release of Tapestry, Tapestry 5.0.16 (Release Candidate), is now available.

Tapestry 5.0.16 is the release candidate; we encourage users to download this version. In about a month, the Tapestry PMC will run a vote to grant it release status, barring any blocker bugs (critical bugs with no workaround).

In the two months since the previous release, we've addressed over 80 issues, including many bugs and a few last minute improvements. New features include a LinkSubmit component (dearly missed from Tapestry 4), new support for reporting Ajax errors on the client side, smarter client-side validation, support for several new locales, and much new documentation.

Tapestry 5.0.16 is available for download, or via the central Maven repository.

Thursday, November 13, 2008

Clojure Baby Steps

Getting some baby steps going with Clojure. What threw me for a while is how difficult it was to get to do some basic output.

I wanted to see the JVM system properties. It's easy to get them, Java inter-operation is strong in Clojure, but in a readable format?

user=> (System/getProperties)
#=(java.util.Properties. {"java.runtime.name" "Java(TM) 2 Runtime Environment, Standard Edition", "sun.boot.library.path" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Libraries", "java.vm.version" "1.5.0_16-133", "awt.nativeDoubleBuffering" "true", "gopherProxySet" "false", "java.vm.vendor" "Apple Inc.", "java.vendor.url" "http://www.apple.com/", "path.separator" ":", "java.vm.name" "Java HotSpot(TM) Client VM", "file.encoding.pkg" "sun.io", "sun.java.launcher" "SUN_STANDARD", "user.country" "US", "sun.os.patch.level" "unknown", "java.vm.specification.name" "Java Virtual Machine Specification", "user.dir" "/Users/Howard", "java.runtime.version" "1.5.0_16-b06-284", "java.awt.graphicsenv" "apple.awt.CGraphicsEnvironment", "java.endorsed.dirs" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/endorsed", "os.arch" "i386", "java.io.tmpdir" "/tmp", "line.separator" "\n", "java.vm.specification.vendor" "Sun Microsystems Inc.", "os.name" "Mac OS X", "sun.jnu.encoding" "MacRoman", "java.library.path" ".:/Users/Howard/Library/Java/Extensions:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java", "java.specification.name" "Java Platform API Specification", "java.class.version" "49.0", "sun.management.compiler" "HotSpot Client Compiler", "os.version" "10.5.5", "user.home" "/Users/Howard", "user.timezone" "", "java.awt.printerjob" "apple.awt.CPrinterJob", "file.encoding" "MacRoman", "java.specification.version" "1.5", "java.class.path" "/usr/local/clojure/clojure-contrib.jar:/usr/local/clojure/clojure-lang-1.0-SNAPSHOT.jar", "user.name" "Howard", "java.vm.specification.version" "1.0", "java.home" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home", "sun.arch.data.model" "32", "user.language" "en", "java.specification.vendor" "Sun Microsystems Inc.", "awt.toolkit" "apple.awt.CToolkit", "java.vm.info" "mixed mode", "java.version" "1.5.0_16", "java.ext.dirs" "/Users/Howard/Library/Java/Extensions:/Library/Java/Extensions:/System/Library/Java/Extensions:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext", "sun.boot.class.path" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/ui.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/laf.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/sunrsasign.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/jsse.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/jce.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/charsets.jar", "java.vendor" "Apple Inc.", "file.separator" "/", "java.vendor.url.bug" "http://bugreport.apple.com/", "sun.io.unicode.encoding" "UnicodeLittle", "sun.cpu.endian" "little", "mrj.version" "1050.1.5.0_16-284", "sun.cpu.isalist" ""})
user=> 

So I thought I'd print out the results, one item per line.

user=> (range 5)
(0 1 2 3 4)
user=> (for [x (range 5)] (println x))
(0
nil 1
nil 2
nil 3
nil 4
nil)
user=> 

Hm. This took a while to figure out. The trick is that for is a lazy operator, used for list comprehensions. That is, the sequence produced by for is lazy, you have to start navigating the sequence for it to fully execute. Thus, what we see above is two different streams of output, mixed together: A list: (nil nil nil nil nil) interspersed with the println calls. Here's the order of operations:

  • The Repl starts to print the sequence, starting with a "(" before the first atom
  • The first atom is evaluated, printing "0" and returning nil
  • The Repl prints the "nil"
  • The second atom is evaluated (lazily), printing "1" and returning nil
  • The Repl prints the second "nil"
  • And so on ...

That's a lesson ... in a lazily-evaluated world, even the most basics ideas have to be thrown out the window. You would never see this in Haskell, because it creates impenetrable barriers between functional, side-effect free code and any code that communicates with the outside world, even something as simple as println.

The solution? Collapse the list to a string and print that at the end:

user=> (use 'clojure.contrib.str-utils)
nil
user=> (println (str-join "\n" (range 5)))
0
1
2
3
4
nil
user=>

Baby steps. Sometimes the simplest things are tricky, but I suspect the more advanced things are easier.

Tapestry 5 Ajax Screencast

This is a follow on to my previous JSF comparison; Jim Discoll produced a Simple Ajax JSF example, and this screencast is the Tapestry 5 equivalent. I promise I'll stop now!

Wednesday, November 12, 2008

Getting started with Clojure

Clojure Logo Whenever I finish a major piece of work (Tapestry 5.0.16, in this case), I like to try learning some new things before I start the next major phase. I've got a pent-up stack of things to try and learn, but foremost of these is Clojure.

I've always been fascinated with Lisp, though I last used it in college, nearly 20 years ago. Still, even then, I considered it a kind of litmus test ... if you didn't "get" Lisp (and, especially, recursion) you really were limited in how far you could go in this industry.

In any case, Clojure is something new; it's a Lisp, meaning it is similar to Common Lisp ... but it is not a common Lisp, it's a new functional language designed for high concurrency but also full interoperation with the JVM. In other words, it's like Scala, a new language explicitly tied to the JVM, but it goes the opposite direction in terms of the type system.

What's attracting me to it, rather than Scala (or Haskell for that matter) is the syntax. Scala's type system has been pretty daunting for me, and its syntax does reflect that. Lisp has the simplest syntax you can imagine.

The fact that Clojure supports Erlang-ish agents as well as Software Transactional Memory means that it is built for very high concurrency applications ... but you can always escape down to the nitty gritty Java level.

In other words, I've been buying into the "less-is-more" philosophy of Clojure vs. the "more-is-more" philosophy of Scala. Further, it's combining nearly 50 years of Lisp concepts with the ubiquitous and fast JVM platform, and embracing relatively new ideas to support concurrency, with a target on the dozens or hundreds of cores typical machines are expected to have in just a few years.

I've started reading Stuart Halloway's book, Programming Clojure. Fun read so far, Stuart's style is very approachable.

However, Clojure is right on the bleeding edge, and getting an environment set up is still a bit of an exercise left up to the reader.

To keep things organized, I created a /usr/local/clojure directory (I'm on a Mac) to keep the various Clojure resources stable and centrally located.

First is Clojure itself. There are downloads available, but Stu's book is already ahead of the latest download, so you'll want to build it yourself.

svn co https://clojure.svn.sourceforge.net/svnroot/clojure/trunk clojure
It builds with Maven in a few seconds. It's relatively small too .. 461K, and that includes a private copy of ASM. Just build with mvn clean install and copy the JAR file to /usr/local/clojure/clojure.jar.

Next is the standard contrib library for Clojure; this is needed for many of the examples in Stu's book. I haven't found an easy download for this yet, so I downloaded the source and built it myself:

svn co https://clojure-contrib.svn.sourceforge.net/svnroot/clojure-contrib/trunk clojure-contrib 

This one has an Ant build. It's really just a JAR file of Clojure script files. I recommend building it yourself, but if you are in a rush, I've placed a copy of clojure-contrib.jar on my website for download. You can imagine that it will be quickly out of date.

Next up is Emacs. Clojure syntax is very close to Lisp syntax, and nothing edits Lisp syntax better than Emacs, which is itself written in its own Lisp dialect.

I haven't used Emacs in anger in years, and then it was a bit of a monster (a version written in PL/1!). In fact, its probably been long enough that I won't remember the wrong, broken key bindings of the PL/1 version. In any case, I'm on a Mac, so Emacs is built in ... but there's a better alternative, Aquamacs Emacs which is striving for a best-of-both worlds approach. If you are on windows, Cygwin (as usual) is the way to go, and there's a lot of discussion on the Clojure Programming Wiki Page as well.

Next up we need to set up a Clojure mode for Eclipse. This is available at http://clojure.codestuffs.com/. Download, unpack, and copy the files to /usr/local/clojure as well.

The last step is to modify Emacs to load the Clojure mode. The following can go into ~/.emacs for a traditional Emacs, or ~/Library/Preferences/Aquamacs Emacs/Preferences.el for AquaEamcs:


(setq inferior-lisp-program
                                        ; Path to java implementation
      (let* ((java-path "java")
                                        ; Extra command-line options
                                        ; to java.
             (java-options "-Xms100M -Xmx600M")
                                        ; Base directory to Clojure.
                                        ; Change this accordingly.
             (clojure-path "/usr/local/clojure/")
                                        ; The character between
                                        ; elements of your classpath.
             (class-path-delimiter ":")
             (class-path (mapconcat (lambda (s) s)
            ; Include all *.jar files in the clojure-path directory
        (file-expand-wildcards (concat clojure-path "*.jar"))
                                    class-path-delimiter)))
        (concat java-path
                " " java-options
                " -cp " class-path
                " clojure.lang.Repl")))

;; Require clojure-mode to load and associate it to all .clj files.

(add-to-list 'load-path "/usr/local/clojure")

(require 'clojure-mode)

(setq auto-mode-alist
      (cons '("\\.clj$" . clojure-mode)
            auto-mode-alist))

Of course, you'll need to modify this to reflect the proper path you've been placing all these files.

Note: Stu's book mentions adding the sources from the book to the classpath as well; you can see in the above code where a list of classpath entries is being assembled, and you could add "/path/to/book/source" into the list.

When you next restart Emacs, you'll want to find a Clojure file to activate Clojure Mode ... any file with a .clj extension will activate Clojure Mode. Then C-c C-z will launch Clojure.

Lots to learn, lots to learn.

Monday, November 10, 2008

Tapestry 5.0.16 (Release Candidate) is under vote

Tapestry 5.0.16 is being voted upon for release as the RC (Release Candidate). The vote will run for a few days, then we'll make it publically available from Apache (and Maven). A couple of weeks from now, we can vote it as the GA release (assuming nothing horrible announces itself in the meantime).

Sunday, November 02, 2008

"Simple" JSF 2.0 Component vs. Tapestry

I just saw an amusing example of JSF 2.0 on Jim Driscoll's blog. He's creating a simple output-only component to display some text in yellow, using an inline CSS style. To make things 'simpler' (my quotes) he's using JSF.

Here's his JSF page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ez="http://java.sun.com/jsf/composite/simpleout">
<h:head>
    <title>Yellow Text Example</title>
</h:head>
<h:body>
        <h1>Editable Text Example</h1>
        <h:form id="form1">
                <ez:out value="Test Value"/>
            <p><h:commandButton value="reload"/></p>
            <h:messages/>
        </h:form>
</h:body>
</html>

Not too bad ... the relevant part is <ez:out value="Test Value">, that's the reference to his Out component inside his simpleout library. JSF puts a lot of interpretation into the namespace URLs.

On the other hand, the implementation of this simple component seems a bit out of hand:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<head>
<title>This will not be present in rendered output</title>
</head>
<body>

<composite:interface name="yellowOut"
                     displayName="Very Basic Output Component"
                     preferred="true"
                     expert="false"
                     shortDescription="A basic example">
    <composite:attribute name="value" required="false"/>
</composite:interface>

<composite:implementation>
    <h:outputText value="#{compositeComponent.attrs.value}" style="background-color: yellow"/>
</composite:implementation>
</body>
</html>

You can kind of vaguely see, inside all that clutter, what the component is doing. I'm personally troubled by name="yellowOut" ... is the component's name "out" or "yellowOut"? Also, compared to the Tapestry version coming up, you can see that a few nods to visual builder tools are included.

So, what does the Tapestry equivalent look like?

Well, our page template would look something like*:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
  <title>Yellow Text Example (Tapestry)</title>
</head>
<body>
  <h1>Yellow Text Example</h1>
  <t:form t:id="form1">
    <t:out value="Test Value"/>
   <p><input type="submit" value="reload"/></p>
  </t:form>
</body>
</html>

The Tapestry namespace identifies elements and attributes that are not truly HTML, but controlled by Tapestry. This includes built-in Tapestry components such as Form as well as user-defined components such as Out.

The Out component itself is in two parts: a Java class to define the parameters (and, in a real world example, provide other logic and properties) and a matching template to perform output**. First the class:

package org.example.myapp.components;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.annotations.*;

public class Out
{
  @Property
  @Parameter(defaultPrefix = BindingConstants.LITERAL)
  private String value;
}

The @Parameter annotation on the value field marks the field as a parameter. The defaultPrefix attribute is configured to indicate that, by default, the binding expression (the value attribute inside the page template) should be interpreted as a literal string (as opposed to the normal default, a property expression).

The @Property annotation directs Tapestry to provide a getter and setter method for the field, so that it can be accessed from the template.

<span style="background-color: yellow">${value}</span>

Again, this is a trivial example. We didn't even have to define the Tapestry namespace for this template. The ${value} is an expansion which outputs the value property of the Out component instance, which itself is the literal string ("Test Value") bound to the Out component's value parameter.

Let's add a little improvement: If the value is null or empty, don't even render the <span> tag. This is accomplished in one of two ways.

First, we can change the template slightly, to turn the <span> tag into an If component that renders a span tag:

<span t:type="if" t:test="value" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd" style="background-color: yellow;">${value}</span>

The If component evaluates its test parameter and if true, renders its tag (the <span> in this case), its non-Tapestry attributes (style) and its body (${value}).

Alternately, we can leave the template alone and abort the component's render in the Java class:

  boolean beforeRenderTemplate()
  {
    return ! (value == null || value.equals(""));  
  }

This is a Tapestry render phase method, a kind of optional callback method that can control how the component is rendered. We return true to render normally, and false to skip the template rendering. Tapestry invokes this method at the right time based on a naming convention, though an annotation can be used if that is more to your taste.

This is a good example of how Tapestry really cuts to the chase relative to other frameworks. Both the usage of the component and its definition are simple, concise and readable. Further, Tapestry components can scale up nicely, adding sub-components in the template, and more properties and logic in the Java class. Finally, we can also see a hint of how Tapestry relieves you of a lot of low-level drudgery, such as using the @Property annotation rather than writing those methods by hand (Tapestry has a wide ranging set of such meta-programming tools built in).

* I'm not sure why the original version used a form, but I've dutifully recreated that here.

** For a component this simple I'd normally not bother with a template and do it all in code.

Friday, October 31, 2008

Pushing towards 5.0.16

I've been steaming along towards the Tapestry 5.0.16 release. I've hit nearly all the major bugs and have also been implementing a number of irritating (but less important) fixes and improvements and working on some better documentation.

Tapestry 5 is a much better, faster, more powerful beast now than what I first envisioned over 2.5 years ago. There are aspects about Tapestry, connections between ideas, leveraging of concepts, and other things that I could not have predicated ahead of time. Building applications in T5, I always keep some attention on what's working well, and what could be improved (usually by making something automatic). Tapestry 5 is very, very concise.

Working at Formos for the last 11 months has given me the time and focus to work on Tapestry itself, as well as direct and indirect hands on experience to really know and understand the rough spots. Certainly, this has shown up as improved Ajax functionality, and a lot of other stuff under the covers.

I think some time next week we'll be drawing the line and starting a vote on 5.0.16. I fully expect to then vote it as the release candidate and, ultimately, the final release for 5.0.

Keep tuned the best is yet to come ... I have a lot of ideas for way to extend and improve Tapestry with out sacrificing backwards compatibility. I've made that claim before and I look forward to proving it valid.

Monday, October 27, 2008

An afternoon off from Tapestry

One thing about being an open source developer is that the concept of "time off" becomes somewhat vague. Working on stupid bugs and documentation is "grunge work" ... so that counts as "work" whereas creating a super-nifty new component or plugin is fun, and so that's play time. Either can happen during the week, or on the weekend.

Likewise, when I was vacationing in Paris it was play time ... except for the two clients* I met there, which was work. Except it was fun talking with people about what they're doing in Tapestry, what they look forward to, etc.

So, it was purely fun Saturday to dust off my Mindstorms NXT kit, which was previously languishing underneath a bookshelf for nearly two years, and start actually using it. Merlyn has much more room at his house, so we put it all together there.

We had some fun building the basic robots and doing the simple visual programming, and I'm looking forward to doing quite a bit more. The point isn't that we accomplished anything amazing (we surely didn't), but that I was doing something besides Tapestry, which is healthy for me. Not a big deal for Merlyn, he's much better at separating his work-week coding from his weekend hacking.

*Clients here means users of Tapestry, not (yet) clients of Formos.

Friday, October 17, 2008

Comic: Coding Appreciation

From Saturday Morning Breakfast Cereal:

Of course, since I work at home, I can live this out every single day.

Monday, October 13, 2008

Back from Europe

Just got back from Europe on Friday, and have been recovering from a nasty cold and jet lag. This week I'm prepping for another Tapestry 5 training session next week, in Denver. I'm also trying to nail down the remaining bugs standing in the way of a release candidate.

Sunday, September 21, 2008

Tapestry 5.0.15 released

Tapestry version 5.0.15 has been released. The good news is that this is the final beta. The bad news is that it was supposed to be the release candidate. After the release was created, and during the voting period, a couple of annoyances (mostly for non-English applications) were identified. Rather than postpone the release further, it was decided to continue with the release and have one more release as the release candidate (and likely final release).

Anyway, outside of these minor issues, this release is ready for prime time, with some significant improvements to client-side behavior and Ajax support, more localizations, improvements to the IoC container, and the solution to a significant deadlocking problem.

Meanwhile, I'm in London now after a very successful talk on Tapestry at JavaZone in Oslo. My goal for those sessions is shock and awe with a dash of humor, and I'm told I delivered, to nearly 100 attendees. Suzy was in the audience, as was Eitan Suez, James Ward and Scott Davis.

This week is Tapestry 5 training in London, and next week Haarlem (in Amsterdam). I get back to the US in October, and that's when we'll get the final Tapestry 5.0 release out the door ... and I'm already working on 5.1 in a branch.

Sunday, September 14, 2008

Vote for Tapestry 5.0.15 (Release Candidate) is under way

The vote to release Tapestry version 5.0.15 is under way. This is the release candidate. Once it's available, we'll give it a few weeks of exposure (while I'm in Europe) and then, barring any unforeseen critical bugs, we will vote it up as the 5.0 GA release.

I already have extensive plans for improvements to Tapestry in 5.1. I want one of the compelling reasons to use Tapestry to be performance, so I'm looking at automatically GZIPing content, compressing JavaScript (and perhaps combining JavaScript files together), making increased use of far-future expired headers and so forth. In other words, just make the framework do the right thing.

I think there's also room to optimize the server-side further. I have some ideas for limiting the number of render commands needed to render a page, and limit the amount of work wasted on event notifications that have no listener.

Saturday, September 13, 2008

Boost your Productivity with Tapestry

Just found a short article by Moataz Anany, detailing his search for a good Java web framework ... and how he's taken to Tapestry. He identifies some of the strengths and a few of the weaknesses of Tapestry.

A Succinct Definition of Science Fiction

Just a bit bored, redoing the Tapestry 5.0.15 build. Surfing the web during the build, and found Robert Heinlein's all purpose FAQ.

Science Fiction: Stories that would cease to exist if elements involving science or technology were omitted.

This puts Star Trek and Star Wars where they both belong: in fantasy. You could "swap out" phasers for guns, light sabers for magic swords, aliens for demons, planets for foreign cities or countries, and space ships for traditional transportation and not really change the Star Wars story, or most Star Trek episodes, at all. I tend to call this "Sci Fantasy"; most older Space Opera falls into this space as well.

Cryptonomicon and the Baroque Cycle are very firmly in SF however, since both stories are driven by science and technology and even the philosophy of science and technology (and the personalities behind those who invent science and technology).

My thought here is that the movies and television, which nominally have a large bandwidth of information, are actually too narrow to portray science fiction properly. Novels and even short stories, because they can more naturally go inside people's heads and reveal their thoughts, are much more adept at capturing what makes Science Fiction about science and not fantasy. But, of course, it's always on a spectrum ... virtually any story that places actual humans on distant planets beyond the solar system is implausible to a point that borders on fantasy, even if faster-than-light travel is avoided.

Thursday, September 11, 2008

Tapestry 5 IoC: Introducing Service Configurations

In the previous article, I discussed the basics of Tapestry 5 IoC. I focused on the terseness of Tapestry's container, even though everything occurs in Java code. I alluded to special features of Tapestry 5 IoC, service configurations. Let's start investigating those.

In traditional dependency injection, the relationship between a service and its dependencies is many-to-one: many services may inject a specific dependency. Whether that dependency is selected just by service type, or by service id, or by some other mechanism, it's still one single object.

Service configurations are somewhat inverted: they are a relationship from one service to many objects. The objects, or contributions, may be simple objects or may themselves be services.

Let's use a specific example from Tapestry to put this into perspective. Previously I showed the service builder method for the TranslatorSource service:

public static TranslatorSource buildTranslatorSource(ComponentInstantiatorSource componentInstantiatorSource, 
  ServiceResources resources)
{
    TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class);

    componentInstantiatorSource.addInvalidationListener(service);

    return service;
}

Let's dive a little deeper and look at what this service does. It's a source for Translator objects, which are an integral part of Tapestry's HTML form support. Translators are responsible for converting between server-side values (such as numbers, dates, and so forth) and client-side strings. They also play a role in client-side validation of user input.

Tapestry matches up properties that are edited by TextFields with corresponding Translator instances. This all happens inside the TextField component and is largely invisible to programmers. In any case, the TranslatorSource service is central:

public interface TranslatorSource
{
    /**
     * Returns the translator with the given logical name.
     *
     * @param name name of translator (as configured)
     * @return the shared translator instance
     * @throws RuntimeException if no translator is configured for the provided name
     */
    Translator get(String name);

    /**
     * Finds a {@link Translator} that is appropriate to the given type, which is usually obtained via {@link
     * org.apache.tapestry5.Binding#getBindingType()}. Performs an inheritanced-based search for the best match.
     *
     * @param valueType the type of value for which a default translator is needed
     * @return the matching translator, or null if no match can be found
     */
    Translator findByType(Class valueType);

    /**
     * Finds a {@link Translator} that is appropriate to the given type, which is usually obtained via {@link
     * org.apache.tapestry5.Binding#getBindingType()}. Performs an inheritanced-based search for the best match.
     *
     * @param valueType the type of value for which a default translator is needed
     * @return the matching translator
     * @throws IllegalArgumentException if no known validator matches the provided type
     */
    Translator getByType(Class valueType);
}

Here's where it gets interesting

So, what Translators are built into Tapestry? You might think you could tell by looking at the implementation of the service:

public class TranslatorSourceImpl implements TranslatorSource, InvalidationListener
{
    private final Map<String, Translator> translators = CollectionFactory.newCaseInsensitiveMap();

    private final StrategyRegistry<Translator> registry;

    public TranslatorSourceImpl(Collection<Translator> configuration)
    {
        Map<Class, Translator> typeToTranslator = CollectionFactory.newMap();

        for (Translator t : configuration)
        {
            translators.put(t.getName(), t);
            typeToTranslator.put(t.getType(), t);
        }

        registry = StrategyRegistry.newInstance(Translator.class, typeToTranslator, true);
    }

    public Translator get(String name)
    {

        Translator result = translators.get(name);

        if (result == null)
            throw new RuntimeException(ServicesMessages.unknownTranslatorType(name, InternalUtils
                    .sortedKeys(translators)));

        return result;
    }

    public Translator getByType(Class valueType)
    {
        Translator result = registry.get(valueType);

        if (result == null)
        {
            List<String> names = CollectionFactory.newList();

            for (Class type : registry.getTypes())
            {
                names.add(type.getName());
            }

            throw new IllegalArgumentException(ServicesMessages.noTranslatorForType(valueType, names));
        }

        return result;
    }

    public Translator findByType(Class valueType)
    {
        return registry.get(valueType);
    }

    public void objectWasInvalidated()
    {
        registry.clearCache();
    }
}

But you don't see any pre-defined Translator instances here ... just Collection<Translator> configuration passed to the constructor. Each Translator provides its name, and those all go into the translators map ... but the question is, where do they come from?

Jumping back to TapestryModule, we see a likely method:

public static void contributeTranslatorSource(Configuration<Translator> configuration)
{
    configuration.add(new StringTranslator());
    configuration.add(new ByteTranslator());
    configuration.add(new IntegerTranslator());
    configuration.add(new LongTranslator());
    configuration.add(new FloatTranslator());
    configuration.add(new DoubleTranslator());
    configuration.add(new ShortTranslator());
}

It's looking pretty likely that Tapestry supports string, byte, integer, long, float, double and short out of the box. The naming of this method is another example of convention over configuration. The prefix this time is contribute and the rest of the method name matches the service id, TranslatorSource.

The Configuration object has a single method, add():

public interface Configuration<T>
{
    /**
     * Adds an object to the service's contribution.
     *
     * @param object to add to the service's configuration
     */
    void add(T object);
}

So, Tapestry has invoked the contributeTranslatorSource() method, collected up the objects, the Translators, added to the configuration object, and converted the configuration object to a Collection, which is ultimately passed to the TranslatorSourceImpl constructor.

Seems awfully complicated, doesn't it? Well, it is nice (from a testing perspective) that TranslatorSourceImpl isn't tied to any particular implementations of Translator. But that's not the real benefit.

The real benefit, and this is the basis of the entire concept, is that you are not locked into just these Translators. You can define your own, and mix them in with the ones supplied by Tapestry. And you don't have to hack TapestryModule or TranslatorSourceImpl to do it.

Say your application defines a Currency class, to track currency amounts of orders and payments. That might be handy to use instead of double, for accuracy reasons. You might also want to parse and format currency values differently than naked doubles ... for example, to require exactly two digits of precision, or to ignore a leading dollar sign.

To mix in your own Translators, all you need to do is define your own module:

public class AppModule
{
  public static void contributeTranslatorSource(Configuration<Translator> configuration)
  {
    configuration.add(new CurrencyTranslator());
  }
}

This translator, CurrencyTranslator, will be indistinguishable from the default set of Translators provided by Tapestry; TranslatorSourceImpl will have no way of telling which Translators came from where. Your contributions are on an even footing with those provided by Tapestry itself.

You might ask in what order are these contribute methods are invoked? The answer is: Who knows? That's why the configuration is passed to the service implementation as (unordered) Collection, not (ordered) List. As we'll see in the next article, Tapestry has alternatives for when you care about ordering, or when you want your configuration in the form of a Map.

When are these methods called? They are called when the TranslatorSource service is realized, which happens when a method of the service is first invoked. Tapestry IoC is by default very lazy, it doesn't instantiate services until necessary. The service's proxy is responsible for this realization process, and its done in a thread-safe manner. Dealing with this kind of loose binding in a structured manner is very helpful: it means that the TranslatorSource service is simplified: it doesn't need a method to add a new Translator, there's fewer issues related to multiple threads, and the available set of Translators never changes, which makes the behavior of the service much more predictable.

Conclusion

We've only just pierced the surface of Tapestry configurations, but we're beginning to see what I mean when talk about Tapestry's extensibility. Much of the key behavior of Tapestry is specified in terms of this kind of configuration, or one of its close relatives. And, as the example showed, building a service that uses a configuration is just a matter of defining a parameter of type Collection in the service's constructor.

In future articles, I'll discuss other variations of service configurations, and show how to go meta with Tapestry by leveraging configurations in combination with service-building services!

Monday, September 08, 2008

Closures coming for Objective-C

Apparent, closures are coming for C and Objective-C. Objective-C was my first OO programming language, and the transition to Java was a painful one. For a long time, I couldn't go back because Objective-C was not garbage collected (this has changed). Then I couldn't go back because it didn't have closures (or even closures' red headed step child: the inner class).

But Objective-C with garbage collection and true closures? That's getting somewhere, fast.

And no I'm not rewriting Tapestry in Objective-C. You know that question was coming!

IntelliJ: Maven improvements in 7.0.4

I haven't updated my project dependencies in a bit since upgrading from IntelliJ 7.0.3 to 7.0.4. I was pleasantly surprised after updating my POM dependencies that the Maven support has improved. In the past, the Maven support had a tendency to select less than ideal values for each module's source paths ... and it would overwrite my customizations every time I did a sync. I had come to dread changing dependencies because of this.

In 7.0.4 I had to refresh my Maven projects list; this took a few minutes. But lo and behold; it did a perfect job, not altering my personal changes. Bravo!

Sunday, September 07, 2008

And the release candidate sprint is on!

Just rounding out the last few important bug fixes (and enhancements) that are needed for the Tapestry 5 release candidate. Can I get it together before I leave for my Europe trip on Sept. 15th? If not, the RC may wait until October.

Importantly, I think I've nailed TAPESTRY-2561, the thread deadlock bug.

I'm already looking forward to 5.1: I have lots of fully backwards compatible enhancements to make.

Java's jconsole to the rescue

If you find yourself doing something tricky with threads, such as rooting out some insidious thread deadlocks, jconsole is invaluable. Having it analyze the threads to get the deadlocks is awesome, and the stack trace even includes identifiers about what object monitors have been locked. Way better than sorting through all that, by hand, from a text stack dump.

Selenium tests just started hanging? Don't Panic!

So, here I am, in the middle of some intense debugging related to the dreaded combination of threads, class loaders and deadlocks and suddenly ... my integration tests no longer run!

Ultimately, my changes were very modest (a little bit of extra synchronization against the context class loader), so what gives?

Well, if your tests are like mine, and run against Firefox, your issue might be that Selenium is unable to start up Firefox if an upgrade has occurred, especially if the Firefox upgrade is not compatible with your plugins.

The solution? Start Firefox manually, to get through the dialogs it presents. Then rerun your tests.

Friday, August 29, 2008

Id conflict inside Web Browser

Just spent many minutes on a wild goose chase and the underlying cause was that I had a <div> and a <textarea> that shared the same id. $(id) returned the <div>, most likely because it occured first in the document. I then got an error because <div> doesn't response to activate() ... that's a Prototype method attached to form elements.

I would be tempted to make Tapestry enforce this (that the id attribute for any node was unique), but that won't proof it against partial renders under Ajax, so I don't know that there's any point.

What's really going on is that CSS rules based on element id rather than element class are evil, and should only be used as a last resort. The free CSS template I got off the web uses too many explicit ids where it could be using CSS classes. You get what you pay for.

Tuesday, August 26, 2008

Tapestry Europe Tour

I'm going to be in Oslo, Norway for JavaZone from Sep. 16 to Sep. 19th. I'm presenting on Thursday afternoon.

Next up, a week of Tapestry training in London.

I then fly to Amsterdam on Sep. 27th, for three more days of Tapestry training and then some vacation.

Next, down to Paris just for vacation before returning home on October 10th.

Should be a lot of fun, and a chance to meet with new and future Tapestry users. I can't wait!

Tapestry 5 IoC: Binding and Building Services

Tapestry 5 includes its own internal Inversion of Control container. This is often a point of contention ... why not just use Spring or (in more recent conversations) Guice?

That's a complex question; simply put, Tapestry has requirements as a framework that the other containers don't offer solutions to.

This posting is a simple introduction to the basics of Tapestry 5 IoC. In later postings, we'll get into more detail about the advanced features of Tapestry's IoC container, the ones that really distance it from Spring and Guice.

Tapestry uses the term "service" for the primary objects that it manages for you. Spring uses the term "bean". A service is normally an interface and a class that implements the interface. In the most typical case, only a single service implements the interface, but T5 IoC is fully capable of handling the case where one service interface has a number of distinct services; even the case where a single class is instantiated with a different configuration.

Every service has a unique id string. In most cases, this is just the simple name of the service interface. When the same interface is used by multiple services, you will have to identify the service id explicitly.

To keep things real, I'll use actual, though abbreviated, examples from Tapestry's code base.

T5 IoC uses module classes to identify what services are available. A module class is a POJO class with a special method on it, a method named bind(). A Tapestry application will consist of a number of modules: some modules provided by Tapestry itself, some by third party libraries or extensions, and some by the application itself. Tapestry mixes and matches all of this information, all of the services defined by each of the modules, into a single registry of services.

That may sound more complex than it really is. The reality is that in the bind() method, we simply match service interfaces to corresponding implementations:

public final class TapestryModule
{
    public static void bind(ServiceBinder binder)
    {
        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
        binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
        binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
        // ... and so on
    }
}

The ServiceBinder is uses generics to ensure that the class you specify implements the service interface. The API is a fluent interface: you can chain a few extra method calls onto bind() to override defaults, for example:

      binder.bind(ObjectProvider.class, AssetObjectProvider.class).withId("AssetObjectProvider");

TapestryModule actually defines quite a few additional services.

Let's look at an example:

public interface PersistentLocale
{
    void set(Locale locale);

    Locale get();

    boolean isSet();
}

I've stripped out the comments to save space ... but this service manages the user's locale; it's a key part of Tapestry 5's localization support. The implementation we'll see shortly works using HTTP Cookies, but that isn't important to the code that uses PersistentLocale.

public class PersistentLocaleImpl implements PersistentLocale
{
    private static final String LOCALE_COOKIE_NAME = "org.apache.tapestry5.locale";

    private final Cookies cookieSource;

    public PersistentLocaleImpl(Cookies cookieSource)
    {
        this.cookieSource = cookieSource;
    }

    public void set(Locale locale)
    {
        cookieSource.writeCookieValue(LOCALE_COOKIE_NAME, locale.toString());
    }

    public Locale get()
    {
        String localeCookieValue = getCookieValue();

        return localeCookieValue != null ? LocaleUtils.toLocale(localeCookieValue) : null;
    }

    private String getCookieValue()
    {
        return cookieSource.readCookieValue(LOCALE_COOKIE_NAME);
    }

    public boolean isSet()
    {
        return getCookieValue() != null;
    }
}

T5 IoC does all injection through the constructor. This is to encourage you to write your dependencies into final fields, which is thread safe. Typically, your services will be immutable objects: all fields final.

PersistentLocaleImpl has a dependency on another service, Cookies. And what is Cookies? It's another service interface. Notice that we don't have to do any extra configuration here ... since there's one, and only one, service that implements the Cookies interface, that's all the information Tapestry needs to wire things together.

Other service implementations inside Tapestry have as few as zero dependencies, and as many as eight. There's no theoretical limit, it's just that having more than a few dependencies is a design smell ... that you can break things into smaller pieces.

One of the hallmarks of coding using an IoC container is this level of terseness, also knows as passing the buck. Given that PersistentLocaleImpl is concerned with HTTP cookies, you'd think that it would, somehow, get ahold of the HttpServletRequest object and start invoking getCookies() and addCookie() on it ... but instead, all the details of interfacing with the Servlet API and the rather awkward API for HTTP cookies is swept into a corner, inside the Cookies service implementation.

That's great ... it makes the implementation of PersistentLocaleImpl (as well as any other code that happens to care about HTTP cookies) that much simpler and easier to test.

Service Lifecycle

Tapestry services have a specific lifecycle:

defined
Identified via the ServiceBinder, but not yet referenced
virtual
A proxy exists that has been injected as a dependency of some other service, but no methods of the proxy have been invoked
realized
The service has been instantiated with dependencies

The beauty of this is that your code is completely unaware of this; all the work inside Tapestry ... creating proxies, realizing service implementations, occurs in a lazy but thread-safe manner. It's as if all the services are instantiated at startup without taking the time to actually do that work.

Again, the appeal of an IoC container is that you get to break your application into tiny, easily tested bits, and the IoC container is responsible for connecting everything back together at runtime. It really leads to a new way of coding, and thinking about coding.

Service Builder Methods

Sometimes just instantiating a class is not enough; there may be additional configuration needed as part of instantiating the class. Tapestry 5 IoC's predecessor, HiveMind, accomplished such goals with complex service-building services. It ended up being a lot of XML.

T5 IoC accomplishes the same, and more, using service builder methods; module methods that construct a service. A typical case is when a service implementation needs to listen to events from some other service:

public static TranslatorSource buildTranslatorSource(ComponentInstantiatorSource componentInstantiatorSource, 
  ServiceResources resources)
{
    TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class);

    componentInstantiatorSource.addInvalidationListener(service);

    return service;
}

Module methods prefixed with "build" are service builder methods. The service interface is defined from the return value (TranslatorSource). The service id is explicitly "TranslatorSource" (that is, everything after "build" in the method name).

Here, Tapestry has injected into the service builder method. ComponentInstantiatorSource is a service that fires events. ServiceResources is something else: it is a bundle of resources related to the service being constructed ... including the ability to instantiate an object including dependencies. What's great here is that buildTranslatorSource() doesn't need to know what the dependencies of TranslatorSourceImpl are, it can instantiate the class with dependencies using the autobuild() method. The service builder then adds the new service as a listener of the ComponentInstantiatorSource, before returning it.

This is a great separation of concerns: we have a construction concern (being an event listener) that's distinct from the operational concerns of TranslatorSource. And they are kept separate.

Conclusion

Tapestry IoC has simple and concise API for defining services and, in most cases, handles dependencies automatically. The end result is that it becomes child's play to divide-and-conquer: convert old, monolithic, hard to maintain code into small, easily tested, easily understood services.

In future postings, I'll go into more detail about the more advanced features of Tapestry: service scopes, service configurations and service decorations.

Ajax and Selenium: waitForCondition()

Selenium is a very useful tool but it can be very, very obtuse.

One challenge is dealing with Ajax; you might click on a button, but without a full page refresh, it's hard to know when to look for expected changes via Ajax and DHTML.

In the past, my test suites had short sleeps, a few hundred milliseconds. This makes them fail sporadically ... every once and a while on my MacBook Pro I'm doing so much other stuff while the tests run that the timing goes screwy.

You're then left with a difficult choice: sleep too short and the tests may fail. Sleep too long and your tests will always be slow.

Fortunately, there's a third option: Selenium's waitForCondition call. Of course, their documentation is worthless.

What it is supposed to do is evaluate a JavaScript snippet repeatedly, until the snippet returns true. However, it's tricky to get right. Like much in JavaScript, it's about context.

In my case, I wanted to wait for a client-side popup <div> to appear:

        type("amount", "abc");
        type("quantity", "abc");

        click(SUBMIT);

        waitForCondition("document.getElementById('amount:errorpopup')", "5000");

        assertText("//div[@id='amount:errorpopup']/span", "You must provide a numeric value for Amount.");
        assertText("//div[@id='quantity:errorpopup']/span", "Provide quantity as a number.");

JavaScript treats null as false, and getElementById() returns null if an element with the id does not exist.

I'm making the assumption that once one of two <div> elements appears, they both will. I then use some XPath to get the text inside the <span> inside each <div>, to make sure the correct message was displayed to the user.

But this code doesn't work.

The problem is that document isn't what you'd expect; I'm guessing that it's some other frame inside the browser (Selenium's UI and code executes in one frame, which runs the actual application inside the second frame).

The solution took some research and the sacrifice of a few small furry animals to obtain:

        waitForCondition("selenium.browserbot.getCurrentWindow().document.getElementById('amount:errorpopup')", "5000");

That works, and it works much faster than adding a Thread.sleep() in the middle of my code.

Tapestry @ JavaZone 2008

I'll be presenting on Tapestry 5 at JavaZone. I'll be in Lab 2, on Sept 18th, at 2:15. With less than an hour to talk about Tapestry 5 and compare it briefly to Wicket and Rails ... well, I'll be talking fast!

I also just noticed that there's a session on comparing IoC containers that includes Tapestry 5 IoC. That's Lab 6, on the 17th, at 5pm.

Friday, August 22, 2008

Stupid DateFormat

Working on some bugs related to the DateField component. Did you know that (for US English, anyway) DateFormat.getDateInstance(DateFormat.SHORT) provides back a DateFormat equivalent to "M/d/yy"?

That's a problem, because it has a tendency to format Dates to be two digit years. When it parses those same strings, it ends up considering the date to be in the first century AD.

Thus in this code:

  Date date1 = new Date(...);
  Date date2 = format.parse(format.format(date1));

date2 may or may not be the same as date1. Certainly any time information will be stripped out (this is expected), but often date2 will be in the wrong century.

I'm working right now to get the correct localized DateFormat, but ensure that the year portion is four digits, not two.

Thursday, August 21, 2008

Joshua Considers Tapestry 5

Josua Partogi has put together a nice quick intro to Tapestry 5. My only complaints are that the formatting makes the blog posting a bit hard to read, and that he included a lot of extra stuff in AppModule (put there by the Tapestry quickstart archetype) that's not required, such as the request timing filter. The archetype adds that as an example of what you can do. Finally, he uses the term "template" where all other documentation calls that type of component a "layout" (or, in Tapestry 4 terms, a "border").

Wednesday, August 13, 2008

My Ideas for Java Closures

According to Neal Gafter, the story for closures it still wide open.

As someone who writes a lot of code using closure-like mechanisms ... in the form of lots of inline inner classes ... I have a few idea of what I want in a solution. I think I'm writing some powerful and elegant code today, but that elegance in function is undermined by some severe awkwardness in its expression as Java code.

It really comes down to conciseness. I can accomplish pretty much everything I need using inner classes and holder objects, such as AtomicInteger and friends. But it ends up being more code than I'd like.

What I want (to borrow Stu's term) is to emphasize the essence of my logic, and strip away the ceremony: the naming of the interface (it should be known from context), the types of parameters (just the names, please), the list of thrown exceptions, etc.

First of all, let's constrain the problem. Closures would be defined in terms of an interface, an interface that contains a single method. Attempting to use the concise syntax with an interface that contains multiple methods would simply be a compiler error.

Second, the closure block should have free read/write access to parameters and local variables in the enclosing method. This can easily be accomplished with syntactic sugar: the variables can be converted into references to holder objects, such as AtomicInteger, that are stored on the heap. Today, shared fields must be final, to indicate that it is safe to share references to the object between the main method and any inner classes.

The hard part about sharing local variables in this way isn't implementing the syntactic sugar, it's about the updating of the information provided to the debugger so that the debugger can undo the syntactic sugar changes, and make variables in the enclosing method, or in the closure block, appear to be local.

Lastly, syntax. I think Groovy has the right syntax here. The important part is for the compiler to actually help out rather than for it to complain from the side-lines. Today's Java compiler has all the type information, but just uses that to build fancy error messages about what you should have typed. It should be using that type information to avoid the necessity of all the extra typing (that is, keyboard entry, not the need for types in the Ruby/Groovy sense of the word).

When a closure is passed to another method, the parameter type determines all the compiler should need to know. Likewise, when a closure is assigned to a variable, the variable defines the closure interface. Thus:

List<Widget> widgets = ... ;
Collections.sort(widgets,
  { w1, w2 -> return w1.getWidgetId().compareTo(w2.getWidgetId()); });

The generic type of the list, Widget, informs the generic type of the Comparator. Thus w1 and w2 are fully typed, as instances of Widget. The return type of the closure is pegged as int (also from the Comparator interface).

Side note: I'd also like to see a lot of other streamlining of Java, such as a Groovy-style implicit return.

In other words, the above could be expanded by the compiler to the following in terms of compilation:

List<Widget> widgets = ... ;
Collections.sort(widgets,
  new Comparator<Widget>()
  {
    public int compare(Widget w1, Widget w2)
    {
       return w1.getWidgetId().compareTo(w2.getWidgetId());
    }
  });

See, the essence is still there, the comparison of the widget Ids. But its now occluded by all that ceremony about interface names, method names, return values, generic types, and so forth.

A caveat: there are edge cases where we'll need to identify the closure interface type. This occurs when a method to be passed a closure is overridden. The compiler should be able to reduce the candidates based on parameter count, checked exceptions thrown inside the closure, and other factors ... but it may be necessary to implement an alternative syntax. For example:

ExecutorService service = ...;
Future<?> = service.submit(Runnable { performExpensiveOperation(); });

submit() is overridden to accept a Callable as well as a Runnable. Callable can return a value. Again, minimal syntax here: the interface name followed by the implementation of the closure method. This compares to the Groovy as keyword.

Now, it's easy to get carried away and put in stuff like Python style co-routines (the yield keyword), or want a syntax to allow the closure to force a return value from the enclosing block or method. <sarcasm> Yep, let's add a few more alien concepts to the language, people love that. </sarcasm>.

These are also not function objects, so you can't easily do magic things such as currying (currying is a way of pre-supplying some of the parameters to a function, such that a new function is created that takes fewer parameters). An "interface" that's curried is a whole new interface and that's OK by me.

I'm more more modest. I don't particularly want to add new features to the Java language, just new syntax for the compiler, to let it do the ugly plumbing. That's kind of my theory for Tapestry's relationship to Java and the Servlet API as well, and it works.

A few side notes:

Annotations could be used to help the compiler out. For example, if a common interface should be a closure, but has multiple methods, an annotation could be used to tell the compiler which method of the interface is applicable to use as a closure; any other methods (in the inner class) would be no-ops or (perhaps guided by additional annotations) failures.

Annotations on fields and parameters (or perhaps on the method) could help the compiler decide how to share visible variables: do we need to use a thread-safe approach (such as something based on AtomicReference) or simple, non-synchronized holder objects? The compiler could do some escape analysis as well to chose the best solution, but in many cases, it's on the coder's shoulders to make the right decision.

From what I can tell, my ideas are closest to the Concise Instance Creation Expressions proposal by Bob Lee, Doug Lea, and Josh Bloch. However, in my opinion, the compiler can do much more in terms of providing type information. I think CICE stumbles in that it allows for creation of classes, not just interfaces, and gets bogged down in defining closure types and parameter types ... again, things that (with the mandate of single-method interfaces), the compiler can do autonomously.

Likewise, CICE requires that variables visible to the inner block be marked "public". This to me is something that the compiler can analyze; it can identify any assignments to variables and promote such variables to stored-on-the-heap status. I don't see the need for final; the compiler has plenty of ability to determine if a parameter or local variable is ever updated within the body of a method (and the body of any inner classes or closures of that method).

My basic concept is: Less is More. Support fewer cases but do so more cleanly, more concisely, and more understandably. Simplify. Let the compiler do more work. Reduce the density and complexity of the Java code. Expose the essence.

Thursday, August 07, 2008

In Portland, even the cafes are Scalable

Portland is a town where, while walking to the gym, you can come across someone sitting at a sidewalk table of a local cafe, leafing through a hard-copy of Programming in Scala. We chatted for a moment (even though I was not quite awake yet). One more thing for me to do ... check out Lift.

And, yes, he actually had a print shop print and bind the PDF (as the book is only available electronically right now).

Wednesday, August 06, 2008

Coming to London, UK

It's looking very likely I'll be in London, UK next month for a bit of on-site training. I'm also speaking at JavaZone 2008. I'd love to optimize the number of flights I make to Europe, so if anyone is on the Continent is hankering for a little Tapestry training, now's a good time to get in touch!

After London, Suzy and I are then going to do a little trip around Europe, taking a couple of days in Paris and Amsterdam.

Latest FireBug understands redirects

Huzzah! One of the problems I've had using FireBug is that it didn't record a component event request long enough for me to see it; all I'd see was the follow-up page render request. But the latest FireBug is showing me the component event and the 302 response, and then the page render request, which gives a much more accurate idea of what is going on and how long it takes.

Thursday, July 31, 2008

Blast from the Past: Tapestry 3.0.5

My current task has put me in an odd mood. A client of Jesse's (Kuhnert) contracted him to make some changes to the Tapestry 3 code base, with me auditting those changes. Jesse has backported some ideas from Tapestry 4 and Tapestry 5: mostly the use of a backported version of java.util.concurrent to improve Tapestry 3 throughput by replacing single-thread synchronized access with concurrent access.

Many of these files haven't really been touched since 2004. It's brought back a lot of memories, such as working on TheServerSide's application.

To my jaundiced eye, the code looks almost primitive; no IoC container, no dependency management, heavy reliance on base classes, a lot of rather naive multithreading.

Overall, I'd say that Tapestry 5 is as far advanced over Tapestry 3 as Tapestry 3 was advanced over Struts. Probably more so. Looking back over this code has given me some perspective.

Thursday, July 24, 2008

IntelliJ: Paste...

screenshot I discovered this one on the flight home. The Paste ... menu item (Shift-Command-V) raises up a window of recently copied selections on the clipboard.

Handy!

Making Java EE Suck Less

Ben Gidley has posted a medium length blog praising Tapestry 5. He took an interesting tack ... describing the requirements of a useful, useable web framework ... in terms of what Tapestry 5 does. I especially liked "Not require a large bespoke framework to be assembled around the framework."

He ends with:

This is the framework that could save J2EE from being a 'dull' and slow corporate tool and eventually fading as other toolsets come and replace it.

This, to me, hearkens back to how Dave Thomas defined Ajax, early on: It's whatever makes the web browser suck less.

To paraphrase, Tapestry is what makes the entire Java EE platform suck less.

Last week, when I was training in Cambridge, UK I saw a bit of this. The client was gearing up to create a full REST interface around their database access. They were starting to evaluate servers and APIs and think about integrations as well as how to represent object hierarchies and updates inside an XML or JSON structured REST query and response ... and how many more Java representations of entity objects would be required.

... but after working on the examples in the Tapestry Workshop they have abandoned the REST approach (until some external system actually needs access). Instead, they are modeling more of their application on the same light principals as the Tapestry Workshop:

  • Inject the Hibernate Session directly into pages, to perform simple queries
  • Use the @CommitAfter annotation to handle update operations
  • Move common queries (used on multiple pages) to a central, injectable service

This way they are able to leverage a single Java object representation of their database entities, as managed by Hibernate. They can use Hibernate features such as lazy loading and update cascading (things that would be prohibitively more difficult if there were a REST layer between the presentation tier and Hibernate) . This is all possible because Tapestry isn't just a web framework, it's also, because of Tapestry IoC, an integration platform. This allows the integration with Hibernate to be "light and tight".

Wednesday, July 09, 2008

Coming to Cambridge, UK

I'll be flying into Cambridge, UK for a week of Tapestry training. I'll be there from Sunday through Thursday night before returning to sunny Portland on Friday. Drop me a line if you are a Tapestry enthusiast in the area ... it's always fun to get together for a drink and talk tech.

Thursday, June 26, 2008

IntelliJ: Flip Equals

Just hit a NullPointerException in some code:

    public boolean isOwner()
    {
        return authManager.getUser().equals(blog.getOwner());
    }

Turns out, sometimes getUser() returns null. I started to retype this, then thought: "Can IntelliJ help me?"

Answer: yes. Because of IntelliJ coolness, I click anywhere in the expression, type option-enter and choose 'Flip .equals()' and it rewrites the code to:

    public boolean isOwner()
    {
        return blog.getOwner().equals(authManager.getUser());
    }