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!

Wednesday, February 18, 2009

Speeding up Tapestry 5.1

I've very excited about some big changes coming in Tapestry 5.1: A significant increase in performance.

This may come as a surprise ... that performance changes are even needed! Tapestry's response time is nearly instantaneous for typical applications. I'm really talking about atypical applications ... applications that push the boundaries on performance and scalability.

This has been driven by a couple of clients who are building large applications on Tapestry: both large numbers of concurrent users, and very complex pages, containing hundreds of components that, due to loops, render thousands of times.

One particular client, Lithium, has been moving from a JSP to a Tapestry 5 solution for their network of community sites. They've been a great help in terms of tracking performance problems and solutions.

Make it Work. Make it Right. Make it Fast.

Tapestry 5 is a complete rewrite of the aging Tapestry code base ... though its hard for me to think of it as "new", since I've been working on it since 2006! Still, the first part of Tapestry 5 was to make it work and Tapestry has lead a lot of innovation here, especially in terms of live class reloading, and the new Inversion of Control container. By the time of the final release, Tapestry was "right" and it was fast enough ... but no effort had been made at any point to make it fast. Tapestry 5.0 was coasting along on good basic design, and the raw speed of Java. With Lithium's input, I've been working to make the actual code live up to the performance goals.

Comparing with JavaServer Pages

Comparing Tapestry application performance can be an uphill battle, since Tapestry's memory usage and processing pattern is completely different than something simple, like a servlet or JavaServer Page (JSP). One hurdle that may never be surmounted is the intermediate representation: JSPs have none; the first bit out output will stream to the client, which is brutallly efficient.

Tapestry, by contrast, actually renders templates and components into a light-weight DOM (Document Object Model). Some post-processing of the DOM then occurs ... some new elements are added related to JavaScript and other features. Then the DOM is rendered to a character stream. Tapestry is always going to require more memory per request and take longer to begin rendering content.

Further, Tapestry pages are full, stateful objects, rather than the singleton objects that JSPs compile down to. This too represents more overhead: to create them in the first place, to manage (and reset) their internal state, and to pool them between requests.

Interestingly, for smaller pages, Tapestry is as fast or faster than JSPs. You can see why if you look at the Jasper JSP implementation: it spends a lot of time managing pools of JSP tag instances, constantly checking them out, configuring them for a single use, then cleaning them up and putting them back into the pool. Tapestry uses a much more coarse caching policy (entire pages, not components within pages) and has more opportunities to optimize the flow of data within the page, between components.

Speeding up Page Rendering

My first pass at improving performance was to optimize Tapestry rendering state machine. Tapestry breaks the rendering of each individual component up into a series of states: Tapestry Rendering States

A component may provide a render phase method for each state. But most components will supply methods for just one or two of these phases. Some phases only make sense for components with templates (not all components do). Tapestry 5.0.18 grinds through each state for each component regardless ... a bit of thrash in the middle of rendering. By analyzing which render phase methods a component actually implements, Tapestry 5.1 is able to optimize away large chunks of the state machine. This results in considerably fewer render operations: less work to accomplish the exact same output. In my sample test application the number of render operations declined 25 - 30%. More importantly, combined with a few spot optimizations, the Tapestry application came down to spitting distance to the JSP application in terms of performance, even though it had vastly more complex behavior.

I actually experimented with a number of other options which I won't detail here. An important part was to measure performance, using JMeter and YourKit. I spent several days analyzing hot spots and trying out different theories.

Speeding up Page Assembly

This was a much more interesting change for real applications and real development. In Tapestry, pages are objects with internal state (not singletons the way servlets and Struts Actions are); this means we need to create multiple instances of pages when two or more requests reference the same page simulateneously.

Instantiating a page, the function of the PageLoader service, is rather involved; it must coordinate the contents of the page's template with the page's components and their templates; it must match up attributes in templates to parameters of components and hook those up as well. One thing I noticed is that there was a lot of duplicated effort when the same component is used multiple times, either on the same page, or on multiple pages.

My approach here was to split page instantiation into two phases: a one time analysis phase to figure out what needs to be done to actually construct a page, and a second repeatable phase to perform the construction. The goal here was to limit the amount of computation necessary when constructing a page (especially the second time).

The end result is a lot of code like this:

   private void expansion(AssemblerContext context)
    {
        final ExpansionToken token = context.next(ExpansionToken.class);

        context.add(new PageAssemblyAction()
        {
            public void execute(PageAssembly pageAssembly)
            {
                ComponentResources resources = pageAssembly.activeElement.peek().getComponentResources();

                RenderCommand command = elementFactory.newExpansionElement(resources, token);

                pageAssembly.addRenderCommand(command);
            }
        });
    }

