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!

Monday, February 28, 2005

HiveMind: JMX and AOP Aliance Support

Achim Hugen is preparing to contribute JMX Support for HiveMind. Looks very much like I had originally envisioned. As with Tapestry, I'm looking forward to a time where I'm not the one doing the most interesting work.

Additionally, James Carman has spun up and started checking in code; he's added support for AOP Alliance style interceptors. He's also written an article on HiveMind for TheServerSide.com which should be up soon.

Simplify, Simplify

Getting more and more interesting things done in the Tapestry 3.1 code base is getting easier and easier.

One complaint I've gotten about Tapestry 3.0 is that, even if you use all implicit style components on a page, you still need to provide an XML page specification, just to specify the page class name.

I just checked in some changes to how page class names are determined; there's now an chain of command, that starts with the page specification itself, and ends with a default page class name.

In the middle is a new workhorse: NamespaceClassSearchPageClassProvider. This short little class gets a property from the namespace (the containing application or library specification). The property is a list of package names to search, looking for the page's class. So you might add <meta key="org.apache.tapestry.page-class-packages" value="org.mycompany.myproject"/> to the application specification, and just put your page classes into the org.mycompany.myproject package. And you're done.

It's a command separated list of package names, so you can have a couple of packages to search.

I think, gradually, the XML side of Tapestry will wither. I already have plans to support JDK 1.5 annotations in Tapestry to control a lot of the things you would currently do using XML ... and for some things, the XML is still the best approach.

I suspect that in the Tapestry 4.0 time frame, we'll re-organize the artifact triumvirate (specification, class, template) such that we start with the page or component class, then locate the optional specification and template. In 3.1 (and earlier) we start with the page name, find the spec, determine the page class from the spec.

Thursday, February 24, 2005

Crawl, Walk, Run

I've started my first few steps towards adding JSR-168 Portlet support to Tapestry.

I've got a pretty good handle on what it's going to look like, but I have a lot of unanswered questions, so I'm building a few simple, exploratory examples.

I've started with Jetspeed 2.0-M1 and hit all the standard problems. Like having to use Maven, and tripping over a JDK 1.4 bug.

I'm also getting a bit of a taste of my own medicine ... getting started with Jetspeed is painful for lack of documentation, a common concern for Tapestry. I'm sure it makes perfect sense from the inside, but as an outsider, I'm a bit daunted by some big gaps in the documentation. You have to learn a lot by experimentation.

I'm beginning to get a picture of how everything will fit together. HiveMind will be key here. There'll be an abstraction layer of the servlet/portlet APIs. In a few critical places, there'll be a lot of indirection to allow servlet implementations vs. portlet implemenations ... I expect to use the ServicePropertyFactory quite a bit in these situations. I haven't seen anything that looks like a show stopper yet. Time to roll up my sleeves and have some fun!

Tuesday, February 22, 2005

Defensive Patents

I see stories like this and get pretty angry. The largest companies are doing themselves, and the American programmer, a disservice with this blind attention to software patents. Obviously, as an open-source programmer, software patents make no sense to me. Code is protected by copyrights. If you have competitors, beat them on implementation, or service. Playing the big-bad patent card is an admission that you can't compete otherwise.

I'd like to think that companies are pursuing defensive software patents ... but if big software companies (including IBM, Microsoft and Sun) really wanted to do something good for the American IT industry, they would work to end software patents entirely. These companies have big, big political muscle (because that's what money is), and if they wanted to accomplish such a goal, they could.

The fact that they don't do more than maintain the status quo (and funnel bushels of money to the lawyers) says that they are afraid of competing fairly. Meanwhile, which each frivolous, obvious patent the ability of the entire software world to create something without being paranoid about a cease-and-desist arriving in the mail arbitrarily erodes ... and with it, a fractional amount of our competitiveness, individually and as a country.

Monday, February 21, 2005

Updated PresentationExamples.zip

Once again, I've updated my presentations. Only two big changes: the libraries have been updated to Tapestry 3.0.2, and I've replaced the lame "Developer Address Book" page (used to demonstrate the Table component), with a page that reads your iTunes music library. I supposed that's a problem if you don't have an iPod, and the correct solution is to go get one.

Follow the downloads link (from the list of links on the right side of the page) to find PresentationExamples.zip.

Saturday, February 19, 2005

