Tapestry Training -- From The Source

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

Friday, April 25, 2008

Tapestry: Components + Aspects

When I was first starting on the Tapestry 5 code base, I started using AspectJ for parts of the framework. I wrote some clever aspects for defensive programming (cementing "don't accept null, don't return null" as an iron-clad rule) and created some aspects to simplify concurrent access to certain resources.

I certainly never expected Tapestry developers to have to use AspectJ: it's certainly a powerful tool, but it has downsides: AspectJ affects the build process in a signifcant way, it's tricky to use properly, it adds a huge runtime dependency and the plugin for Eclipse made the IDE slow(-er) and (more) unstable. I eventually stripped out the AspectJ code entirely and restricted the framework to traditional code (plus, of course, Javassist).

In the interrum a number of aspect oriented touches have appeared in Tapestry. Certainly, the entire mixins concept is an approach to aspect orientation. Likewise, service decorators. In fact, much of the class transformation mechanism is a way of performing aspect-oriented operations on Tapestry component classes. Creating your own parameter binding prefixes (something I often do for client projects) is another aspect: one focused on streamlining how data gets into or out of a component.

However, all of these techniques can be cumbersome; they are built on the Javassist "language" which is another tool that can be difficult to use properly. I've created layers around Javassist to make things easier, but it's still an uphill battle.

Earlier in the week I was able to take the Javassist out of service method advice. However, that's not quite enough ... I want it to be easier to apply advice to component methods just as easily.

Well, I did just that. There is a ComponentMethodAdvice that can be applied to component methods. It's only a slight variation from the MethodAdvice used on service interfaces (the difference being that the invocation allows access to the ComponentResources of the component).

Here's a concrete example: Part of tapestry-hibernate is the @CommitAfter annotation, which is used to commit the running transaction after invoking a method. There's a decorator that can be applied to services, and there's a worker that is automatically threaded into the Component Class Transformation chain of command.

The worker used to use the Javassist approach to decorate method invocations with the desired logic: commit the transaction when the method completes normally, or with a checked exception. Abort the transaction when the method throws a runtime exception.

First, the old code:

public class CommitAfterWorker implements ComponentClassTransformWorker
{
    private final HibernateSessionManager _manager;

    public CommitAfterWorker(HibernateSessionManager manager)
    {
        _manager = manager;
    }

    public void transform(ClassTransformation transformation, MutableComponentModel model)
    {
        for (TransformMethodSignature sig : transformation.findMethodsWithAnnotation(CommitAfter.class))
        {
            addCommitAbortLogic(sig, transformation);
        }
    }

    private void addCommitAbortLogic(TransformMethodSignature method, ClassTransformation transformation)
    {
        String managerField = transformation.addInjectedField(HibernateSessionManager.class, "manager", _manager);

        // Handle the normal case, a succesful method invocation.

        transformation.extendExistingMethod(method, String.format("%s.commit();", managerField));

        // Now, abort on any RuntimeException

        BodyBuilder builder = new BodyBuilder().begin().addln("%s.abort();", managerField);

        builder.addln("throw $e;").end();

        transformation.addCatch(method, RuntimeException.class.getName(), builder.toString());

        // Now, a commit for each thrown exception

        builder.clear();
        builder.begin().addln("%s.commit();", managerField).addln("throw $e;").end();

        String body = builder.toString();

        for (String name : method.getExceptionTypes())
        {
            transformation.addCatch(method, name, body);
        }
    }
}

Can you tell where that logic (when to commit and abort) is? Didn't think so, it's obscured by lots of nuts-and-bolt logic for creating the Javassist method body.

All that stuff with the BodyBuilder is a way to assemble the Javascript method body piecewise. It's certainly meta-programming, but it's only a few steps ahead of writing the Java code to a temporary file and compiling it. That's great for people into the meta-coding concept (count me in) but sets the bar so high that most people will never be able to tap into the power.

That's where the advice comes in: the new version of the worker is much simpler:

public class CommitAfterWorker implements ComponentClassTransformWorker
{
    private final HibernateSessionManager _manager;

    private final ComponentMethodAdvice _advice = new ComponentMethodAdvice()
    {
        public void advise(ComponentMethodInvocation invocation)
        {
            try
            {
                invocation.proceed();

                // Success or checked exception:

                _manager.commit();
            }
            catch (RuntimeException ex)
            {
                _manager.abort();

                throw ex;
            }
        }
    };

    public CommitAfterWorker(HibernateSessionManager manager)
    {
        _manager = manager;
    }