This is a big win for development in large projects with many large and complex shared components, cutting down on the refresh time after change to a Java component class.

Speeding Up The Client

Improvements to the server side are one thing, but in many cases, a bigger win is optimizing the client side. Web applications are more than just individual HTML pages: all the JavaScript, stylesheets, images and other assets are just as important to a user's perception of performance.

Tapestry 5.1 addresses this in two ways: versioning and compression.

Versioning

In Tapestry 5.0, classpath assets (stored in JARs) are exposed through the Tapestry filter. These assets end up with a URL that includes a version number, and are provided to the client with a far-future expires header. This means that the client web browser will aggresively cache the file. This doesn't help with the first hit to a web site, but makes a big difference when navigating through the site, or making return visit, since most of what the browser needs to get content up on the screen will already be present without an HTTP request.

Tapestry 5.1 extends this: context assets (files stored in the web application context) can now be exposed with an alternate URL that also incudes an application-defined version number, and also get the far-future expires header.

Compression

Tapestry 5.1 also adds content compression: if the client supports it, then rendered pages and static assets that exceed a configurable size can be sent to the client as a GZIP compressed stream. This does help with the first visit to the application.

There's an advantage to letting Tapestry do the compression; Tapestry will cache the compressed bytestream, so that later requests (from other clients) for the same content will get the compressed version without paying the server-side cost to compress the content. The more traditional approach, using a servlet filter, doesn't have the ability to determine what content is dynamic and what is static, so it has to compress and re-compress the same content blindly.

Backwards Compatibility

The crowning achievement of all these changes is the compatibility issue: the only things that changed were internal implementations and some internal interfaces. Existing Tapestry applications can upgrade and immediately benefit from all the changes with at most a recompile.

Part of my goals for Tapestry is to ensure that your application can start small and grow with you. These performance improvements will not be visible for a small in-house application, or a niche application ... but once your application is successful and grows in scope and popularity, the performance you need is already in there by default.

Sunday, February 15, 2009

IDEA 8.1 on Mac

Installed it, launched it. Immediately: Jetty Integration is not compatible and disabled. Strike one. Sure, it has GIT integration and promises to be much faster (which I'm getting desperate for, as I seem to watch the cursor spin quite a bit too much). However, my level of trust dropped through the floor when I saw just how many graphics glitches are all over the screen ... it doesn't inspire confidence that it was tested on a Mac at all. Back to 8.0.1.

Thursday, February 05, 2009

A Better Web Framework: Tapestry's Response

Last month, Ibrahim Levent published a detailed posting about the capabilities of a better web framework on his blog.

I'd thought I'd describe my reaction to it, as well as how Tapestry today, and Tapestry in the future, fits in with this vision.

Certainly there's a lot here, and there's a troubling lack of focus: Mr. Levent is demanding very specific features that span a number of domains. In effect, he's asking for an application server vendor to deliver the One True Stack ... to which I say "good luck with that!"

1- Includes all core application layers (MVC):

Web framework should include data access, business logic, controller and presentation layers internally. As frameworks turn out to be an integration hub, it looses value. Every integration among the core layers introduces new complexity, new glue code, new dependency, and conflicting of intersecting features. If data access layer (Model) uses another framework, presentation layer (View) uses another framework, integrating these frameworks adds a very big challenge even if frameworks support each other. Replacing any framework causes many new problems later. For example, JPA is developed for data access independence but at this time you are limited only the features of JPA. IDE is a major development tool, but at this time we need an “Integrated Development Frameworks” environment within IDE. (Similar with ERPs that brought together enterprise applications under the same umbrella)

Choice is a good thing. I can get up and running quickly using Hibernate; others prefer Cayenne or pure JDBC. I wouldn't want to mandate just one, but Mr. Levent is correct that frameworks must adopt the role of an integration hub, and Tapestry (with it's very dynamic, very late binding Inversion of Control container) really fits that bill!

I also chafe at the mention of the IDE: we've been down that path before (.Net, JSF) ... what we really need are tools that work with a minimal amount of support from the IDE.

2- Avoids heavy-componentization:

