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!

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.

Sunday, November 25, 2007

First Tapestry 5 Book

The first book on Tapestry 5, Tapestry 5: Building Web Applications, is available for pre-order. I've been helping out by tech editing the chapters.

Saturday, November 24, 2007

Working on T5 Ajax Support

This is stuff people have waited impatiently for going on six months or more, and it's starting to come together. I'm continuing to work using Prototype/Scriptaculous and put together a simple Autocompleter mixin. I've been working on a more general Ajax operations, wherein a link on the page will trigger a component event and the return value there (usually a Block) will be used to update a portion of the page (demarked by a Zone component).

I'm also simplifying a bunch of APIs that got twisted up for PageTester (Kent Tong's unit testing layer for T5). ActionResponseGenerator is going away which makes a lot of code and testing easier and simpler.

Right now the only thing slowing me down is some soreness in my left hand.

Wednesday, November 21, 2007

IntelliJ Watch: Change Sets

I've started using IntelliJ change sets, another great feature. A change set is very intuitive: a set of related changes. Typically the changes are related to a bug.

IntelliJ tracks those changes as a group: all the changed, added and deleted files. You can then commit those changes as a group, without checking in anything else.

This is just perfect for checking in a quick bug fix in the middle of a larger body of work.

Nice features: You create a comment when you first create the change set, this comment is the default check-in comment when you do commit changes. Further, they didn't skimp ... you can move a change between change sets.

The pattern I'm seeing is that IntellJ has features that cover specific things I've had to do manually and awkwardly in Eclipse. I can definitely remember times I've fixed a small bug and had to carefully pick and choose which files to check in, because I had many unfinished outstanding changes waiting to go. IntelliJ just picks up the slack and does it for me.

I'm not sure how you handle this situation in Mylyn, Eclipse's super-invasive ... tool? Framework? Filter? Nanny? Whatever it is, I guess it can do it, but I doubt it accomplishes the same goal as simply and comprehensibly.

Saturday, November 17, 2007

Another IntelliJ Zealot

Wow that was quick. My first attempt with IntelliJ didn't really take, but I've had more time now to make the adjustment under less pressure.

What a difference. On my Mac at least, it's much faster and more stable than Eclipse. I've been running current versions of Eclipse, with the Maven plugin but very little else, and Mylyn disabled as much as possible ... and Eclipse has been agonizingly slow and very unstable with multiple lock ups and crashes per day.