    public void transform(ClassTransformation transformation, MutableComponentModel model)
    {
        for (TransformMethodSignature sig : transformation.findMethodsWithAnnotation(CommitAfter.class))
        {
            transformation.advise(sig, _advice);
        }
    }
}

Really simple! The advice is very clear on what happens when, as long as you realize that proceed() invokes the original method (the method "being advised"), and also Tapestry's way of handling thrown exceptions: runtime exceptions are not caught by proceed(), but checked exceptions are.

This simplicity opens up the power of aspects without all mental overhead of AspectJ's join points and declarative point-cut language. It's only a fraction of AspectJ's power, but it's a very useful fraction.

Further, AspectJ works entirely in a static way (it primarily works at build time), but this style of AOP is a mix of declarative and imperative. In other words, we can have active code decide on a component-by-component and method-by-method basis what methods should be advised. The above example is typical: it's driven by an annotation on the method ... but other cases jump to mind: triggered by naming conventions, by parameter or return types, or by thrown exceptions. And there's no guesswork about going from the advice to Tapestry services: its the same uniform style of injection used throughout Tapestry.

In the future, we may add other options, such as advice for field access, but what's just checked in is already a huge start.

Sunday, April 20, 2008

Tapestry AOP: MethodAdvice

I don't go out of my way to talk about Tapestry 5 IoC, since it is really just a side issue compared to the main framework (but it is the infrastructure that allows Tapestry to be everything it is).

However, the IoC container is quite powerful and fully featured and quite useful on its own. However, one feature of the container wasn't quite up to snuff: service decorators. Service decorators are a limited form of aspect oriented programming wherein its possible to "wrap" a service with an interceptor that provides additional behavior. Tapestry includes two decorators: one for adding logging of method invocations, results and exceptions and a second decorator for managing Hibernate transactions.

Tapestry is responsible for invoking a decorator method, but the responsibility of that method was to return an interceptor object that encapsulates the behavior. So, a logging interceptor is responsible for logging method entry and exits around re-invoking each method on the service, and a transaction interceptor is responsible for commiting or aborting the transaction after invoking the service method.

The problem is how to provide such an interceptor. Option A is to use a JDK dynamic proxy. This is a perfectly good solution, but has a few annoyances; you drop out of pure bytecode which means that Hotspot will not be able to fully optimize your code. In addition, there a minute cost associated with constantly boxing and unboxing primitives and packaging parameters up as an object array.

Option B is to use Tapestry's ClassFactory and ClassFab interfaces to create a new class and then instantiate. Javassist is under the surface, but the APIs are tamed. Even so, its a lot of work and a lot to test to check all the edge cases such as handling of primitives (more boxing and unboxing). This was the approach I've been taking in the past.

So I've taken some inspiration from the AOP Alliance approach.

You can now create an interceptor using a special service, AspectDecorator, and providing MethodAdvice. For example, the HibernateTransactionDecorator (which looks for the @CommitAfter annotation) is simplified to:

public class HibernateTransactionDecoratorImpl implements HibernateTransactionDecorator
{
    private final AspectDecorator _aspectDecorator;

    private final HibernateSessionManager _manager;

    private final MethodAdvice _advice = new MethodAdvice()
    {
        public void advise(Invocation invocation)
        {
            try
            {
                invocation.proceed();
            }
            catch (RuntimeException ex)
            {
                _manager.abort();

                throw ex;
            }

            // For success or checked exception, commit the transaction.

            _manager.commit();
        }
    };

    public HibernateTransactionDecoratorImpl(AspectDecorator aspectDecorator, HibernateSessionManager manager)
    {
        _aspectDecorator = aspectDecorator;

        _manager = manager;
    }

    public <T> T build(Class<T> serviceInterface, T delegate, String serviceId)
    {
        String description = String.format("<Hibernate Transaction interceptor for %s(%s)>",
                                           serviceId,
                                           serviceInterface.getName());

        AspectInterceptorBuilder<T> builder = _aspectDecorator.createBuilder(serviceInterface, delegate, description);

        for (Method m : serviceInterface.getMethods())
        {
            if (m.getAnnotation(CommitAfter.class) != null)
            {
                builder.adviseMethod(m, _advice);
            }
        }

        return builder.build();
    }
}

At this weekend's No Fluff Just Stuff, Neal Ford went out of his way to point out how hard meta-programming in Java is, and how impressed he was by what I've accomplished using meta-programming throughout Tapestry. Even so, he and the other speakers voted me "most in need of a dynamic programming language" behind my back!

Meanwhile, this MethodAdvice approach (the terminology is borrowed from AOP and AspectJ) really is a very succinct way of expressing this transaction concern, and it's so easy to mix and match methods within the interface, advising some of them and letting the others just "pass thru". There's going to be a lot of room to use this same technique to provide many other cross-cutting concerns such as performance monitoring, security checks ... even checking for null pointers!