In web architecture, desktop-like componentization is heavy and inflexible. Components in desktop applications were very successful. They utilized reusability and used in IDEs. In web applications, component model doesn’t work at the same form. Efforts to convert HTML (+JavaScript) into component model will not be successful as desired. This is because HTML is dynamic (DHTML), works on client and declarative (Declarative Programming). With heavy-components, we loose declarative programming to some degree. We loose “Web Graphics Designer” ability to edit web pages because of moving from design-time to run-time and moving structure information from HTML to programs (With losing HTML Editors functions). Web editor’s favorite structure place is CSS files so what about CSS componentization? Another problem is architectural. Web GUI has 2 runtimes; server and client (Browser). At previous years, web frameworks supported only server-side-only functionality. Then today we see client-side-only approach. I think best solution is balanced mixture of both client-side (JS) and server-side code with component templates (not hard components but light partial HTML+JSP+Servlet codes). I’ll not detail further, there are already many discussions about “Component-based” versus “Action-based” frameworks on the web.

I come down strongly PRO componentization; that's been the focus of Tapestry since day one. Mr. Levent is correct that as more of a page's content is encapsulated inside components, the high level templates (page templates, in Tapestry terms) start to loose their ability to be view stand-alone, outside of the running application. However, I'd rather trade productivity and consistency (and testability) across my application for this one "feature". Here's a better question: how hard would it be to set up your application to run for the designer? Tapestry can allow a designer to run the application and see changes in real time.

3- No new tag markup or page template:

Some web framework requires learning a new markup with no added-value. Your form inputs turn out to be strange tags. Finally, developers don’t understand HTML, JavaScript, CSS because no time left for this learning. Who will fix GUI errors? Frameworks should bring minimum or no new tags (instead we may prefer attributes). HTML tags with simple JSP expressions are enough (KISS). Isolating developers from HTML and JavaScript is not possible.

Tapestry really excels here, as the Tapestry Markup Language templates are just XHTML with a namespace for the Tapestry parts; and those can be limited to just a t:id attribute with all other details in Java code. I don't do it that way ... it's more work for little gain, but a purist can appreciate this.

Even at the opposite extreme, a "heavily" instrumented Tapestry template is still pretty light, with no true Java code (though a few proeprty names and expressions will show up).

4- No XML usage:

Heavy XML usage for configurations makes programs hard to develop, hard to understand, hard to test. One example is “Page Flow” information in XML files. Another example is bean configuration. Yes, pulling this information makes it flexible but who needs it? How many times your page flow changed? How many times did we utilize flexible bean configuration? What about source code readability? I don’t like “Dependency” so “Dependency Injection”. I think dependency is not free that you have to manage its subtleties. Here is my anti-pattern “Dependency Rejection”. XML can be used in other useful places like AJAX messages or data import-export.

Here's where I agree; only Tapestry templates use XML. Tapestry 5 did away with all other XML (except for the ubiquitous web.xml, which is only touched once, when first creating a Tapestry project).

I feel that Mr. Levent is really missing the boat here; properly used Dependency Injection is incredibly important. Dependency Injection is what makes a clunky dinosaur of a language like Java useful, scalable in complexity, testable, and extensible (via late binding). To paraphrase: Dependency Injection is like violence; if it isn't working for you, you aren't using enough of it!

Dependency injection is critical to source code readability because it allows you to easily break your code into small, focused bits that each perform a well-defined function. The IoC container's job is to put all those tiny, testable bits back together into a running application. Tapestry IoC and Guice do this with aplomb.

5- Has its own web GUI page elements:

Rich web elements (say light components) are generally found only in JS or AJAX libraries. Web frameworks should provide rich elements like; Calendar, Dialog, Menu, Popup, Progress Bar, List, Grid, Tab (With sub-levels), Master-Detail Windows, Child Windows, Record Navigator etc. Developers can easily extend these elements. We are still turning around simple features like table sorting, filtering etc. We should step ahead. There is still no desktop-like web grid components to use (I see only in JS libraries) that I mentioned in my previous blog post.