IntelliJ is just ... better. Faster (note that you need to turn off the Mac L&F if you want best performance; I'm using "Alloy").

I've been so thoroughly used to the Eclipse development model, including constant compilation. IntelliJ doesn't compile until you need to run code. It does parse your code constantly, which amounts to the same thing in terms of refactoring and code completion, but IntelliJ is far more forgiving of syntax errors and the like.

Maven integration is better; I chose an option where it synchronizes from Maven on demand (and on startup).

For a multi-module projects, IntelliJ does a better job. Each module really gets its own classpath, whereas Eclipse merges together all the source paths and all the libraries for all modules.

A couple of Tapestry development notes:

  • You do want to make sure that your exported resource patterns (in the Compiler project options) is !?*.java ... otherwise critical Tapestry files don't get copied and made available on the classpath.
  • Also, for IntelliJ you have to perform a make (command-F9) after changing code or templates for those changes to be visible.

Yes, there's a number of nitpicks, especially on a Mac. It's extremely intimidating the first time you launch, with a wealth of confusing options. Documentation is spotty and key concepts are never explained. There's too many modal popup windows. Regardless, I'm already working faster, using the mouse less, and keep finding new features. I'd say I'm using about 20% of what IntelliJ can do right now, and I'm already well ahead of the Eclipse curve.

Apparently, IntelliJ is like snowboarding: you need to give it three full days before you're hooked. Counterpoint: I'm still on skis.

Another analogy: IntelliJ is metric and Eclipse is english. If all you've ever known is feet and inches and pounds and ounces, then metric doesn't seem worth the effort to switch. Metric just seems like a different set of numbers, mixed with disconcerting nuances (such as the difference between mass and weight). On the other hand, once you make the transition, many difficult tasks become easier, since most conversions are multiplying or dividing by a power of ten.

IntelliJ's 30 day evaluation is long enough to get hooked. Give it a try.

Monday, November 12, 2007

A Leopard Upgrade Story

I haven't been having the easiest time with my Leopard upgrade.

After my first attempt to upgrade, I got the dreaded "blue screen hang" on startup. This afflicts some number of systems, where after the install, all you get is a blue screen. After a couple of tries, I did a clean install and that worked better. Fortunately, I didn't follow Ben Galbraith's advice and "pull the switch", and I'm so glad a made a complete backup first!

I imported my settings from my backup and the vast majority of things just worked, including applications (/Application and ~/Applications) and most settings. Not my printer, though.

I'm still trying to figure out how to get svnserve up and running again, which is no fun, as I had just figured it out for Tiger. I'm getting a permissions problem accessing the files as user _svn (Leopoard puts an underscore in front of all system or daemon user ids and groups). I don't understand exactly why.

Other fun ... I decided to move to Mail.app from Thunderbird, since Mail.app can import Thunderbird mailboxes. For some reason, it only imported my older mail, so I have to keep Thunderbird around to access all my mail from 2007. 2006 and earlier imported. I don't get it!

Eclipse has become unstable for me. That's no fun, it's been getting memory access violations and crashing hard. So, this is a new chance to try IntelliJ. I'm already like some things, but I've also already hit a couple of bugs and the shear wealth of options and terminology is overwhelming.

I haven't tried out Time Machine yet, I may give it a whirl tonight.

I'm starting to adjust to using Spaces and have closed up my Mac's screen; with multiple desktops, it's just simpler (and better for my posture) to use a single screen. I may have to get a camera for iChat.

I immediately moved the Dock to the right side of the screen, to turn off the awful, stupid, distracting look they introduced.

Leopard includes the svn suite by default (I'm not sure if Tiger did); using MacPorts to install Hugs and erlang was easy, but ghc is a mess because of something that's changed between tiger and leopard. But who has time to study anything new when they're busy trying to finish Tapestry (and learn IntelliJ)?

Friday, November 09, 2007

SVNServe on Mac OS X

Somewhere along the way in switching from Fink to MacPorts (ostensibly because MacPorts is better supported) I shutdown my local SVN server. Strangely, the SVN for MacPorts doesn't include anything to assist in running a local SVN server.

Fortunately I found this useful snippet and it seems to work great, even after a reboot. Be careful to change the user name (at the bottom), the group name (at the top) and the SVN root repository (towards the top). For my system, I created a www user and www group as the owner of the SVN repository.

It may seem odd to run SVN server on a laptop; but this laptop is my development machine, and it goes with me everywhere, so I'm never in a position where I can't access my repository. Dave Thomas encourages you to store everything in version control, and I do my best.

Over the weekend, I'll be upgrading to Leopard, hopefully this will continue to work. I'm optimistic.

Thursday, October 25, 2007

A few new Tapestry 5 components

Sven Homburg is hosting a Tapestry 5 components site on Google code. Interestingly, it requires Tapestry 5.0.6 which is only available today. They've been riding the bleeding edge, they have!

Big improvement to quickstart archetype

I've figured out the necessary magic incantation that allows you to use an archetype without having to specify the archetype version number. Now anybody with Maven 2.0.7 installed can be up and running a Tapestry application in just a few seconds.

To test it out, I just removed my ~/.m2/repository/org/apache/tapestry folder and used the archetype to create a new Tapestry web application. The listing below gets clipped ... the command is mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart -DgroupId=org.example -DartifactId=myapp -DpackageName=org.example.myapp -Dversion=1.0.0-SNAPSHOT

$ mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart -DgroupId=org.example -DartifactId=myapp -DpackageName=org.example.myapp -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] ----------------------------------------------------------------------------
[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] ************************************************************** 
[INFO] Starting Jakarta Velocity v1.4
[INFO] RuntimeInstance initializing.
[INFO] Default Properties File: org/apache/velocity/runtime/defaults/velocity.properties
[INFO] Default ResourceManager initializing. (class org.apache.velocity.runtime.resource.ResourceManagerImpl)
[INFO] Resource Loader Instantiated: org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader
[INFO] ClasspathResourceLoader : initialization starting.
[INFO] ClasspathResourceLoader : initialization complete.
[INFO] ResourceCache : initialized. (class org.apache.velocity.runtime.resource.ResourceCacheImpl)
[INFO] Default ResourceManager initialization complete.
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Literal
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Macro
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Parse
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Include
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Foreach
[INFO] Created: 20 parsers.
[INFO] Velocimacro : initialization starting.
[INFO] Velocimacro : adding VMs from VM library template : VM_global_library.vm
[ERROR] ResourceManager : unable to find resource 'VM_global_library.vm' in any resource loader.
[INFO] Velocimacro : error using  VM library template VM_global_library.vm : org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'VM_global_library.vm'
[INFO] Velocimacro :  VM library template macro registration complete.
[INFO] Velocimacro : allowInline = true : VMs can be defined inline in templates
[INFO] Velocimacro : allowInlineToOverride = false : VMs defined inline may NOT replace previous VM definitions
[INFO] Velocimacro : allowInlineLocal = false : VMs defined inline will be  global in scope if allowed.
[INFO] Velocimacro : initialization complete.
[INFO] Velocity successfully started.
[INFO] [archetype:create]
[INFO] artifact org.apache.tapestry:quickstart: checking for updates from central
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/quickstart/5.0.6/quickstart-5.0.6.jar
11K downloaded
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: quickstart:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.example
[INFO] Parameter: packageName, Value: org.example.myapp
[INFO] Parameter: basedir, Value: /Users/Howard/work
[INFO] Parameter: package, Value: org.example.myapp
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: myapp
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/pom.xml [line 14,column 22] : ${tapestry-release-version} is not a valid reference.
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/pom.xml [line 80,column 26] : ${tapestry-release-version} is not a valid reference.
[INFO] ********************* End of debug info from resources from generated POM ***********************
[WARNING] org.apache.velocity.runtime.exception.ReferenceException: reference : template = archetype-resources/src/main/webapp/Start.tml [line 11,column 34] : ${currentTime} is not a valid reference.
[INFO] Archetype created in dir: /Users/Howard/work/myapp
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Thu Oct 25 07:47:04 PDT 2007
[INFO] Final Memory: 5M/9M
[INFO] ------------------------------------------------------------------------
~/work
$ cd myapp
~/work/myapp
$ mvn jetty:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] org.apache.maven.plugins: checking for updates from tapestry-snapshots
[INFO] org.codehaus.mojo: checking for updates from tapestry-snapshots
[INFO] artifact org.apache.maven.plugins:maven-compiler-plugin: checking for updates from tapestry-snapshots
[INFO] artifact org.mortbay.jetty:maven-jetty-plugin: checking for updates from tapestry-snapshots
[INFO] artifact org.apache.maven.plugins:maven-war-plugin: checking for updates from tapestry-snapshots
[INFO] ----------------------------------------------------------------------------
[INFO] Building myapp Tapestry 5 Application
[INFO]    task-segment: [jetty:run]
[INFO] ----------------------------------------------------------------------------
[INFO] Preparing jetty:run
[INFO] artifact org.apache.maven.plugins:maven-resources-plugin: checking for updates from tapestry-snapshots
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.pom
3K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-project/5.0.6/tapestry-project-5.0.6.pom
12K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.pom
3K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.pom
1K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-repository/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-snapshot-repository/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-ioc/5.0.6/tapestry-ioc-5.0.6.jar
267K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-core/5.0.6/tapestry-core-5.0.6.jar
952K downloaded
Downloading: http://tapestry.formos.com/maven-snapshot-repository//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://snapshots.repository.codehaus.org/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://maven.openqa.org//org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-repository/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://tapestry.formos.com/maven-snapshot-repository/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
Downloading: http://repo1.maven.org/maven2/org/apache/tapestry/tapestry-annotations/5.0.6/tapestry-annotations-5.0.6.jar
4K downloaded
[INFO] [compiler:compile]
[INFO] Compiling 2 source files to /Users/Howard/work/myapp/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: myapp Tapestry 5 Application
[INFO] Webapp source directory = /Users/Howard/work/myapp/src/main/webapp
[INFO] web.xml file = /Users/Howard/work/myapp/src/main/webapp/WEB-INF/web.xml
[INFO] Classes = /Users/Howard/work/myapp/target/classes
2007-10-25 07:47:44.872::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /myapp
[INFO] Tmp directory = /Users/Howard/work/myapp/target/work
[INFO] Web defaults =  jetty default
[INFO] Web overrides =  none
[INFO] Webapp directory = /Users/Howard/work/myapp/src/main/webapp
[INFO] Starting jetty 6.1H.5-beta ...
2007-10-25 07:47:44.011::INFO:  jetty-6.1H.5-beta
2007-10-25 07:47:44.189::INFO:  No Transaction manager found - if your webapp requires one, please configure one.
[INFO] TapestryFilter Startup time: 189 ms to build IoC Registry, 587 ms overall.
2007-10-25 07:47:45.282::INFO:  Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:04 GMT] "GET / HTTP/1.1" 404 765 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:07 GMT] "GET /myapp HTTP/1.1" 302 0 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 459 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:07 GMT] "GET /myapp/ HTTP/1.1" 200 519 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 3 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:08 GMT] "GET /myapp/assets/tapestry/default.css HTTP/1.1" 200 4916 "http://localhost:8080/myapp/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 2 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:12 GMT] "GET /myapp/start HTTP/1.1" 200 519 "http://localhost:8080/myapp/" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"
[INFO] TimingFilter Request time: 4 ms
127.0.0.1 -  -  [Thu, 25 Oct 2007 14:48:12 GMT] "GET /myapp/assets/tapestry/default.css HTTP/1.1" 304 0 "http://localhost:8080/myapp/start" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8"