Thursday, April 17, 2008

Service Dependencies in Tapestry 5

I was curious to see if I could generate a diagram that showed how all the services inside Tapestry 5 are interconnected. In the diagram, solid lines are dependencies, dashed lines are contributions, and dotted lines represent a services that listens to events from another service.

The final diagram is a bit complex. Ok, that's a tremendous understatement. Yellow nodes are public services, grey nodes are internal services, and orange nodes are simple beans (contributed into services) ... those beans, though, may still have their own dependencies. I think OmniGraffle choked on the complexity (why do some of the lines zip around so aimlessly?) and kind of "phoned it in" in terms of layout. I had hoped for something that could go on a T-shirt.

Worse yet, this is only a partial diagram; I didn't cover many of the dependencies that arrive as contributions to services (there's just a few of those in there). Those contributions would show up as even more orange nodes; these additional nodes would provide connections to many of the outliers that appear to be unreferenced.

Is this good or bad? Is it too complex? Can it be simplified? Does all that really need to be in there?

I think it is good: When I'm coding, I find it easy enough to work from a service interface, to the implementation of the interface, to the dependencies of the implementation. This works for me when developing Tapestry itself, or writing apps in Tapestry. Since there are so many small objects, no single object is large, or complex, or hard to understand or debug. It's a bit daunting to know where to start, I'm sure. However, only a few of these services are all that relevant for day-to-day development of Tapestry.

The moral of the story is that simplicity is hard! In Tapestry, pages and templates are as simple and straight forward as I think is possible. There's no excess, no distractions, no clutter, no boilerplate. Much of the logic represented in the diagram is to "fill around the edges" of this simplicity, to bridge the gap from the simple Tapestry component model to the much more complex world of stateless HTTP and multi-threaded request handling.

New Tapestry 5 Features

I've been using Tapestry 5 for a client project, which is a great chance to find the rough edges. If you've been following the bug list, you'll have seen lots of minor bug fixes, many related to JavaScript.

I've also added a couple of powerful new features.

The Grid component now has an inplace parameter; setting this to true "Ajax-ifies" the Grid; it updates in place. Clicking any of the paging or sorting controls will repaint the Grid in place, without affecting the rest of the page. Handy. I had originally though in terms of creating an AjaxGrid component for this purpose capable of redrawing individual cells ... now I'm not so sure that's necessary.

Perhaps more importantly, I've added a new annotation, @CommitAfter, that can be placed on component methods, to indicate the Hibernate transaction should be committed after invoking the method. It can also be used with services, via a decorator. In any case, much of the work was contributed by Igor Drobiazko.

These features are available in the nightly build, and will be part of Tapestry 5.0.12.

They are also a good indication of how Tapestry future backwards compatibility will work. The new behaviors "slots in" with the existing behavior. In this case, a new annotation triggers new behavior, or a new parameter is added to an existing component. These are the vectors by which Tapestry adapts to your code (and not vice-versa).

In the future, we'll be adding much more dramatic new features, as well as integrations with other frameworks, such as Seam, Spring Web Flow, Lucene, Quartz and other parts of the evolving standard open source stack. But due to the baked in elegance of Tapestry, they will integrate with barely a ripple.

Tuesday, April 15, 2008

A round-up of Tapestry blogs

I was just fiddling with Google's Blog Search and searched for "java tapestry"; here's a few things I stumbled upon:

Fun stuff ... and I have more advancements for Tapestry 5 about ready to announce!

Sunday, March 30, 2008

Web Framework Smackdown: Tapestry