Tapestry does well here (and this requirement seems to contradict item #2). In any case, Tapestry has decent support built in, with lots of great 3rd party support.

Fundamentally, Tapestry is page oriented: the Ajax effects can be well integrated, but not the degree of either a Google Web Toolkit solution, or something entirely hand-tooled (on top of Direct Web Remoting, perhaps).

6- Code generation:

Code Generation makes “Rapid Development” possible. Every part of software should be generated (Generative Programming); CRUD data access classes, business code, controller code, and view pages. Code generation takes development one step ahead of “Drag and Drop” WYSIWYG editors. If web framework facilitates code generation, developers could jump to customization details of application instead of building everything from scratch (MDA).

I disagree here: I don't like code generation unless it happens at runtime. If you look at Tapestry's "scaffolding components" (BeanEditForm and Grid particularly), you can see this ethic: the application is dynamically assembled at runtime. Likewise, all of Tapestry's meta-programming happens by class instrumentation at runtime, without a tedious build stage.

7- Has its own GUI JavaScript library:

Another bleeding integration point is JavaScript libraries. JavaScript libraries are not fully-integratable with web frameworks. They try to solve the problem in client side. What we need is close cooperation with client-side and server-side. Most of web frameworks unfortunately have no or little JavaScript in their presentation layer.

Again we return to integration; Tapestry has a set of libraries built on top of Prototype and Scriptaculous. Many applications also bring in jQuery. They all mix together nicely on the client side.

8- AJAX support (Asynchronous Communication):

AJAX eliminates bothering page-refreshes. Web frameworks should properly blend AJAX functionality into their code architecture. AJAX requires server-side coding. As we make client runtime powerful with AJAX, GUI state management code is duplicated. For example, if we update and fill a combo-box with AJAX call then server-side bean that is bound to this element is not aware of this state change. We have to change server-side state as well. AJAX functionality should be implemented without code duplication (Another interesting trend is AJAX MVC).

... and the nature of component encapsulation is to allow Ajax without the fuss and duplication alluded to here.

9- Portable among application or database servers:

Application and database portability is not easy. In Application Server side, class loader policies change, session management changes, deployment model changes etc. In DBMS side, join clauses change, paging, and sequence generating changes. Web frameworks should provide portable packages for different platforms. On the other hand, some web frameworks have their IDE and Application Server (believe me even DBMS). I think we must leave this job to the famous bright products (IDEs and Application Servers in the market).

This can be a sore point; the servlet API doesn't specify a few important behaviors for Tapestry (that mostly show up only in a cluster). I'm not sure what a "bright product" is though? Any clues?

Tapestry does work on popular servers (Jetty, Tomcat, WebLogic) because it's careful to follow the Servlet API rules, especially with respect to careful use of the HttpSession.

10- Input validation:

Data input validation is a very important feature. If validation doesn’t occur in application, database error occurs. Database errors are not user-friendly. Some validation errors may not be related to database. Programmers need automatic validation according to database object metadata. Custom validations should be added if needed.

I agree, and add further, that validation should occur on the client and then be re-executed on the server. Once you escape from the web tier, the errors get uglier.

11- Bug-free:

Because of bugs in frameworks, all average developers become framework expert spending valuable time to figure out the problem. “Focusing business problems” is lost. I read many open source framework hacks and workarounds in many blogs which is not the task of developer.

As if proprietary code is bug free? This one gets my blood pressure up ... I can't tell you how much time I've spent stepping though WebLogic code, guessing at what's gone wrong (where a bit of source code would have helped). The alternative to Open Source is to still become a framework export, but pay through the nose for the privilege, and deliberately let yourself become helpless, in thrall to your vendor.

12- Handles exceptions user-friendly:

If error or exception occurs, user-friendly messages should be returned. Application programmer has some responsibility for this but web frameworks may ease this task.

Tapestry excels here; I strongly maintain that Tapestry's exception reporting is the best of breed, with a detailed exception report and lots of contextual data ... and the ability to easily turn it all off or otherwise customize what happens when things go wrong.

13- Eliminates double-click, double-submission problems:

Double-click may cause double-submission. Double-submission may cause unexpected errors in application (2 threads tries to do same thing). Web frameworks can eliminate this problem even in client-side without going to server.

This is on the wish list; certainly a little JavaScript to disable the form or submit button goes a long way here! But a better solution intercepts the duplicate submission and that requires some coordination across the server cluster, which is why it isn't in Tapestry yet.

14- Authentication and authorization support:

User login (authentication) is still developed by programmers without knowledge of SQL-Injection attacks. Web application authorization is still missing. Who will be granted for CRUD on which application etc.?(User roles, permissions) I am sure that in every enterprise web application, application authentication and authorization is re-invented.

Is it the role of the application framework to define your security constraints? In a very constrained world, such as content management system, these roles and their application is well defined. I the real world of real applications, it's much harder to pin down. I've worked on many apps that had somewhat intricate permission schemes, and the ability for some users to "jump out" of those schemes.

That being said, Tapestry's modularity means that a standard security library can just be "dropped in". That's what we've been doing at Formos; we use a standard permissioning system, based on page and method level annotations.

15- Security controls for web attacks:

Web frameworks should prevent web security attacks like; Cross-Site Scripting (XSS), SQL Injection, URL Manipulation, HTTP Injection, Session Hijacking etc. Web client data is un-trusted and open to tampering so this is why we can’t quit totally server-side validation for the sake of client-side validation.

Tapestry does a great job on these issue; XSS is virtually impossible, as all output generated by Tapestry is "filtered" unless you specifically ask Tapestry not to. SQL Injection can't occur in a world where you are using Hibernate or another layer to generated prepared statements (this isn't PHP!). URL Manipulation is also somewhat of a non-starter because URLs are linked to components and components are configured on the server side to perform specific functions. It's not like Struts or Rails where you can hack a form submission to turn your admin flag on!