That's it! Jetty is running, and the application is available at http://localhost:8080/myapp. And of course, I have a Ruby script to make setup even easier.

Tapestry 5.0.6 is available

Yet another preview release, Tapestry 5.0.6 adds a raft of new features , including:

A new DateField component (using a client-side JavaScript calendar).

The Grid component can now be utilized inside a Form.

The BeanEditForm component has been refactored, allowing you to create complex forms with multiple BeanEditors.

There is now a BeanDisplay component, a counterpart to BeanEditor that displays the properties of a bean.

For early adopters upgrading from release 5.0.5, you should be aware that Tapestry template files now have a .tml extension, and are stored in the context root, not under WEB-INF . In addition, the @Inject annotation in tapestry-core has been removed, and the @Inject annotation from tapestry-ioc is now doing double-duty.

In addition, Tapestry 5 now uses SLF4j as its pluggable logging library, replacing commons-logging. This may require that you upgrade to Log4j 1.2.14.

Download Tapestry 5.0.6

Sunday, October 14, 2007

Tapestry 5.0.6 soon

I'm very much hoping to have Tapestry 5.0.6 out soon. I've been squeezing in some more time to work on Tapestry 5 and just added a BeanDisplay component (to complement the BeanEditor component).

The Ajax story for Tapestry 5 is still up in the air ... the specter of writing an abstraction layer has come back. And I still want better, smarter handling of Hibernate ... it seems to me that the BeanEditor should be capable of handling to-one relationships in a smart way. And Spring WebFlow integration would be a snap if I just had a couple of days to work on it.

I'm personally anxious to get Tapestry 5 solid and shipped ... but not before it's ready.

Monday, October 01, 2007

TheDailyTube: Powered by Tapestry 5

A posting on the Tapestry mailing list led me to TheDailyTube, a video sharing site powered by ... Tapestry 5. It may be "alpha", but people are putting their faith in Tapestry 5.

The site looks sweet and is plenty responsive. Can't really tell too much else about the site though ... they've turned off the default exception page. Still, this is the kind of high volume, outward facing site that I'm proud to have helped facilitate. Viva Tapestry 5!

Friday, September 28, 2007

One of the greatest Worse-Than-Failures, ever ...

Serializalicious, now on WTF is just one example Steve, Ben and I discovered in this application. The article is somewhat misleading, it makes it look like Steve put that code in there. Nope, Steve, Ben and I were all victims of the missing Architect. Funny, scary stuff.

Wednesday, September 26, 2007

Let there be Nightly Docs!

With just a little bit of tweaking, we now have full, nightly documentation for Tapestry 5. This is great news, as the public web site can start staying static between releases (thus all the docs there will reflect just the latest stable release), and the snapshots and documentation on tapestry.formos.com will also be in sync, reflecting what's currently going on in SVN trunk.

That should ease a lot of confusion, when the docs say something exists but it doesn't in the stable (non-snapshot) versions.

Tuesday, September 25, 2007

Dr. Seuss visits the Tapestry Mailing List

Just noticed this posting by Bill Holloway:

Will you work with JSP?

They mess up my MVC.
I will not work with JSP.

We'll put them in an IDE.

I don't want them in an IDE.
They screw up the MVC.
I will not work with JSP.

We'll put them down inside some struts.
You'll never see them,
In the guts.

You cannot hide them in the guts!
They're in your face when they're in struts!
I don't want them on my IDE.
They screw up our MVC!
I will not work with JSP.

We'll put them in some server faces.
Many use them, many places.
That way the MVC's ok.
Will you work with them today?

The XML of server faces
Bogs down the coders in those places!
You cannot hide them in the guts.
I will not work with them in struts.
Get them off my CRT!
They're $@%#*&ing up my MVC!
Give me back my Tapestry.

Tapestry 5 Nightly Builds

I've finally gotten around to setting up nightly builds for Tapestry 5. It just another build on the Bamboo server. You can get access to it by adding:

  <repositories>
    <repository>
      <id>tapestry-snapshot</id>
      <url>http://tapestry.formos.com/maven-snapshot-repository</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
to your POM.

Eventually, we'll be publishing documentation every night too, but that's waiting on a Maven bug.

Saturday, September 15, 2007

Mac OS X bundles vs. Subversion

If you work on Mac OS X, you may have noticed how cool Macs deal with complex documents, things like Keynote presentations or applications themselves. They're stored as directories. The Finder hides this, making them look and act like individual files. This works nicely, often the contents of a bundle are simple text and XML files ... whereas the equivalent under Windows is either a very proprietary (and potentially fragile) binary format, or multiple files and folders that YOU have to treat as a unit.