Life is a blur

Just did a whirlwind trip to Utah to speak at the Utah Java User's Group. It was a fun trip, and a great crowd. Jamis Buck warmed them up with a great demo on Rails.

This was a good change from my last couple of Tapestry sessions; a much more dynamic crowd (numbering well over a hundred), asking useful and challenging questions. I got some good feedback that I hope to incorporate into my upcoming TSSS and NFJS presentations. Basically, rather than explain some of the details of validation poorly, I'll explain it briefly and omit a lot of details.

Also, I had some better ideas for the table component demonstration. Rather than "address book", I think I'll do, "iTunes inventory". I can then highlight rows that have my highest rated songs.

I was busy on the flight out and back. I've added to HiveMind something new and desired ... the ability to use pure POJOs as services ... POJOs without interfaces. This took a little doing ... the internals of HiveMind are specifically geared around interfaces (not just for interceptors, but for all the proxies that control just-in-time creation).

What I did is generate a synthetic interface for services that are really beans. The interface contains all the public methods that aren't inherited from java.lang.Object. The synthetic interface also extends any interfaces implemented by the bean. This seems to work quite well; I started up a vote to release HiveMind 1.1-alpha-2 to get this out into the field and see what works and what doesn't.

It's a little odd that the interface attribute of the <service-point> can now be a ordinary class (not an interface). This does create a split internally ... there's the declared interface (which may be a class) and the service interface (which either matches the declared interface, or is synthetic).

I also added a very simple, light-weight initialization to the instance: object provider. You can now specify, in addition the class name, a list of properties and values. The values are converted from strings to the property's type and assigned. It's using pretty much the same techniques as the smart translator. In fact, just writing this blog entry, I realized a bunch of other places this can and should be used.

Finally, I did some work on Tapestry; adding unit tests for components. This is long overdue; we need tests before we can start doing some of the big improvements with respect to form handling and input validation that are coming down the pike. I started small, with Insert and Conditional. Even so, I found some gaps in the coverage that aren't covered by the integration tests. Eventually, I hope to get rid of all the existing integration tests and replace it with just unit tests and handful of "sanity check" integration tests.

Tuesday, February 15, 2005

Tapestry DHTML Menus

Glen Stampoultzis has put together a nifty library for creating client-side JavaScript menus. It's called Krysalis Menu.

I like the way you construct the menu using hierarchy of components, using component enclosure (i.e., nesting of tags within the template) to define menu structure. It's how I thought about doing the same thing.

The library is on an ASL-derived license, and the documentation indicates a desire to move into Tapestry proper at some point. I suspect they'd be welcome!

Friday, February 11, 2005

Tapestry and Ruby On Rails at Utah JUG

Next week (Feb 17 2005), I'll be presenting a bit on Tapestry to the Utah Java User's Group. Jamis Buck will also be there, discussing Ruby on Rails. Jamis has done some very interesting work in the Ruby space, some of it based on HiveMind, and it will be interesting to meet him.

HiveMind and Pico Compared on java.net

Just read IoC Container Face-Off by Ken Ramirez, an article introducing the concepts of Inversion of Control and two implementations: HiveMind and PicoContainer. Curiously, Spring is not even mentioned!

I had a few minor issues with the article; I don't think it made the case very well for IoC. It confused "dependency injection" with IoC (dependency injection is an important component of IoC, but inversion of control also includes life cycle issues well beyond connecting collaborating services).

Where the article was weakest was on that exact topic: collaborating services. If you have just a main() method call a single service, IoC looks like a waste of effort. It's when you build complex systems with many moving parts ... but those individual parts are simple and testable, that IoC approach really shines.

The code to load a descriptor is too complicated; just put the hivemodule.xml on the classpath and use RegistryBuilder.constructDefaultRegistry().

Some of the terminology is about six months out of date; "singleton" and "deferred" became "primitive" and "singleton". Ken also garbled what the LoggingInterceptor does (it logs method entry and exit, not just exceptions) and missed most of the strengths of HiveMind:

  • HiveMind supports a large number of modules, which is why the extended naming is necessary.
  • It is very common to have many services implementing the same interface.
  • Not even a mention of configurations, HiveMind's most obvious differentiator from other containers.
  • No mention of factories ... many of the most interesting services in HiveMind are created dynamically at runtime.