As I mentioned earlier, Tapestry re-performs input validation on the server side.

There is a concern in Tapestry in that Forms store serialized object data on the client side. This is both insecure and inefficient. A future release of Tapestry will address this by either encrypting or signing the data, or by storing the data server-side and just sending a "token" to the client.

16- Reporting integration and barcode support:

Reporting integration is important. We need reporting products/frameworks integration. Would you use your data access objects in your reports? Would your reporting engine use the same JVM runtime? Barcode is not a general requirement but in ERP applications it is very useful (AI/DC Automatic Identification/Data Capture). Barcode printing, barcode reading and matching may be provided by your web framework.(What about RFID?) Would your reporting product support your application barcode?

This is one of those entries in the original blog that simply makes me wonder; Mr. Levent clearly works on a specific category of applications, but I certainly have never written an application that needs to know about barcodes. Barcode reading? What does that even mean in terms of a web framework?

17- Messaging and workflow integration:

Web frameworks may support easy integration with messaging (JMS) and workflow products. Workflow is one of major element of BPM (Business Process Management). In some middleware stacks, this is included (i.e. JBoss Seam jBPM). Web application frameworks may support business events and workflow activities. These events can also be used to feed messaging backbone (ESB).

Even the example here is odd, and reinforces my earlier points: JBoss Seam doesn't have built-in workflow, its the Seam jBPM module that integrates into Seam. So as long as you are good at integration, we're in the success zone. And Tapestry is great at integration.

18- Application to application integration (i.e. Web Services):

In Java, there is external system (EIS, legacy) integration API, which is JCA, but inter-application communication within same JVM is not standardized. Let’s say we have 2 applications and one should use some call other application code. There is no standard for this. Basic solution is just adding other application’s path into its class-path and then using other application objects. We developed an Adapter API for standardization of this. In one-application environment, this is not a problem but if many applications are required to communicate, it gets more important. You can even convert your APIs into web services when necessary (integration with remote or non-Java systems). Web frameworks may provide tools for web services code generation, deployment and monitoring.

Mr. Levent has moved, about here, from some strong goals and guidelines for a web application framework to a kind of development environment wishlist.

19- Admin application for run-time process and user session monitoring:

This is very important in point of user and system management view. What are my users executing at the moment? Which applications take longer to finish? Which users are on-site? Which pages are they surfing? In each session, which objects are they created? What are the URLs that a user requested? Which SQL statement did a user execute?

This is an interesting concept and one that could perhaps be implemented using Tapestry's various meta-programming facilities. I've definitely been thinking long-term about a Dashboard facility.

20- System resource management:

If your application runs big queries that require a lot of system resource (CPU, RAM, DISK I/O), we are faced the reality that resources are limited. If applications don’t restrict user processes, then system will consume its all resources and will not respond to even small processes. For the sake of system availability some user may be rejected by system. Web framework may have such limitation API’s.

This concept is a tricky thing to bootstrap; if your machine is truly strapped, it may have trouble just getting to the point where it can determine how strapped for resources it really is! I know of no general purpose web frameworks that have this kind of feature.

21- Cluster support:

When server load is high and performance is a major concern, load-balancing is required. Application server clustering will not suffice, web frameworks must support cluster architecture. One simple example is framework’s id generators. They will collide in clustered Application Server environment.

I'm not sure what framework id's he's getting at here. Clustering a servlet application is generally quite sufficient, and clustering Tapestry is even easier, as it is very careful about what data is stored into the session. Tapestry is also good at keeping mutable objects stored in the session "fresh" when they are updated, but mostly it stores many small immutable objects where other frameworks store large mutable objects.

In terms of IDs; session ids can have, for example, DCE ids that can be cheaply generated anywhere with a guarantee of uniqueness (they just tend to be quite large). Database ids are generated, efficiently, by the shared database.

22- Multi-database, multi-company, multi-window, multi-session support:

Application user may need to work on multiple database instances. One user may have to work with multiple companies. User may want to use multiple GUI windows. Web framework should handle or prevent state corruption among windows. User may need to work on the system with many sessions.