Alas, this all breaks down when using Subversion. You can't just check in MyPresentation.key into SVN ... it will create those pesky .svn directories inside the bundle, and those will be destroyed every time you save your presentation.

My solution to this is to convert the bundles into an archive, and check in the archive. The bundle folders are marked as svn:ignore. I guess this reveals that I mostly use SVN as a safe, structured backup.

In any case, manually creating those archives can be a pain. So ... out comes my solution to many problems: Ruby.

The goal here is to find bundles that need to be archived; do it efficiently (only update the archive if necessary) and do it recursively, seeking out bundles in sub-directories.

#!/usr/bin/ruby

# Used to prepare a directory for commit to Subversion. This is necessary for certain file types on Mac OS X because what appear to be files in the Finder
# are actually directories (Mac uses the term "bundle" for this concept). It is useless to put the .svn folder inside such a directory, because it will
# tend to be deleted whenever the "file" is saved.  
#
# Instead, we want to compress the directory to a single archive file; the bundle will be marked as svn:ignore.
#
# We use tar with Bzip2 compression, which is resource intensive to create, but 
# compresses much better than GZip or PKZip.
#
# The trick is that we only want to create the acrhive version when necessary; when 
# the archive does not exist, or when any file
# in the bundle is newer than the archive.

require 'optparse'

# Set via command line options

$extensions = %w{pages key oo3 graffle}
$recursive = true
$dry_run = false

# Queue of folders to search (for bundles)

$queue = []

def matching_extension(name)
  dotx = name.rindex('.')
  
  return false unless dotx
  
  ext = name[dotx + 1 .. -1]
  
  return $extensions.include?(ext)
end


# Iterate over the directory, identify bundles that may need to be compressed and (if recursive) subdirectories
# to search.
#
# path: string path for a directory
def search_directory(dirpath)
  
  Dir.foreach(dirpath) do |name|
    
    # Skip hidden files and directories
    next if name[0..0] == "."
    
    path = File.join(dirpath, name)
        
    next unless File.directory?(path)
                  
    if matching_extension(name)
      update_archive path
      next
    end
    
    if $recursive
      $queue << path
    end
    
  end
  
end


def needs_update(bundle_path, archive_path)
  
  return true unless File.exists?(archive_path)
  
  archive_mtime = File.mtime(archive_path)
  
  # The archive exists ... can we find a file inside the bundle thats newer?
  # This won't catch deletions, but that's ok.  Bundles tend to get completly
  # overwritten when any tiny thing changes.
  
  dirqueue = [bundle_path]

  until dirqueue.empty?
    
    dirpath = dirqueue.pop
    
    Dir.foreach(dirpath) do |name|
      
      path = File.join(dirpath, name)
      
      if File.directory?(path)
        dirqueue << path unless [".", ".."].include?(name)
        next
      end
      
      # Is this file newer?
      
      if File.mtime(path) > archive_mtime
        return true
      end
      
    end
    
  end
  
  return false
end

def update_archive(path)
  archive = path + ".tar.bz2"
  
  return unless needs_update(path, archive)

  if $dry_run
    puts "Would create #{archive}"
    return
  end

  puts "Creating #{archive}"
    
  dir = File.dirname(path)
  bundle = File.basename(path)
    
  # Could probably fork and do it in a subshell
  system "tar --create --file=#{archive} --bzip2 --directory=#{dir} #{bundle}"

end

$opts = OptionParser.new do |opts|
  
  opts.banner = "Usage: prepsvn [options]"

  opts.on("-d", "--dir DIR", "Add directory to search (if no directory specify, current directory is searched)") do |value|
    $queue << value
  end

  opts.on("-e", "--extension EXTENSION", "Add another extension to match when searching for bundles to archive") do |value|
    $extensions << value
  end
  
  opts.on("-N", "--non-recursive", "Do not search non-bundle sub directories for files to archive") do
    $recursive = false
  end
  
  opts.on("-D", "--dry-run", "Identify what archives would be created") do
    $dry_run = true
  end
  
  opts.on("-h", "--help", "Help for this command") do
    puts opts
    exit
  end
end

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

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

# If no --dir specified, use the current directory.

if $queue.empty?
  $queue << Dir.getwd
end

until $queue.empty? 
  search_directory $queue.pop
end

I do love Ruby syntax, it is so minimal, and lets me follow my personal mantra less is more.

I'm sure there's some edge cases that aren't handle well, such as spaces in path names and maybe issues related to permissions. But it works for me.

You do need to have tar installed, in order to build the archives. I can't remember if that's built in to Mac OS X (probably) or whether I obtained it using Fink.

In any case, you need to remember to execute prepsvn in your workspace, to spot file bundles that need archiving, before you check in. It would be awesome if Subversion supported some client-side check-in hooks to do this automatically.

Saturday, September 08, 2007

Itch scratching: Even better feedback for all thumbs typists

I was reading through Matt's rundown of Struts 2 (does it suck?) and he strayed into one of my most passionate areas: feedback.

He gave an example of an incorrect property name, and how that would be reported to the user. He showed examples from all the major frameworks, and the Tapestry 4 version, even without its proper CSS styles, won hands-down.

However, as I was reading and responding, it struck me that while other framework can barely tell you what you've done wrong, Tapestry 5 should be telling you how to fix it. In this case, advising you about the possible properties you could have specified, which I've added as TAPESTRY-1737 and fixed.

Here's the result. Not bad for ten minutes work. And remember: property names in Tapestry 5 are already case insensitive, which wipes out probably 50% of your typical typing errors anyway.

Thursday, September 06, 2007

Biled!

Well, Hani has finally Biled the Bearded One. But what a cheap and meaningless shot ... he's upset about a switch from commons-logging to SLF4J? What is it with Hani and commons-logging? Did commons-logging steal his prom date? Did it kick sand in his face at the beach? Maybe it touched him inappropriately as a child. In any case, he's strangely obsessive about anything that touches on the subject.