For a supposed "face off", there was no direct comparison of the two frameworks. Here, I'll do it: PicoContainer is lighter weight, has an aversion to XML, doesn't mandate the use of interfaces, and uses constructor injection exclusively. HiveMind is driven by XML, is much heavier weight (what with support for many modules, interceptors, configurations, etc.), enforces the use of interfaces, and allows setter and constructor injection.

HiveMind 1.1 and multiple locales

In HiveMind 1.0, you set the locale once, when you build the Registry and you're stuck with it. That's pretty limiting, especially for a web application. In a web application, such as a Tapestry application, each request (which is to say, each thread) may be using a different locale.

Using a fixed locale limits the usefulness of HiveMind. It means that the business layer can't generate localized error and status messages and pass them back up to the presentation layer. Instead, you fall into the same old trap of exceptions and return codes so that the presentation layer can generate those messages (in the proper locale). That's counter to whole philosophy of HiveMind: colocate everything, so that they can collaborate richly and limit the amount of ugly plumbing and klude code you have to write.

Well, I just checked in changes to HiveMind 1.1 that allow the locale to be changed at any time. The Registry's locale is simply the default locale. The key aspect of this is the Messages object injected into your services ... this was how services gained access to localized messages. The new implementation is smart enough to combine the request message key with the thread's current locale when obtaining a message.

For a web application, this means that, as soon as the client's locale is determined, the ThreadLocale service should be notified. Any messages generated by any services will then be in the proper locale. Tapestry 3.1 has a particular place for doing this ... in fact, the engine's getLocale() and setLocale() methods will be changed to simply invoke getLocale() and setLocale() on the ThreadLocale service.

Thursday, February 10, 2005

Streamlining HiveMind Module Deployment Descriptors

We often complain about the verbosity of J2EE XML descriptors. The HiveMind descriptors are better, but there's still a lot of typing.

One thing that struck me is that there's a lot of needless duplication of package names. Often the package name matches the module id ... and yet, you'll retype the package name for each service interface and each class.

So ... what if HiveMind just assumed that simple interface and class names were in a specific package? Also, what if the module id and the package name aren't the same?

What I've done it to add a package attribute to <module> (which defaults to the module id). I modified a lot of code to make use of the package name when a reference to an object type shows up (and, in fact, improved the code by adding a resolveType() method to the Module interface). Literally, if the class name as is doesn't exist, then a second try (prefixing the class name with the package name) is made.

This simplifies the examples module descriptor:

<?xml version="1.0"?>
<module id="examples" version="1.0.0" package="org.apache.hivemind.examples">
    <service-point id="Adder" interface="Adder">
        <create-instance class="impl.AdderImpl"/>
        <interceptor service-id="hivemind.LoggingInterceptor"/>
    </service-point>
 . . .

What's nice is that you can use "relative" class name, such as impl.AdderImpl, which is really org.apache.hivemind.examples.impl.AdderImpl. See what I mean about less typing?

Perhaps we'll go further in the future ... for instance, maybe <service-point> should assume that the interface matches the service point id?

There will be cases where you really want to support many different packages in the same JAR. You will then either type fully qualified class names, or split your JAR into multiple logical modules using <sub-module>.

Sunday, February 06, 2005

Global message catalog for Tapestry

One of the most frequent requests for Tapestry is support for global message catalogs. In Tapestry, each page and each component can have its own message catalog and that's good ... but this can also foster some duplication between templates. This is now fixed as TAPESTRY-242.

I think of all the changes I've made in Tapestry 3.1 so far, this may be the first one that came as a request from the community, rather than a change I've made from my personal vision for 3.1. Now that the basic infrastructure of 3.1 is in place (but subject to some revision and refactoring) I believe we'll be seeing these kind of changes rolling out quite frequently.

Saturday, February 05, 2005

Tapestry, JSF and FUD

Rick Hightower's recent article, "JSF for nonbelievers: Clearing the FUD about JSF" has been prompting some discussion on TheServerSide. Now, one of the good things about JSF is the way it validates (in some people's eyes) the component based approach.

It's natural for me to be politely antagonistic towards JSF. JSF has an event model and a component model and I find it bothersome for them to claim that JSF has the event model and the component model. Their choices are just their choices.