To me, this indicates a single application deployed, and perhaps "skinned", multiple times ... or represents a single application that is capable of connecting to multiple databases at the same time.

Multiple windows can be something of a challenge; a single server-side session is shared across windows. Tapestry can encode state into URLs, which is handy but ultimately limiting. I think in the future Tapestry has the best chance of dealing with this cleanly because there's the gulf between persistent page fields and the session, which allows Tapestry to arbitrate ... literally, store different values in the session for different windows, but the same user. Not something implemented today, but quite possible.

23- Internationalization:

If there are global users, then i18n support is important. One key aspect here is Application Server and DBMS should also support your localization.

Tapestry has greate L10N support; applications can have localized message catalogs as can individual pages and components. Templates and assets (images, stylesheets, etc.) can be localized as well. Tapestry uses your browser's reported locale, but this can be overridden programatically. In Tapestry 5.0, you'll receive a cookie with your "true" locale. In 5.1, the "true" locale will appear in the URL (which is more search engine compatible).

24- SSL support:

If web application is wanted to be secure in insecure networks, SSL-support is important. SSL deployment in HTTP Server would not be enough. Even if SSL is not used, frameworks must encrypt sensitive data between client and server, like user passwords.

You may mark Tapestry pages as secure, using an annotation. Tapestry will automatically use HTTPS when building links to secure pages, and will reject any attempt to access a secure page using insecure HTTP.

25- Document attachment:

In every enterprise application, document attachment is important. Users may want images, Excel documents attached to their application records. Every programmer first search for an upload utility then tries to understand server document folders. Instead, built-in functionality saves valuable time.

Back to a wish list and not a real framework goal.

26- Mobile device support (i.e. Internet Explorer Mobile):

If we want to plan mobile access to our applications, how can we do this with web technologies? Many mobile devices have built-in web browsers and we may run our applications in these browsers. Web framework mobile support would be very beneficial at such cases. Otherwise, you should explore mobile web browser limitations by yourself.

I have long maintained that an application for a mobile device and an application for a desktop browser are not the same application. Creating a useful version of an application for a size and bandwidth limited client is more than just choosing new fonts and omitting a few options ... to do it succesfully is a completely different flow, and therefore, a different application (or at least, a seperate corner of the application).

You often hear about a magic XST transformation (Coccoon, anybody?) where a single service layer could be vended out in multiple formats. But I've never seen one in practice that worked, scaled and was maintainable, never mind acceptable to end users.

27- Portal features:

Partial web components should be supported to use in Portals or external sites. In portal terminology, its name is portlet. There are many synonyms; Widget, Mashup etc.

Tapestry 4.0 was a great platform for Portlet development, that will return in Tapestry 5.2.

28- Scheduling:

Application task may be batched and scheduled. After task completion, users may see results.

I have long thought of a layer for Tapestry to leverage Quartz for this purpose. Again, Tapestry's current goal of being a comprehensive user interface layer (rather than a total vertical application framework, which is what Mr. Levent is looking for) has made some of these non-goals for Tapestry.

29- Keyboard hot-keys:

Users, especially old TUI (Text UI) users want keyboard hot-keys. Buttons, command icons should be bound to hot-keys. Web frameworks elements can support this instead of developing in every application.

This is largely a function of HTML and JavaScript, things well encapsulated by Tapestry.

30- Alerts between users:

Users may want to send messages to each other or system admin may want to send messages to users like notifying a shutdown or an application restart. This feature will be very handy.

Again, a wishlist item that could easily be implemented for a specific application.

Summary