I wasn't able to attend this years TSSJS and I really would have liked to be in on Matt Raible's web framework smackdown. Matt has raised a number of questions, here are the responses for Tapestry 5:

  • What is the overall performance of your framework as it compares to others?

    I haven't done any direct performance comparisons, but Tapestry 5 is considerably faster than Tapestry 4, which was already "fast enough". Using FireBug, I see that most requests (on my development machine) are on the order of 4 - 6 ms to process a request and get the response stream back; it takes the browser much longer to render the HTML up onto the screen. Tapestry uses no runtime reflection and doesn't appear to have any concurrency bottlenecks (the way OGNL can be in Tapestry 4).

  • How does your web framework position themselves in relation to Web Beans?

    Web Beans is one way of looking at how to manage state and process in a web application, but not the only one. Tapestry is already being used with Seam, and integration with Spring Web Flow will come some day (likely 5.1 as we're trying to get a stable 5.0 release out the door). Tapestry is in a good position to "embrace and extend" these kinds of specifications, since it doesn't care about anything but JavaBeans properties.

  • How easy is it to create a re-useable component in your framework? Is it as easy as sub-classing an existing component?

    You can subclass an existing component, but you can create a component as just a POJO with no inheritance. There's no configuration (other than putting it in the correct components package for your application). Bi-directional component parameters are as easy as placing a @Parameter annotation on a field. Components may have templates and may contain other components, it's all very consistent. It's always been easy to create components in Tapestry, but Tapestry 5 adds a number of features that make it even easier (such as no longer requiring you to select the correct base class). Further, it is much easier in Tapestry 5 to subclass a component and inherit its template and message catalog, but add new functionality. In addition, Tapestry 5 has the concept of Mixins, which provide another way to add new functionality to an existing component.

  • What is the key differentiating characteristic of your framework that makes it better than the rest?

    A ruthless emphasis on developer productivity drives nearly all the design decisions in Tapestry: this shows up with live class reloading, exceptional error reporting, adaptive APIs, and minimizing the amount of code (and markup) in an application. In terms of implementation, Tapestry embraces pervasive object orientation (not just inheritance), meaning lots of small focused objects able to work together seamlessly.

  • What do you think about the various scopes introduced by Seam, e.g. conversation vs request or session? If you support these additional scopes, do you also provide some sort of concurrency control?

    These are very important; Tapestry already has a number of ways of managing server-side state; individual page or component fields may be stored on the client, rather than in the session, for example. As with much of T5, the support is cleanly extensible, so natively supporting conversational state without using Seam is a possibility, as is leverage Seam itself (there's an add-on library for this), as are many other options.

  • Why can't we, the Java Community, come together and adopt the best application framework and settle the web development subject?

    That's like saying, "Why can't we, the developer community, come together and adopt the best language?". Generally, people with a lot of experience in one framework are looking for a bigger, badder version of their framework, that also magically solves all their problems ... especially those caused by their choice of framework. Sometimes it's the craptaculous framework. Often the fix is something is a completely different vein (and Tapestry has always been that different vein). It's kind of like the adage about a council of Great Apes designing their evolutionary successor. They'd probably come up with a bigger, stronger Ape ... not a scrawny, weak, hairless human.

  • What are you doing to help with developer productivity?

    Tapestry is primarily focused on developer productivity, and this should not be confused with simply adding tool support (and dealing with wizard-generated code). Tapestry addresses the needs of developers to build applications using simple, off the shelf tools, such as Java IDEs and standard XML editors. Tapestry has best-of-breed exception reporting. This is more than a fancy exception report page; it's also attention to detail throughout the framework to catch problems and report them in a useful manner to the developer. For example, if you use the wrong name when defining a component or referencing a page, Tapestry doesn't just throw a NullPointerException the way too many libraries would; it clearly identifies the line in your template with the problem, identifies the name you gave, and provides a list of possible values to help you make the correction. Oh, and it's generally case insensitive. Add live class reloading on top of this, and you can burn through your application very quickly. In fact, Tapestry addresses developer productivity by making the development more Fun and less of a Drudge.

  • 2008 is a huge year for the mobile web. How do you help developers build great mobile web applications?

    Size counts on the mobile web and Tapestry has been built with terseness as a virtue. URLs are shorter and more readable, page content is simpler, and many components include a "lean" mode to reduce optional content generation (such as CSS classes inside the Grid component).

  • If you couldn't use your framework, what would you use and why?

    I'd be very interested in learning to really leverage Adobe's Flex ... if I can't use Tapestry, I want something that gives me pixel-level control and good abstraction without the kludges and half measures of JavaScript and the DOM.

  • How do you enable rich Ajax applications?

    Tapestry supports very clean interactions between the client and the server, as server-side components can easily be referenced via a URL. Handling Ajax requests in Tapestry is virtually identical to handling of traditional page-oriented requests. A limited amount of base support is provided in the framework (text field autocompletion, dynamic content zones, extensible forms) with much more support provided by the community. Currently Ajax support is a wrapper on top of Prototype/Scriptaculous and there is great desire to make the client-side support more agnostic.

  • Can a developer make a change to source, and hit RELOAD in the browser to see the change? If not, why not?

    Absolutely. I think Tapestry was on the cutting edge nearly two years ago when this feature was introduced in early versions of Tapestry 5; other frameworks are still trying to catch up. This feature is critical to developer productivity and making it fun. Further, Tapestry's approach to server-side state means that persistent data is not lost on reload, which significantly enhances the developer's ability to continue work from where they left off. Tapestry is even pretty good about picking up the addition of new pages and components, not just updates to existing Java classes.

  • What do you think about the whole Flex revolution, and do you think you are competitors to this technology?

    Flex is an impressive piece of work, and it gives you quite a lot of power. Still, I think RIAs (such as Flex) are in a parallel, not competing, category against traditional web applications (even those enhanced with Ajax) . Tapestry has an emphasis on bookmarkable URLs, and though Flex supports this feature, there are some costs and limitations. I don't want to spread FUD, but to me a Flex application is pretty heavy weight ... big enough to require a loading screen, whereas a web application just pops up instantly. I haven't pushed myself far enough up the Flex learning curve to talk in more detail. I tend to discount the "Grandma factor": that is, Flash is almost certainly already installed on Grandma's computer. Even so, Flex's greatest strength and greatest weakness is the proprietary Flash runtime: even more than Sun, Adobe is protective of their runtime environment, and fearful of splintering. This is increasingly unsettling to developers used to full open source everything.

    P.S. I'm sure James Ward will correct everything I've just stated.

  • How easy is it to create a module and plug it into a bigger application, complete with configuration, code, and view?

    Really, really easy. Choose a package name, put your pages, components, base classes, etc. in sub-packages, along with any templates. Create a module class for your library to provide Tapestry with configuration for the module's package. One tweak to your Ant or Maven build script to enable autoloading of the module, at which point it Just Works. Tapestry takes care of all sorts of issues, including providing secure and efficient client-side access to assets (images, stylesheets, etc.) packaged inside your library's JAR.

Friday, March 14, 2008

Tapestry 5.0.11

Tapestry 5.0.11 is now available for download. This is unofficially a beta, meaning that the functionality is really nailed down and we're just stabilizing and fixing bugs.

There's a lot of cool new things in this release. I think the three biggest are:

  • HTTPS control: annotate a page as only accessible via HTTPS. Tapestry takes care of the rest.
  • Index pages for folders: kind of like the Start page, but everywhere.
  • Automatic Hibernate ValueEncoders: Use an entity as a page or event context and what goes in the URL is the entity's id. Likewise, ids are turned back into entities when passed to event handler methods.

What's next? A period of stabilization and bug fixes and then a release candidate. And then, some really awesome (and really backwards compatible) stuff for the 5.1 release.

Thursday, February 28, 2008

No Fluff Just Stuff / Danvers, MA

I'll be kicking off my participation in the No Fluff Just Stuff symposium series this year on Sunday April 6th. I'll be talking about Tapestry, testing and design patterns.

I'm especially looking forward to the Tapestry session, of course, as I have so much more to say on Tapestry this year than last. But I also enjoy the testing talks (it's a pair of talks), perhaps because I'm not as emotionally invested in EasyMock and TestNG as I am in Tapestry.

Monday, February 25, 2008

Tapestry 5 Index Pages

I've been rapidly closing the gap between where Tapestry 5 is and where it needs to be for a stable release.

Today I added in index pages; literally, pages named Index.

In the past, you could create a Start page and it would be used for any request that just specified the servlet context but didn't identify a specific page. That was great ... at the top level. It's also supported for backwards compatibility (even I am not that cruel).

However, the preferred method is to call that page Index. Further, you can place Index pages in subfolders as well. Tapestry can reference such a page using just the folder name.

That's great so far, but nobody wants to have a bunch of classes with the same name, even in different folders. No problem; the standard "name stripping" occurs. Thus, you might have an ecommerce application and three pages : com.myco.pages.order.OrderIndex, com.myco.pages.order.OrderVerify and com.myco.pages.order.OrderSubmit. The names of these three pages will be "order/Index", "order/Submit" and "order/Verify" (the redundant parts are stripped out). Lastly, Tapestry will create URLs of the form ".../context/order", ".../context/order/submit" and ".../context/order/verify".

In other words, short, sensible URLs and no compromises in your code. And, as always, any extra path info beyond the page name is treated as page activation context, made available to the page via the "activate" event handler method.

I'm very happy with this change; I also think Tapestry's built-in rules for URLs are really strong and appealing; I simply don't see customizing the built-in rules to be a priority, or frankly, of any great value.

Thursday, February 21, 2008

Secure applications in Tapestry 5

Tapestry 5 features continue to come together rapidly; the latest is HTTPS support.

It's very simple: mark a page as secure ...

@Secure
public class ProcessOrder
{
  . . .
}

... and Tapestry takes it from there. Every request for that page will automatially use an HTTPS protocol URL. Any attempt to circumvent HTTPS will result in an HTTPS redirect; there's no way to access the page without HTTPS. Links from a secure page to an insecure page use the standard "http" protocol ... so Tapestry handles the whole cycle, from insecure to secure and back again.

That's about it; behind a firewall there's a little bit more configuration to allow Tapestry to know how to properly build the complete URL (the URL with protocol, server name and maybe port) since (behind a firewall) the servlet API doesn't properly report the information the way the client web browser needs it.

Wednesday, February 20, 2008

Unit Testing: Crisis of Faith

I'm hitting a bit of a quandary with response to testing: I'm losing some faith in unit testing, or at least busywork unit testing relative to integration testing.

At issue is the way Tapestry is constructed; there's a lot of small moving parts that work together (integrate together might give you some foreshadowing). Sometimes testing the small pieces won't accomplish much. Case in point:

public class SecureWorker implements ComponentClassTransformWorker
{
    public void transform(ClassTransformation transformation, MutableComponentModel model)
    {
        Secure secure = transformation.getAnnotation(Secure.class);

        if (secure != null)
            model.setMeta(TapestryConstants.SECURE_PAGE, "true");
    }
}

This piece of code looks for the @Secure annotation on a component class, and records a bit of meta data that will be used elsewhere.

In the past I've written a chunk of tests even for something as simple as snippet; Following that pattern, I'd write two tests: 1) what if the annotation is missing? 2) what if the annotation is present? I'd use mock objects to ensure that the correct methods of the correct dependencies are invoked in each scenario. However, to write those tests, even leveraging existing base classes to manage the mock objects I'd need, would require several times as much test code as the production code I want to test.