The one thing to keep in mind during any discussion of these technologies is that, from a high enough level, they are all identical. HTTP requests flow in, HTML (or other) responses flow out. This is the same for CGI, Perl, ASP.NET, etc. The differences are not in what's possible, but in how hard it is to create and how easy it is to maintain. While I'm envious about certain aspects of JSF (particularly enforced acceptance from above, lack of enforced inheritance, and having design-time tool support built in), my basic stance, unchanged from reading this article, is that real-world Tapestry applications are faster to build, easier to maintain and extend, and more robust in deployment.

JSF and JSP technology

I find this aspect of JSF quite interesting; the choice to "support" JSP creates some strange behaviors that would be unacceptable if it wasn't coming out of Sun and the JCP. In Tapestry, when any aspect of a page or component changes (and you've disabled caching for development purposes), then templates and the component tree stay synchronized ... because they are the same thing.

Figure 1. Example application from an MVC point of view

The Tapestry equivalent of this is, I think, a bit simpler. There's a lot less to map because the Tapestry HTML template is tied to the component tree. Tapestry component and page classes are the equivalent of the JSF backing bean (though it is quite practical to have both properties and listener methods in a different object entirely).

faces-config.xml

One of the things that bothers me about JSF is the fact that managed beans are entirely global. Tapestry allows each page or component to manage its own set of beans, and has additional lifecycles (the equivalent of bean scopes).

For me, the navigation rules are problematic. Because of how the view (templates) and component model are so loosely tied together, it becomes necessary to add another level of abstraction, the outcome/view-id mapping. If you corner a JSF developer and challenge them about the need for this, the response will likely be something about supporting different views (i.e., WML, Flash, etc.) from the same set of controls.

And that's a problem for me. I've repeatedly asked my audiences at various events who has done this, or needed to do this ... support multiple view types with a single application. I've yet to find anyone who needs to do this or finds it practical. This was the promise of XML/HTTP pipelines before JSF. The problem is, this is a non-starter of an issue ... there's no such thing as a single application that support multiple views.

What there are is multiple similar applications that share common back end processing and data. When you try and have one application shoe horn into multiple view technologies, you are asking for a disaster ... I call it "coding inside a case statement". You'll be adding, removing, moving and morphing so much stuff that you end up with something that is brittle in all views.

My response when this kind of feature is asked for in Tapestry is to challenge the questioner about what the different forms of the application will look like ... and to point out that they are different applications that should share a common back end. In Tapestry terms, they may even share page classes and components ... but the templates should be unique to each view. The ultimate goal is to keep the templates clean because any other approach is incompatible with enterprise application development realities. Good for demos, bad for real life.

By contrast, Tapestry pages concentrate on just on type of markup at a time, typically HTML (but just as easily XHTML, XML or WML). This allows the kind of fine-grained control that demanding page designers expect.

Case in point is Tapestry's support for informal parameters. Tapestry components may optionally support additional, arbitrary parameters beyond the explicit, named (and typed) set of parameters. The majority do. The parameters (which may be literal values, evaluated expressions, or localized message -- just like formal parameters) are passed through as additional attributes on the element. This allows you to specify any and all HTML attributes; particularly useful for dealing with CSS styles or JavaScript event handlers. Having a tight binding between the template engine and the component model makes this easy ... using JSPs (where each JSP tag attribute must be formally defined) would make this impossible.

A final note; I'm dissapointed that the name of the calculator controller bean, CalcBean, is a "simple" name, not a qualified name. From the later examples, it appears that such simple names are mandated by the expression language. On large projects, this will simply encourage naming conflicts. My experience, as far back as 1997, is that naming conflicts can wreak havoc on large team projects ... and Tapestry is specifically designed so that naming conflicts simply don't occur.

Gluing the model and the view

Tapestry allows you to have as much or as little flexibility as you want in terms of where properties and operations are located. My preference is to co-locate properties and operations since that is the very definition of object-oriented programming.

In Rick's example, he demonstrates how the pure business logic can be externalized, by having a Calculator class that is separate from the CalculatorController that is referenced inside JSF.

Certainly the same thing is possible in Tapestry. The equalivalent Tapestry page class would be:

public abstract class CalculatorPage extends BasePage
{
  public abstract int getFirstNumber();
  public abstract int getSecondNumber();

  private Calculator _calculator = new  Calculator();

