Wednesday, May 13, 2009

RAD w/ Tapestry 5, NetBeans 6.7, Maven, and Jetty : Really !!!

One of the major upsides of using Tapestry 5 is the much touted live class and template reloading. Up until recently, if you followed my previous post on working with Tapestry 5 and NetBeans, you probably ended w/ a workable solution, but still not ideal , as the live template and class reloading wasn't exactly working as expected. As a result, whenever you wanted to see the changes that you made in the live app (after running mvn jetty:run) you had to do the following:

mvn compile resources:resources
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Wed May 13 03:16:32 EDT 2009
[INFO] Final Memory: 16M/71M

The issue here was that NetBeans (in 6.1 and prior) did not support CopyOnSave or CompileOnSave properly in Maven projects (it did for NetBeans native projects, so if you had set up a NetBeans native project w/ explicit jar dependencies, etc it would work fine). The effect of running the above command was to compile your changes, and copy the compiled classes and modified resources into your <outputDirectory> (typically target/classes) . So, the 18 seconds above are not exactly something to lose sleep over, but it's still not the same like having the immediate Grails(or Rails)-like immediate feedback loop (that is, "Ctrl-S->Alt-Tab to browser->F5", which is "Save->Switch to Browser->Refresh").

In any case, help is on the way.

In the most recent version of NetBeans (in the 6.7 daily builds ), the issues w/ CopyOnSave support has been fixed (well, almost fixed, see the NetBeans IssuZilla issue), and now it transparently copies your modified resource files to target/[app-name]/WEB-INF/classes. Thus, with just a minor tweak, you can accomplish a Tapestry 5 Nirvana.

  1. First, set up a new Maven project by File->New Project->Select Maven project type. Follow my previous instructions on creating the actual project. Just a side note, for some reason the latest production T5 version (5.1) doesn't show up on the list of available archetypes in NetBeans.

  2. Running the app is easy, the default project comes w/ the Jetty plugin set up, so you can just run "mvn jetty:run" on the console.

  3. Alternatively, map a custom Maven goal in NetBeans by right clicking on the project, going to Custom-Goals and mapping jetty:run . See the screenshots for some extra help

  4. The default project setup comes with an Index page living in the web app context. Now that you ran Jetty, you should be able to just make changes to the template, and see them immediately. The secret here is that Jetty runs by default out of src/main/webapp, so T5 picks up the changes out of the box, no additional support by the IDE is needed.

    The problem here is that if you tried making changes to your page class (e.g., they're not being picked up. Jetty runs from the classes in target/classes. The idea here is that we want to IDE to autocompile the changes, drop them into target/classes and have T5 pick up the new page classes. As mentioned at the beginning of the post, if you just ran the maven build again (e.g. mvn compiler:compile), but we need something better.

    OK, so, go to the project properties, go to the Build-Compile section. In the panel, select from the "Compile on Save" (COS) dropdown the "for both application and test execution".

    The trick to remember here is that this only works for "supported servers" (e.g. I know that at least Tomcat and Glassfish are in that list) where the IDE would compile the new classes, and re-deploy them on the server. Jetty is not one of these supported servers, and in order for the Compile-on-save goodness to work, the IDE needs to know you ran the app so that it can activate COS. Now, although you probably don't want to run the app in Tomcat , go ahead and run the app, select to run it in Tomcat. Now that you ran the app in Tomcat, NetBeans activated COS for this app, and now if you make new changes to your, NetBeans copies out the compiled classes to target/classes, and Jetty picks up the changes. After you run the app, you can just stop Tomcat (and the COS feature will continue working).

  5. This is pretty close to perfect. Trouble is, if you have any page templates under src/main/resources, you're still out of luck, as the resources don't get copied out into target/classes after you do the initial jetty:run. But don't despair, there is just one more step that will get us there.

  6. Add the following to your pom.xml


Now, this is a bit of a hack. Basically, we're telling maven to use the target/[app-name]/WEB-INF/classes to do the initial and any subsequent builds, which is where both the classes from src/main (and whereever else) and src/main/resources end up. The trick here is that this is the same directory that "mvn package" uses, and it is also the same directory that NetBeans uses for the Compile-on-save functionality. Basically, when you make changes to your page template sin src/main/resources (and after you've run your app in Tomcat once), NetBeans continues to compile the classes and copy the modified resources from src/main/resources and drop them into the target/[app-name]/WEB-INF/classes.

Considering that this is indeed a a hack, I filed a patch for NetBeans to properly support this T5 setup in Maven project. However, what got into 6.7 is only the fix to properly copy resources into target/[app-name]/WEB-INF/classes (and not in target/classes). The develoeper on the issue has some other ideas on how this should go, hopefully the full fix will go into the NetBeans version after 6.7. In the meantime, either use this little hack, or I'll probably try to repackage my fix as a standalone plugin to support this out of the box.