But what would that prove? This bit of code will be useless without a large number of other pieces yet to be written: code that uses that meta data and forces the use of secure HTTPS links and so forth. Code that integrates this worker into the chain of command for processing component classes. Code that verifies that requests for pages that are marked as secure are, in fact, secure.

In other words, to verify the behavior I need to validate that all of those pieces are working together, that they are all integrated.

That doesn't mean I'm calling for the abandonment of unit tests. What I'm saying is that pointless unit tests don't accomplish much. I've found that unit tests are invaluable for testing edge cases and error conditions. Even the most rabid test driven pundits don't expect you to unit test your accessor methods ... I think tiny classes like this SecureWorker fall under the same general umbrella. I intend to focus my efforts on the integration tests that will encompass this code in concert with other code. And maybe write a few unit tests along the way, as long as they actually have value.

Update: It's not that I can't write the test, it's that doing so is not always worth the effort. Here's a similar test I wrote in the past:

public class SupportsInformalParametersWorkerTest extends InternalBaseTestCase
{

    @Test
    public void annotation_present()
    {
        ClassTransformation ct = mockClassTransformation();
        MutableComponentModel model = mockMutableComponentModel();
        SupportsInformalParameters annotation = newMock(SupportsInformalParameters.class);

        train_getAnnotation(ct, SupportsInformalParameters.class, annotation);
        model.enableSupportsInformalParameters();

        replay();

        new SupportsInformalParametersWorker().transform(ct, model);

        verify();
    }