Mr. Levent has brought up a number of interesting concepts, and a number of real oddities, in his quest for the "improved web framework" (in fact, he's looking for a vertical application framework with some very specific niche capabilities).

I can't say that Tapestry fits his bill perfectly ... but I can say that Tapestry would be my first choice to anchor the stack that would meet his needs. The most important features of his "better web framework" are already present in Tapestry today.

Clojure at TheServerSide Symposium

I'll be presenting a lightning fast overview of Clojure at this year's TheServerSide symposium. I'm speaking at 3:50pm on Wednesday, March 18th.

Monday, February 02, 2009

Obama Icon Me

Obama-Iconed Howard

Tapestry 5 Refcard

DZone has just published a Tapestry 5 Refcard, authored by yours truly. It was a great challenge fitting even a representative sample of all the great concepts and features of Tapestry 5 into less than six pages. Download it and tell me what you think!

Sunday, February 01, 2009

Did I mention that RealPlayer is evil?

So for the last few months, my wife has been having constant trouble with her MacBook, specifically, wireless connectivity. Our wireless network has always worked well for me, but hers was constantly un-responsive. Intenet connections would work for a minute or two, then hang. It was constantly trying to re-connect to the router. DNS names would not look up properly. Pings would never return. I tried upgrading from a DLink router to an Apple Internet Extreme ... same problem.

Was it a hardware issue? Suzy took her MacBook to the Apple Store. They noticed some oddities and tried re-installing the operating system (without wiping the drive).

Was it a Comcast issue? I mean, they are rat bastards and totally clueless ... and as it turns out, our cable connection was wired backwards and it was lucky we got any signal at all. But that never affected me, and even once Comcast came out and rewired it, the change didn't seem to make a difference for Suzy's MacBook.

Meanwhile, Samantha's Dell worked great on the wireless. So did Merlyn's MacBook Pro. And my MacBook Pro. And the Wii. It got so that every time Suzy would bring it up, I'd start getting angry because I couldn't fix it! And fixing things is what I do!

Last night, staring in frustration at her MacBook, I did something I should have done a ways back: launch Console.app and see if there was anything interesting in there. And there was ... endless error messages from "RealPlayer Downloader" (which was running in the dock) with messages specifically mentioning the network interfaces.

It wasn't clear what it was doing there. Calling home? Opening up ports? Suzy had installed RealPlayer to watch a work-related video for a client months past and had forgotten about it. Guess what? I removed the application (using AppZapper) and Suzy's internet connection has been smooth sailing ever since.

Moral of the story: Run, Run, Run away from RealPlayer!

Saturday, January 31, 2009

Using Maven to create a new Tapestry 5.1 project

I've been digging deep into the (revised) Maven archetype plugin: the tool used to generate new projects from a template. The existing quickstart archetype had some glaring omissions, partially related to limitations of the old plugin. The new plugin makes many more things possible.

In the past, you needed to specify the new project's groupId, artifactId and other data on the command line. This process was so tedious and error prone that I advised wrapping it up in a Ruby script.

Now it's a bit easier, as Maven will ask you for any necessary properties:

$ mvn archetype:generate -DarchetypeCatalog=http://tapestry.formos.com/maven-snapshot-repository
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:generate] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Preparing archetype:generate
[INFO] No goals needed for project - skipping
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: http://tapestry.formos.com/maven-snapshot-repository -> quickstart (Tapestry 5.1.0.0-SNAPSHOT Quickstart Project)
Choose a number:  (1): 1
[INFO] snapshot org.apache.tapestry:quickstart:5.1.0.0-SNAPSHOT: checking for updates from quickstart-repo
Define value for groupId: : com.formos 
Define value for artifactId: : demo1
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  com.formos: : com.formos.demo1.web
Confirm properties configuration:
groupId: com.formos
artifactId: demo1
version: 1.0-SNAPSHOT
package: com.formos.demo1.web
 Y: : 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24 seconds
[INFO] Finished at: Sat Jan 31 13:12:31 PST 2009
[INFO] Final Memory: 8M/14M
[INFO] ------------------------------------------------------------------------
~/work
$ 

The key part is the -DarchetypeCatalog=http://tapestry.formos.com/maven-snapshot-repository, which points Maven at Tapestry's own nightly snapshot repository. It picks up a catalog file from there which points to the quickstart archetype stored inside the snapshot repository. No muss, no fuss.

Of course, the first time you run this, Maven has to download about 34MB of modules!

In any case, it works nicely. I put some extra effort in so that the archetype description clearly identifies the exact version of Tapestry.

The new quickstart archetype is much improved; it now includes a Layout component that wraps the page content using a free CSS layout, "concrete" (which has a Creative Commons license). In other words, your application will look good from day 1, though (of course) I'd expect you to throw away the layout subsequently. Anyway, this will answer some questions such as: "how do I use a Layout component?" and "how do I access a message catalog?"