  // Listener method for adding.

  public void add(IRequestCycle cycle)
  {
    int result = _calculator.add(getFirstNumber(), getSecondNumber());

    showResult(result);
  }


  public void multiply(IRequestCycle cycle)
  {
    int result = _calculator.multiply(getFirstNumber(), getSecondNumber());

    showResult(result);
  }

  private void showResult(int result)
  {
    IRequestCycle cycle = getRequestCycle();
    ShowResult page = (ShowResult) cycle.getPage("ShowResult");

    page.setFirstNumber(getFirstNumber());
    page.setSecondNumber(getSecondNumber());
    page.setResult(result);

    cycle.activate(page);
  }
}

This demonstrates some advantages of JSF and some advantages of Tapestry. JSF doesn't require you to extend from a base class, which is a good thing (and a huge, backwards incompatible change for Tapestry, which is why it hasn't been implemented yet).

The abstract class, with abstract accessor, causes a bit of confusion; it is because Tapestry injects code into your class by subclassing it and filling in the abstract methods. In this way, it can efficiently manage the properties of your page ... storing persistent properties in the HttpSession as they change, and properly resetting the values for transient and persistent properties at the end of each request. This reflects the Efficiency principle of Tapestry ... the expensive to construct page objects are pooled between requests and shared by different sessions from one request to then next; the enhanced subclass fulfills the contract needed by Tapestry to safely share the page objects in this way.

The method invocation is the same; Tapestry 3.0 requires that such listener methods take a single IRequestCycle parameter (Tapestry 3.1 will likely improve on this). The difference is how that method is reference; in Tapestry, the Submit component can reference the method as:

  <input jwcid="@Submit" listener="ognl:listeners.add" value="Add"/>

In Tapestry syntax; this means "an anonymous instance of the Submit component, with its listener parameter bound to the add method of the page class".

An advantage for Tapestry, I think, is the way the two pages (the firsts containing the form, the second displaying the result) communicate ... in proper, type-safe Java code. The first page obtains an instance of the "ShowResult", casts it down from IPage to its actual type, and invokes methods on it to inform it of what it needs to operate. This is a clean interface between the two pages ... the first page concerned with collecting the two values and calculating a result, the second with displaying the two values and the result. The are many variations on this "Tapestry bucket brigade" that will appear more or less efficient. For example, we could have a data object containing the first and second numbers and the result, and pass that single object between the pages.

Again, this is more of an object-oriented approach. The pages of the application are objects, at least for Tapestry. The proper way for them to communicate is via methods and properties ... rather than the engineered coincidence that they both reference the same, arbitrarily named bean stored as an HttpServletRequest attribute. JSF is much more beholden to the Servlet APIs than Tapestry ... which does much more to hide the APIs and the mechanisms they represent.

A final note on this subject; in Rick's example, the CalculatorController was given session scope; a somewhat odd choice. I suspect the reasoning for this will become evident as the examples expand in later chapters. In any case, the Tapestry example will be request scope and the application will itself be stateless (no HttpSession).

JSP vs. HTML template

In this simple example, JSF has a slight advantage, in that JSF input fields include validation by default. Even so, the JSPs here would not preview correctly inside any HTML editor ... it would require a JSF-aware tool to render a preview properly. By contrast, Tapestry HTML templates are ordinary HTML elements and attributes (with an occasional extra, non-HTML attribute thrown in) and will preview properly in any WYSIWYG HTML editor.

In Tapestry, form input validation is an add-on, requiring a different component, TextField. However, Tapestry's form input validation is quite powerful, with tremendous control over look and feel, error message formatting and reporting, and support for complex forms containing loops and conditionals.

Conclusions

Rick has demonstrated that your can assemble simple JSF applications without tool support. JSF does have an improved model over typical Struts development. I look forward to more articles in this series, as I think it will prove an excellent way of differentiating Tapestry 3.0 from JSF.

Certainly, I've seen nothing in any publication about JSF that would make me consider "closing up shop". At a high level, yes, the seem quite similar ... but there are basic assumptions and pervasive practices in both JSF and Tapestry that result in worlds of differences when you sit down to build a real application.

I expect to take a few minutes to put together Tapestry versions of Rick's examples. Monitor this blog for the details ... and remember that Tapestry discussions are best held on tapestry-user@jakarta.apache.org.