    @Test
    public void annotation_missing()
    {
        ClassTransformation ct = mockClassTransformation();
        MutableComponentModel model = mockMutableComponentModel();

        train_getAnnotation(ct, SupportsInformalParameters.class, null);

        replay();

        new SupportsInformalParametersWorker().transform(ct, model);

        verify();

    }
}

Great: this detects whether @SupportsInformalParameters is on the class ... but I have other integration tests that actually stress the behavior when the annotation is present and when the annotation is absent. Ultimately, that tests both of these cases, and the code that ensures that this code is even executed, and the desired behavior of the component (with and without the annotation). Failures are unlikely to occur in this code, but likely in other code. What is the value of this test? Not even code coverage, and the integration tests hit both cases already.

Monday, February 18, 2008

Prototype and custom events

I've been doing a lot of work using Prototype for the Ajax support in Tapestry 5. I use Prototype because it is generally easy to use, easy to bundle with Tapestry and reasonably well documented.

I've been gradually using new features as I need them and as I grow more comfortable with Prototype. I just started using some custom events (related to synchronizing some data when a form is submitted).

Here's an important note: custom events, used with the fire() function must contain a colon. this.form.fire("prepareforsubmit") will do nothing. this.form.fire("form:prepareforsubmit") will properly invoke observers. This is not yet documented, though the ironically named blog Painfully Obvious clued me in.

Sunday, February 17, 2008

Civility

Well over a year ago, I had to start moderating comments on this blog, due to a couple of (or more likely, a single) pathetic individual(s) who were using the comments feature in an attempt to upset me with derogatory comments about Tapestry and about myself. Let's call this person "Jed". Jed is truly wierd and sad ... that anyone would find this kind of harrassment appropriate points to an injured and emotionally crippled mind.