On a serious note ... people have come out of the woodwork to defend the use of JDK logging, especially inside Websphere (God help them), which does justify a pluggable approach. Again, Log4J does not quite fit all, and because it is based on classes, not interfaces, it gets in the way of mock testing.

Friday, August 31, 2007

The Blindness of James Gosling: Java as A First Language

I think Java is an excellent all-around-language. It is truly ubiqitous, well specified, highly performant and well accepted by the industry.

But there's a big difference between those credentials, and the credentials of the first language a developer learns. Regardless, James Gosling feels obligated to recommend Java as the first language anyone learns.

Java as a first language? Please! Yes, Java is simpler than C++, C, Lisp, assembler and all the languages that seasoned veterans, such as James Gosling, are familiar with. But the fact that Java shields you from pointers, malloc() and zero-terminated strings does not make it a good first language!

Java is extremely monolithic: in order to understand how to run a simple Hello World program, you'll be exposed to:

  • Classes
  • Java packages
  • Static vs. instance methods
  • Source files vs. compiled classes
  • Editing vs. Execution
  • Using a compiler or an IDE
  • Method return types and method parameter types
  • The magic (for newbies) that is "System.out.println()"

... in fact, this list is far from exhaustive. A lot of that has fallen off our collective radar ... we're blind to it from seeing it so much over the last ten years or more.

What's important to understand is that people new to programming don't really have a way of understanding the difference between a document and a program, between source code and an application. Certainly the web (with HTML and JavaScript all mixed together) hasn't helped people understand the division intuitively. It's very hard for any of us to think like someone new to our entire world.

I'm rarely in a position to teach people how to program, but when I think about it, only two languages make sense to me: Ruby and Inform.

Ruby, because it is interactive yet fully object oriented. You can start with an interactive puts "Hello World" and quickly work up from there. You get to interact with the Ruby world as objects before you have to define your own objects. There's a nice clean slope from one-off quickies to eventual discipline as a true developer, without any huge leaps in the middle.

Inform, used to create interactive text adventures, is also intriguing. It's specifically designed for non-programmers, and has a laser-focused UI. It is interactive: you type a little bit about your world and hit Run to play it. Again, you experience an object oriented environment in an incredibly intuitive way long before you have to define to yourself, or to the language, what an object is.

Inform is truly some amazing software, given that advanced Inform games will make use of not just object oriented features, but also aspect oriented. Here's a little example I brewed up about a room built on top of a powerful magnet:

"Magnet Room" by Howard Lewis Ship

A thing is either magnetic or non-ferrous. Things are normally non-ferrous.

The Test Lab is a room. "A great circular space with lit by a vast array of ugly
flourescent lights.  In the center of the room is a white circular pedestal with a
great red switch on top. Your feet echo against the cold steel floor."

The red switch is a device in the test lab. It is scenery.

The double-sided coin is in the test lab. "Your lucky double sided half dollar rests
on the floor." The coin is magnetic.  Understand "dollar" as the coin.

A rabbits foot is in the test lab. "Your moth-eaten rabbits foot lies nearby."

Magnet Active is a scene.

Magnet Active begins when the red switch is switched on. 

When Magnet Active begins: say "A menacing hum begins to surge from beneath the
floor."

Magnet Active ends when the red switch is switched off.

When Magnet Active ends: say "The menacing hum fades off to nothing."

Instead of taking a thing that is magnetic during Magnet Active: say "[The noun]
is firmly fixed to the floor."

That's not a description of my program; that's the actual program. It's literate; meaning equally readable, more or less, by the compiler and the author. It's still a formal language with all that wonderful lexical and BNF stuff, it's just a bit richer than a typical programming language.

What I like about Inform is that you get a complete little game from this:

Magnet Room
An Interactive Fiction by Howard Lewis Ship
Release 1 / Serial number 070831 / Inform 7 build 4W37 (I6/v6.31 lib 6/11N) SD

Test Lab
A great circular space with lit by a vast array of ugly flourescent lights.  In the
center of the room is a white circular pedestal with a great red switch on top. Your
feet echo against the cold steel floor.

Your lucky double sided half dollar rests on the floor.

Your moth-eaten rabbits foot lies nearby.

>take dollar
Taken.

>drop it
Dropped.

>turn switch on
You switch the red switch on.

A menacing hum begins to surge from beneath the floor.

>take dollar
The double-sided coin is firmly fixed to the floor.

>take foot
Taken.

>turn switch off
You switch the red switch off.

The menacing hum fades off to nothing.

>take dollar
Taken.

>

The way rules work, especially in the context of scenes, is very much an aspect oriented approach. It is a pattern-based way to apply similar rules to a variety of objects. I've seen this kind of thing with aspect oriented programming in Java, but also with pattern based languages such as Haskell and Erlang.

The point is, using Ruby or Inform, you can learn the practices of a programmer ... dividing a complex problem into small manageable pieces, without being faced with a huge amount of a-priori knowledge and experience in order to get started. Both Ruby and Inform are self-contained environments designed for quick and easy adoption. That's something Java missed the boat on long, long ago!

Wednesday, August 29, 2007

Handling direct URLs in Tapestry 5

So, I'm starting work on the 1% web portion of my otherwise 99% Swing project. It's about sending invoices by email and allowing external users to view invoice PDFs, and accept or reject them.

I wanted to get a tracer bullet going; the process is kicked off when some COBOL code, via typical Rube Golderberg shenanigans, GETs a URL of the form /sendinvoice/invoice-number/email-address.

Well, in Tapestry terms, that could be a page with an activate event handler method:

package com.myclient.pages;

import org.apache.tapestry.util.TextStreamResponse;

public class SendInvoice 
{