I can see replacing the "concrete" layout with something more general, less blog-oriented (but that's beyond my CSS/HTML design skills!).

All of this is all usable right now. Have fun!

Friday, January 30, 2009

A tip for TestNG: Keep the Constructor Simple

TestNG does not do a great job of reporting exceptions when instantiating your test classes. Keep the content simple. If you are going to do anything that can throw an exception, defer it by creating a setup method annotated with @BeforeClass.

In my situation, I had an exception, and add my normal TestNG verbosity level, 2, there was no obvious output explaining why my tests didn't run, just a message about "unable to instantiate" my test case class. Upping the verbosity to 10 helped me identify the problem ... at which point I moved a bunch of setup logic out of my constructor and into a setup configuration method.

Sunday, January 18, 2009

Using a Samsung ML-1450 with Airport Extreme

I just upgraded my router to an Airport Extreme, partially just to allow Suzy to use the printer even when I don't have my laptop hooked up. However, the printer in question is a Samsung ML-1450 which doesn't have great compatibility under Mac OS X.

After a little searching I have it working quite nicely. An Apple Support Thread plus some poorly written release notes almost tell the whole story.

Remove your printer (from the Printer Preferences) if it currently exists.

Delete the folder /Library/Printers/SAMSUNG. That's all the old set of Samsung drivers.

Download the updated Samsung Drivers.

Run the install; it will do a full restart.

Open Printer Preferences and add the printer back in. It should auto-discover the new drivers (which will display as "Samsung SPL 2.5").

Tuesday, January 13, 2009

The Tapestry Roadshow at The Great Indian Developer Summit (April 22 - 25, 2009)

I'll be presenting three sessions at the Great Indian Developer Summit in Bangalore, India this coming April. The sessions are on Clojure, Tapestry IoC, and a long tutorial session on Tapestry 5.

I'm not sure what days I'm speaking yet.

Monday, January 12, 2009

Comparing Prototype and jQuery

Glenn Vanderburg has written a careful treatise on why he prefers Prototype to jQuery. The thing about Glenn is that he doesn't write (or talk, or think) in terms of "feelings" or "impressions"; his thoughts are clear, and organized, and fact based, and detailed.

I looked at jQuery about two years ago; to me it seemed a little iffy, a little skewed towards web developers doing bespoke web page development, rather than the dynamic generation Tapestry is all about. That impression may or may not be accurate, but Glenn has nailed some significant differences in the design and implementation of the two frameworks, which is why Prototype comes out on top.

Wednesday, January 07, 2009

Upcoming features for Tapestry 5.1

I've been working on some new features for Tapestry 5.1. Importantly, I've been trying to get my head around Spring Web Flow and how that would integrate with Tapestry without sacrificing the flavor of either framework ... fortunately, Keith Donald (SWF Head Honcho) has been helping out.

I'm also very interested in performance ... or really, throughput. I made a number of changes in December that dramatically improved Tapestry performance for very large and complex pages (with thousands of component renders). It can be an uphill battle, given that Tapestry creates an in-memory DOM of the page and streams that out at the very end (unlike a JSP that can spit out bits and pieces of content immediately at the start of its render). There's certain overhead in creating all those DOM objects and then navigating them while rendering. So where we take away on one side, we need to add back on another ...

The goal is to minimize the amount of traffic over the wire; specifically, the amount of static content: assets such as images and stylesheets that don't change during the life of the application.

Tapestry 5.1 now has built-in GZIP compression of responses, both for dynamic page renders (traditional and Ajax) and for those static assets.

I'm also automatically building versioned URLs for context assets (not just classpath assets stored inside JARs); they'll get the benefit of automatic compression and far-future expires headers too.

This falls into my vision for Tapestry in particular, and frameworks in general: they should help you employ best practices, even if you don't even know such practices exist. Just using Tapestry becomes a best practice all by itself*.

Another way to embrace this view is to see Tapestry as the embodiment of the combined experiences of the Tapestry developers and community over the last many years. Good lessions, hard won, realized in code.

*At this point, steam should be coming out Ted's ears!

Monday, January 05, 2009

Rapid Turnaround in Tapestry 5

Borut Bolčina has a screencast demonstrating live class reloading in Tapestry 5. And he's doing it with Jetty, in Windows ... so he includes the fixes to the environment to make this work (surprisingly, the problem is with CSS files, not reloaded Java classes!)

It's easy to dismiss how important live class reloading is; you tend not to appreciate how volatile the web tier is, or how often you are twiddling your thumbs (or reading blogs ... Hey! You get back to work!) waiting for redeployments and reloads. Live class reloading lets you explore your solution space a little bit at a time, without paying a horrible cost.

That's exactly what I do in live demos, or when teaching labs. I put a little bit into the template, a placeholder for what's coming. And I refresh the browser. Then I add some properties or event handler methods to the code, and components to the template. And refresh the browser. Then I start checking error cases and edge cases, making changes, refreshing the browser.

I often get runtime exceptions; usually there's a snippet of text (such as the properly spelled property name) in the exception page that I can paste into my code. Refresh the browser, and continue. Working incrementally this way is faster and easier, with less waste and frustration.

You get used to this style very quickly and then the thought of going back to an environment that doesn't support it (Struts, Tapestry 4, even Flex) is disheartening.

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.