Today I got back to my desk to see the latest comment from this twit ... and he'd gone nuclear: he made remarks about my wife.

That goes far past merely sad, and deep into the desperate and cowardly. I've been posting articles, blogs and comments for over ten years and have never posted anything anonymously. And where I have attacked a technology before, and even made statements I later regret, I've always done so openly and honestly. Jed hides behind anonymity to cause injury he would in no way dare make in person.

I have no problems with criticisms of Tapestry or myself because I have appropriate faith in my technical acumen and accomplishments. But I would never, even in jest, stoop to the levels this lowlife scumbag has taken.

The best course of action is just to ignore him, as I do when he writes inflammatory mails on the Tapestry mailing lists. I won't gratify his deplorable need for attention by going into more detail about who he is or what he wrote. But someone out there may actually know "Jed" and should take him aside and tell him to act like an adult, or at least, not like a baboon.

Monday, February 11, 2008

Short review of "Tapestry 5 - Building Web Applications"

The Develop in Java web site has a short review of Alexandar's book. They liked it:

I liked this book and found the writing style to be informative and easy to read. If you have little or no knowledge of Tapestry 5 and you are interested in it (or just want to decide if you should be using it for your applications), then this is the book for you.

Friday, February 01, 2008

Another thing I love about IntelliJ

I've been mentally compiling a few tiny rough edges in IntelliJ. The difference between IntelliJ and Eclipse is that I can use IntelliJ's JIRA, add an issue, an expect a response. Every time I've done this, I've gotten a response in a couple of hours, either pointing out an existing fix, confirming my problem, or pointing out an alternative solution. I just don't get that feeling with Eclipse ... most bugs I've entered over the years into their Bugzilla system have never been resolved to my satisfaction.

And Bugzilla in general? That's pretty much raising the bar against anyone who doesn't have a high tolerance for wierdness and frustration.

Update: Case in point, I can respect their reasoning even if I don't agree, and I got a response in four minutes.

Tuesday, January 29, 2008

Tapestry mentioned in ComputerWeekly

My friend Todd pointed me to this mention of Tapestry in ComputerWeekly under the heading "Hot Skills: MVC". The same article cites the momentum of Tapestry and Spring and the relative weakness of Struts.

They even used one of my catch phrases: The simplest choice should be the correct choice. Nice.

Monday, January 28, 2008

Maven: Won't Get Fooled Again

Fool me once, shame on you. Fool me twice, shame on me. I'm not going to get fooled by Maven again.

I can't express how much grief recommending Maven has caused me. I had a good experience with Maven for Tapestry 5 for quite a while and had the hubris to recommend it for The Really Big Project. TRBP consists of 40 (and growing) modules, all deeply interrelated. That interrelation is killer, it means that most developers have the master project, with all 40+ modules, open at once. That's 80 source paths (one for production code, one for test code), plus class folders, plus an amazing set of dependencies drawn in from Maven.

Maven simply doesn't scale to this level. Sure, the command line build does work in relatively short order (more on this shortly) ... but for day-to-day usage it's all about the IDE, and the IDE support for Maven just isn't there yet, may never get there.

In both Eclipse and IDEA, the Maven support has been very slow, buggy, and unstable. IDEA 7 gets it a bit more right than Eclipse (with the 0.0.12 Maven plugin) because synchronization is explicit. Still, it seems to turn into an endless amount of tweaking, fixing and praying, to keep things operational.

At the core, Maven has a great idea (really well thought out dependency management) saddled with an obnoxiously bad plugin system for performing the build. The obnoxious part is that a build that works on Monday may fail on Tuesday because some new, broken plugin was released into the central Maven repository.

Perhaps the worst part of Maven is the documentation, especially when it comes to writing your own plugins. The Maven team is criminal: their basic methodology appears to be:

  • Crank out an XML version of a data structure
  • Run a tool that generates a Java interface from the XML (containing no comments whatsoever)
  • Implement the class, don't add any comments
  • Don't package source with the plugin, as that would almost make it possible to figure out what's going on
  • If you slip up and accidentally generate JavaDoc, make sure the link to it on your site is broken

The Maven project site is an embarrassment. The tool supposedly designed for "project comprehension" is itself incomprehensible, due to its scale, the chaos of its documentation, and the extreme lack of engineering discipline evidenced by its maintainers.

Get out while you still can.

I'm now looking strongly at Ivy which keeps the dependency management (including Maven compatibilty) but jettisons the build. It just makes it easy to write your own Ant-based builds with the automatic dependency download.