    Object onActivate(String invoiceNumber, String emailAddress) 
    {

        // TODO: Generate and send the email
        
        return new TextStreamResponse("text/plain", 
          String.format("OK %s / %s", invoiceNumber, emailAddress));
    }
}
Returning a StreamResponse (that's the interface, TextStreamResponse is a simple implementation) allows your code to bypass the normal HTML response with one you control. This kind of thing was way too complicated to do in Tapestry 4 so I'm very happy with how minimal the Tapestry 5 approach is.

I fired this up and it worked the first time, echoing back to me the information I passed in the URL. If you don't provide at least two values in the URL, Tapestry will bypass this activate event handler method and will just render out the page; the page template simply announces to the user that they munged up the URL in some way.

This is something that would be pretty easy as a servlet, but is even easier in Tapestry. Just create the SendInvoice page and provide the onActivate() method. onActivate() gets called based on the number of path elements provided in the URL. When there are two (or more), this method gets invoked with the first two. Tapestry can automatically coerce from string to other types, but here both values happen to be strings anyway.

Obviously, much more to do (under deadline ... I don't really have time to blog this). I'm just happy to be doing a little Tapestry on my day job

Sunday, August 26, 2007

So long, commons-logging, hello SLF4J

I'm finally taking a bit to do something I've wanted to do for a long time: get away from commons-logging and switch over to SLF4J.

Commons-logging has a justified reputation for causing more problems than it solves. It's basic premise, that you should need to be able to switch between logging implementations, is very appealing to framework developers. Who am I to mandate that you use Log4J in your application? Sure, I mandate things like Javassist and a bunch of other dependencies, but for some reason, Log4J is too much. I'm not sure where this mindthink came from, but it is prevalent among framework folks.

Anyway, it's not a bad idea, though 99.99999% of applications use commons-logging as a wrapper around Log4J. I haven't yet met someone who uses JDK logging, or anything home brew. Why would you?

It's the implementation of commons-logging that the problem. First off, it called a "logger" a "log". I think that's just wrong, and (in fact) always preferred the term "category" (now deprecated by Log4J). To my mind, the category or logger is what you logged to, the log itself is the console, or a file, or something that receives the formatted output.

Also, commons-logging's ultra-late binding approach always causes class loader frustration and memory leaks. Every time they claim to get that fixed up, some new variation pops up.

The non-Apache follow on to commons-logging is SLF4J. It does things right: Logger, not Log. Better yet, Logger is an interface (it's a class in Log4J) which makes my EasyMock-addled brain quite happy. Finally, SLF4J takes a very simple, very sane approach: you combine the API JAR with one of several implementation JARs. That's it ... no class loader magic, no dynamic binding. This is an ideal solution for those same 99.99999% of applications out there.

This may cause some minor pain for Tapestry 5 early adopters, as they'll have to switch their (JCL) Log parameters to (SLF4J) Logger. Sorry, T5 is alpha ... but not for long, and this is a kind of last opportunity to make such a large change.

Update: Dion's been laughing from the sidelines but he's missing the point. He's put up a smokescreen that boils down to "just use System.out.println". But that's not what a logging framework is about ... it's about filtering, and organizing, a little bit about formatting, but mostly about being able to control what does log and what doesn't without hacking a lot of code (instead just tweaking a couple of control files).

Friday, August 24, 2007

Maven: Love to Hate or Hate to Love?

I've been struggling hard with Maven this week, and its really be bringing me down. I love the theory of Maven, but the execution is so awful I'm very close to eating crow and switching back to a Ant build, maybe using Ivy.

One of the things that concerns me with Maven is that for a tool whose goal is to be a "project comprehension tool" (giving quick access to Java source, Javadoc and other reports) it's almost impossible to find the source or get any insight into how Maven is built. Yet so much about Maven is impossible to find, with broken links, missing and out of date documentation, and other hazards running rampant.

I've been hunting around for 30+ minutes, trying to find the source to MavenCli. It's in the maven-core library. Good luck finding a link to that off the Maven web site. Through arcane means and Google searches I eventually stumbled across http://maven.apache.org/ref/ but 2.0.7 is not present there. So they rolled out a release without external documentation? I guess that's OK when you need a magical incantation to even find the documentation.

Well, I'm getting closer to my answers, but I'm probably still shiv'ed because Maven developer guidelines mandate zero documentation in the source. Or anywhere else. In fact, I believe the maven-fuckyou-plugin exists to strip documentation out of your source and seems to be enabled by default in all the Maven modules.

Well, eventually I answered my own question: -Dmaven.repo.local=directory allows me to control where the local repository exists. This is for my Bamboo server that needs to build multiple branches that share the same version numbers, so I don't want them to use the same repository.

Anyway, that's what it should be (from the tiny fraction of the code that's visible). But it blows up real good, apparently unwilling to download files into this repository, and spewing errors that make it appear as if artifacts were not present in the remote repository.

So, when Maven is useful it is very useful, but when it gets in your way, it totally blocks you.

Thursday, August 23, 2007

Quick and dirty Java application launcher

I've been busy working at a new company, for a new client, helping out on an existing product. The product is a Swing application and so I ran into my old nemesis: the ugly, buggy, hand maintained script to launch the application. You know the beast: start.bat and start.sh that you dread having to maintain.

We have a nifty, Maven based build, and a continuously evolving set of dependencies. Nobody wants to have to maintain that in two or three additional locations.

I've been seeing these kinds of scripts for years and hating them all along. Mostly, these scripts exist to find a bunch of JARs, put together a classpath (often by creating a monster CLASSPATH environment variable), then run Java to launch a particular class.

Previously, I've pushed to use Ant to do this. Ant is great a searching directories, building up classpaths, and invoking Java programs. It's pretty much inherently cross-platform. Who cares if it says "BUILD SUCCESSFUL" at the end?

Sun's tried to tackle this before, with the typical half-assed, lame results. Executable JAR files, Java Web Start, etc. Always stuff that looked good on some engineer's workstation, but never made sense in terms of a real development and deployment scenario. Ten years of Java later and it's still too much work to write a Java application to do a simple job. Meanwhile, Ruby and Groovy are so easy to run, it's painful (or laughable, or both).

JDK 1.6 has a new option to search a directory and pick up the JARs it finds. But we're targetting JDK 1.5 for both the client and the server. Thus my little launcher utility:

package org.example.launcher;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

/**
 * A wrapper used to assemble the classpath before launching the actual
 * application. The big trick is assembling the classpath before the launch.
 * Yep, this is functionality that should be built right into Java.
 * 
 * <p>
 * This is packaged into a JAR with no dependencies.
 * 
 */
public final class LauncherMain {

  private static final List<URL> _classpath = new ArrayList<URL>();

  /**
   * Usage:
   * 
   * <pre>
   * java -jar mblauncher.jar some.class.to.launch [--addclasspath dir] [--addjardir dir] [options]
   * </pre>
   * 
   * <p>
   * The --addclasspath parameter is used to add a directory to add to the
   * classpath. This is most commonly used with a directory containing
   * configuration files that override corresponding files stored inside other
   * JARs on the classpath.
   * 
   * <p>
   * The --addjardir parameter is used to define directories. JAR files
   * directly within such directories will be added to the search path. 
     * 
     * <p>Any
   * remaining options will be collected and passed to the main() method of
   * the launch class.
   */
  public static void main(String[] args) {

    List<String> launchOptions = new ArrayList<String>();

    if (args.length == 0)
      fail("No class to launch was specified.  This should be the first parameter.");

    String launchClass = args[0];

    int cursor = 1;

    while (cursor < args.length) {

      String arg = args[cursor];

      if (arg.equals("--addclasspath")) {
        if (cursor + 1 == args.length)
          fail("--addclasspath argument was not followed by the name of the directory to add to the classpath.");

        String dir = args[cursor + 1];

        add(dir);

        cursor += 2;
        continue;        
      }

      if (arg.equals("--addjardir")) {

        if (cursor + 1 == args.length) {
          fail("--addjardir argument was not followed by the name of a directory to search for JARs.");
        }

        String dir = args[cursor + 1];

        search(dir);

        cursor += 2;
        continue;
      }

      launchOptions.add(arg);

      cursor++;
    }

    String[] newArgs = launchOptions.toArray(new String[launchOptions
        .size()]);

    launch(launchClass, newArgs);
  }

  private static void launch(String launchClassName, String[] args) {

    URL[] classpathURLs = _classpath.toArray(new URL[_classpath.size()]);

    try {
      URLClassLoader newLoader = new URLClassLoader(classpathURLs, Thread
          .currentThread().getContextClassLoader());

      Thread.currentThread().setContextClassLoader(newLoader);

      Class launchClass = newLoader.loadClass(launchClassName);

      Method main = launchClass.getMethod("main",
          new Class[] { String[].class });

      main.invoke(null, new Object[] { args });
    } catch (ClassNotFoundException ex) {
      fail(String.format("Class '%s' not found.", launchClassName));
    } catch (NoSuchMethodException ex) {
      fail(String.format("Class '%s' does not contain a main() method.",
          launchClassName));
    } catch (Exception ex) {
      fail(String.format("Error invoking method main() of %s: %s",
          launchClassName, ex.toString()));
    }
  }

  private static void add(String directoryName) {
    File dir = toDir(directoryName);

    if (dir == null)
      return;

    addToClasspath(dir);
  }

  private static File toDir(String directoryName) {
    File dir = new File(directoryName);

    if (!dir.exists()) {
      System.err.printf("Warning: directory '%s' does not exist.\n",
          directoryName);
      return null;
    }

    if (!dir.isDirectory()) {
      System.err.printf("Warning: '%s' is a file, not a directory.\n",
          directoryName);
      return null;
    }

    return dir;
  }

  private static void search(String directoryName) {

    File dir = toDir(directoryName);

    if (dir == null)
      return;

    File[] jars = dir.listFiles(new FilenameFilter() {

      public boolean accept(File dir, String name) {
        return name.endsWith(".jar");
      }

    });

    for (File jar : jars) {
      addToClasspath(jar);
    }

  }

  private static void addToClasspath(File jar) {
    URL url = toURL(jar);

    if (url != null)
      _classpath.add(url);
  }

  private static URL toURL(File file) {
    try {
      return file.toURL();
    } catch (MalformedURLException ex) {
      System.err.printf("Error converting %s to a URL: %s\n", file, ex
          .getMessage());

      return null;
    }
  }

  private static void fail(String message) {
    System.err.println("Launcher failure: " + message);

    System.exit(-1);
  }

}
This gets packaged up as an executable JAR (i.e., with a MainClass manifest entry pointing to this class). Launching ends up looking like.
java -jar launcher.jar --addjardir lib org.example.MyMain --my-main-opt
Basically, the -jar launcher.jar and its options (--addjardir) replaces any other classpath manipulations. Once the classpath is setup, launcher emulates Java in terms of locating MyMain and executing its main() method, passing any remaining options.

Named mocks in EasyMock 2.3

This is a great new feature of EasyMock 2.3: you can name your mocks as you create them.

This is wonderful news, I can't wait to try it out. Why? Because once you get going with half a dozen different mocks that implement the same interface, it's a lot of work to figure out which one actually failed. This is going to make creating and maintaining some of my more complex tests much easier.

Friday, July 27, 2007

Tapestry 5 Preview at OSCON 2007

Yesterday was my session at OSCON. 45 minutes to covert Tapestry 5? Not a hope, especially with Rod Johnson running a little long.

I got the typical stunned reaction, followed by "is it done yet? Can I use it?" People crave T5 once they see it, and the presentation I do barely scratches the surface of what it can do today.

I've converted the presentation to PDF, which is great considering I had to flash through a lot of stuff about the grid very quickly.

Saturday, June 30, 2007

Upgrading Maven 2.0.5 to 2.0.7

I've been using Maven a bit for my "day job", converting a multi-module Ant build over to Maven. Since I just pushed out Tapestry 5.0.5 (awaiting a release vote), it seemed like a good time to look at upgrading to Maven 2.0.7.

The "day job" work has shown that having a traditionally stuctured aggregate project is viable even when using Eclipse. The Maven plugin for Eclipse is smart enough to read the parent POM as well as the child POM's in each module when building the overall classpath. I expect to restucture Tapestry 5 this way pretty soon, which will make it even easier to checkout and build the code than today. This structure, as a single mega-project, will make things easier when refactoring across module boundaries (a pain to do today, when using multiple Eclipse projects). However, the command line build will be very important, as it will catch some dependency issues that the Eclipse build will not.

Speaking of which ... Maven 2.0.7 (and 2.0.6 I believe) has tweaked dependencies. I had a couple of "runtime" scope dependencies inherited from tapestry-ioc to tapestry-core. These compiled fine in 2.0.5 but need to be changed in 2.0.7 to compile. In my case, since javassist was a "runtime" scope in tapestry-ioc, none of the javassist classes were visible when compiling tapestry-core.

A handy tool inside Maven for diagnosing this is the dependency plugin:

$ mvn dependency:resolve
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] org.apache.maven.plugins: checking for updates from tapestry
[INFO] org.apache.maven.plugins: checking for updates from howardlewisship.com
[INFO] org.apache.maven.plugins: checking for updates from codehaus.snapshots
[INFO] org.codehaus.mojo: checking for updates from tapestry
[INFO] org.codehaus.mojo: checking for updates from howardlewisship.com
[INFO] org.codehaus.mojo: checking for updates from codehaus.snapshots
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from tapestry
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from howardlewisship.com
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from codehaus.snapshots
[INFO] ----------------------------------------------------------------------------
[INFO] Building Tapestry Core Library
[INFO]    task-segment: [dependency:resolve]
[INFO] ----------------------------------------------------------------------------
[INFO] [dependency:resolve]
[INFO] 
[INFO] The following files have been resolved: 
[INFO]    commons-codec:commons-codec:jar:1.3 (scope = compile)
[INFO]    commons-logging:commons-logging:jar:1.0.4 (scope = compile)
[INFO]    javax.servlet:servlet-api:jar:2.4 (scope = provided)
[INFO]    jboss:javassist:jar:3.4.ga (scope = runtime)
[INFO]    junit:junit:jar:3.8.1 (scope = provided)
[INFO]    log4j:log4j:jar:1.2.9 (scope = test)
[INFO]    org.apache.tapestry:tapestry-ioc:jar:5.0.5 (scope = compile)
[INFO]    org.apache.tapestry:tapestry-test:jar:5.0.5 (scope = provided)
[INFO]    org.easymock:easymock:jar:2.2 (scope = provided)
[INFO]    org.openqa.selenium.client-drivers:selenium-java-client-driver:jar:0.8.1 (scope = provided)
[INFO]    org.openqa.selenium.server:selenium-server:jar:0.8.1 (scope = provided)
[INFO]    org.testng:testng:jar:jdk15:5.1 (scope = provided)
[INFO] 
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Sat Jun 30 00:20:31 PDT 2007
[INFO] Final Memory: 4M/9M
[INFO] ------------------------------------------------------------------------
~/workspace/tapestry-core
$

Wednesday, June 06, 2007

Talking in Boulder and Denver

I'm doing back-to-back JUG talks next week:

Jun 13: Denver JUG

Jun 14: Boulder JUG

I'll be doing the same talk, a compressed introduction to Tapestry 5.

Wednesday, May 16, 2007

Free and Excellent Code Coverage for Eclipse

The EMMA plugin for Eclipse is my latest addition to the "can't live without it" category. It allows you to run applications and test suites from within Eclipse and gather code coverage ... better yet, that code coverage data is shown visibly in your code, much as it is in an HTML Cobertura report.

The plugin is slick, fast, easy and non-intrusive.

The one thing you do need to do is split your output directories, so that production code goes into bin, and test code goes into test-bin; this allows you to turn off coverage information for your test classes, and just gather

Of course, IDEA also has code coverage, based on EMMA, built right in.

Tuesday, May 15, 2007

Tapestry at JavaOne 2007

JavaOne was a lot of fun this year; I didn't arrive in time for the JavaFX keynote, but I understood it to be underwhelming. Yep ... that's what Java needs ... to take on Adobe and Microsoft in Adobe's home territory.

On the other hand, this was a very social JavaOne; lots of good conversations and meeting with people I only know online.

I also attended the Java Server Faces 2.0 Expert Group kickoff. I can't really see where that's going to go, alas. It's not like everyone bowed down and said "JSF 2.0 shall be Tapestry" (not even Jacob Hookom, who does see Tapestry as a good model for much of JSF 2.0). But I did get a chance to chat with Gavin King ... about motorcycles. He's riding a Ducati now, and I'm thinking about picking up a new bike once I buy a house. Gavin --- I used to ride a Yamaha FZR1000.

The best part of the JSF meeting was when Jonathan Swartz stopped by. We shook hands and talked about how slow adoption of JCP standards.

Elsewhere ... the fun part about the RubyEnv parody ad ...

... is that Tapestry is jar #3. Interesting, though I guess it's hard to know what "JSF in a Jar" looks like, or "Struts in a Jar" for that matter. My intention is to reverse the meaning, make Tapestry 5 something that'll make the Ruby guys envious. We'll see.

Saturday, May 05, 2007

Tapestry at JavaOne

Not only am I doing my Tapestry 5 "BOF" session (Tapestry 5: Java Programming Language Power, Scripting Ease), but there's also TS-7354 (Fast Feedback Loop: Unit Testing Strategies for Tapestry) and BOF-9834 ( Grails, Sails, and Trails: Rails Through a Coffee Filter).

I'll be arriving Tuesday afternoon and staying through Saturday. JavaOne is always hectic, especially right around my session. However, I'd be glad to meet with people informally; drop me an e-mail at hlship AT gmail DOT com and we'll see.

Meanwhile, did anyone else suffer through Sun's Schedule Builder? Just a horrendous application, made all the worse that it is crying out for the Ajax treatment. Show me the sessions side-by-side, dynamically filter the page by day and time slot and session track, and let me click or drag the sessions I like. For god's sake ... don't make me schedule one sesson at a time and then scroll back to the top of the page!

Thursday, May 03, 2007

Launching IntelliJ for the first time...

First the damn cool crowd of NFJS speakers got me to buy a Mac, now they've suckered me into switching from Eclipse to IntelliJ. God help me, I don't even know where to start with this thing.

Tuesday, April 24, 2007

Duh! Annotation values as constants, not literals

One funny thing I discovered recently is that annotation values don't have to be literal values. You can reference constants. Thus I went from:

@Scope("perthread")
public class .... 

To:

@Scope(IOCConstants.PERTHREAD_SCOPE)
public class ...

I think this is a Good Thing, even though it's a bit more verbose (a static import helps there) because I know at compile time that there isn't a typo in my constant's variable name ... whereas @Scope("prthread") would not be caught until runtime.