Tuesday, January 22, 2008

Tapestry Code Quality

According to Enerjy, Tapestry comes in at about the top of the list for open source projects they've scanned. I suspect that T5 is quite a bit larger than the other top projects, and T5 probably also gets knocked down because I tend not to JavaDoc implementations, just the interfaces.

Also, looks like it doesn't understand Tapestry components, or the concept that the visible annotations on fields are both functional and documentation providing. Oh, and it hates that I don't use braces on trivial if statements.

This is all fine and good, though the code inspections built into IntelliJ were of more use to me.

Monday, January 14, 2008

Latest batch of features

I've been very busy on Tapestry 5 now, sprinting towards an end goal of a stable release. I'm very psyched about the latest stuff:

  • Improved Error Bubbles: those pop-up error bubbles (for client validaton, in 5.0.7) were just a first pass. I've smartened them up a bit, and now the bubbles for the non-focus field fade out entirely. I like the way it works, as you tab from field to field, the bubbles fade in and fade out appropriately.
  • Memory Management: Tapestry is a bit more careful about instantiating pages now. The first few page instantiations are "free" but past a certain point, Tapestry will wait a few milliseconds for a page instance to free up before instantiating a fresh one. And pages that aren't used for about 10 minutes are freed outright. And it's all configurable.
  • Whitespace Compression: Tapestry now strips out unnecessary whitespace from templates. This is a bit controversial (because the output is bit less readble) but is a win in terms of server side processing and on the client side. This change significantly reduces the number of objects needed in memory to represent a component template and that can't help but be a good thing.
  • Optimized Request Paths: When rendering output, Tapestry will make use of shortened, relative URLs, if those are shorter than an absolute URL. This is another attempt to minimize the content sent to the client, before we get into GZIP filters and so forth.
  • Immediate Response Mode: Miss the default Tapestry 4 request/render cycle? Don't care about book-markable URLs or the user hitting the refresh button and inadvertently re-submitting a form? You can now disable the normal "send a client redirect" behavior and have Tapestry render the HTML immediately. This requirement comes from some particular clients, but honestly I hope it won't be widely used.

Beyond that its been bugs, bugs, bugs and getting the last major features in place; more Ajax support (with still more to come) and generally adding some polish.

I still can't say exactly when this will be ready, but I'm now able to work on Tapestry full time (and then some) so its coming together rapidly.

Thursday, January 10, 2008

Creating new T5 projects the easy way

The Maven archetype for Tapestry projects is really useful for getting up and running quickly.

However, that's not what I use day-to-day. That command line is so long and ugly!

I use the following Ruby script as a wrapper around Maven:

#!/usr/bin/ruby

require 'optparse'

$group = nil
$artifact = nil
$package = nil
$archetypeVersion = nil
$version = "1.0.0-SNAPSHOT"
$offline = false

$opts = OptionParser.new do |opts|
  
  opts.banner = "Usage: new-project.rb [options]"
  
  opts.on("-g", "--group GROUP", "The group id for the new project") do |value|
    $group = value
  end

  opts.on("-a", "--artifact ARTIFACT", "The artifact for the new project") do |value|
    $artifact = value
  end
  
  opts.on("-p", "--package PACKAGE", "The root package for source code in the new project") do |value|
    $package = value
  end
  
  opts.on("-v", "--version VERSION", "The version number of the new project") do |value|
    $version = value
  end
  
  opts.on("-o", "--offline", "Execute Maven in offline mode") { $offline = true }
  
  opts.on("-V", "--archetype-version VERSION", "Version of the Tapestry quickstart archetype") do |value|
    $archetypeVersion = value
  end
  
  opts.on("-h", "--help", "Help for this command") do
    puts opts
    exit
  end
end

def fail(message)
  puts "Error: #{message}"
  puts $opts
  exit
end


begin
  $opts.parse!
rescue OptionParser::InvalidOption
  fail $!
end

fail "Unexpected command line argument" if ARGV.length > 0
fail "Must specify group" unless $group
fail "Must specify artifact" unless $artifact

$package = $package || "#$group.#$artifact"

command = ["mvn"]

command << "-o" if $offline

command << [
  "archetype:create",
  "-DarchetypeGroupId=org.apache.tapestry",
  "-DarchetypeArtifactId=quickstart",
  "-DgroupId=#$group",
  "-DartifactId=#$artifact",
  "-DartifactVersion=#$version",
  "-DpackageName=#$package"]

if $archetypeVersion
  command << "-DarchetypeVersion=#$archetypeVersion"
end

command = command.join ' '

exec command

A typical usage is thus: new-project -g com.example -a myapp ... and we're off and running.