Saturday, November 01, 2008

Converting legacy Rails apps to Grails : The Domain

A little story about legacies...
(skip this if you're not interested in hearing a sobbing sentimental story and just want to get to the good stuff)

First a short story about my usage of "legacy" in the title of the post. So, here it is : a few years ago, Rails explodes on the scene. Everybody around you who knows a thing or two about web apps start thinking and asking whether your next app should be in Rails instead of XXX [substitute your framework here]. Nevermind that you'll be writing an "enterprise application" that would most likely need to integrate with the rest of your infrastructure (Java, PHP, whatever), or that the said application might have some performance requirements (e.g. it actually needs to DO something, instead of just pushing a few form feelds from and to the database).

Alright, so, Ruby is cool, Ruby is all the rage. You bring in that intern that seems to be a Rails wizard, he totally blinded you with how he put together an app w/ 3-4 forms in less than an hour. Nobody on your existing team can do that : they want to "think about the problem", "understand what needs to be done", put some thought into how to do it, and only then start writing the code. Not so w/ your superstar intern : he's banging out page after page, form after form, it's glitzy and it's Ajaxy, your heart is about to melt from the love towards your unexpected intern saviour.



Fast forward to a few years later. Your intern is gone, he's onto his next new and exciting gig. Your loyal developers have learned a few tricks from the now "old and crusty" Rails app, you got the next version of your Java web framework and your devs are doing quite better with giving you the "quick forms" when you need them. Your customers, initially raving about how quickly they got their app, are now increasingly annoyed that when they ask for what is seemingly simple feature (e.g. hook into this other database that's not mysql, talk to that 'other app' that's been there for a while) and your estimates are way too high ('cause you have to write all that stuff from scratch). To make things worse, your developers actually popped the hood on the Rails app, and it's a big happy bowl of spaghetti : the controllers have their hands in everything : poking around the database, spitting back dynamic javascript groping the glitzy UI in the most unbelievable places (and btw, your devs don't want to touch it with a ten foot pole). When your company scored that big customer, everybody was enamoured by the cha-ching of the cash register, but nobody thought that all those new users will want to use your intern's app (which btw, turned out to not know much about web apps in general, as Rails was the first thing he learned), and now both new and existing customers are not so happy that it takes longer and longer for the app to service them. On top of that, there are very few people who do understand all the magic that happens under the hood in the Rails app, and there are yet fewer people who know how to scale it to the level you need.



That's the place where the phrase "Legacy Rails" really starts making sense. Sure, there are new releases that promise a little more glitz to your app, the framework is still being actively developed, and nearly everyone out there has heard of Rails by now. But now that you're in this situation, can you really put your job on the line that just this next release will have the promised silver bullet ? Or would it maybe be better to just move the game back into your home court, where you set the rules, your dev team knows the ins and outs of the technology like the back of their hand, it scales well, integrates with EVERYTHING you could imagine ? That's when you really want that little Rails locomotive to let off some steam and disappear into the distance just as quickly as it arrived.


Anyway, I digress :-) Back to what I was talking about : how do you migrate the app to Grails.


Now The Goodies

Below is a sample Rails model class that we'll use to talk about the migration:


class Activity < ActiveRecord::Base
has_many :activity_items
has_many :user_notes
belongs_to :competency_group
belongs_to :course

has_many :activity_item_assets

belongs_to :created_by, :class_name => "User", :foreign_key => "created_by"
belongs_to :updated_by, :class_name => "User", :foreign_key => "updated_by"

validates_presence_of :title, :instruction_text, :competency_group_id
validates_length_of :title, :maximum => AdminType::COURSE_TITLE_LENGTH
validates_uniqueness_of :title, :scope => [:competency_group_id]

end


And the equivalent Grails domain object:



class Activity {

String title, instructionText
Date createdAt, updatedAt


static hasMany = [activityItems : ActivityItem, userNotes : UserNote, activityItemAssets:ActivityItemAsset]
static belongsTo = [competencyGroup:CompetencyGroup, course : Course, createdBy : User, updatedBy : User]

static constraints = {
title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])
instructionText(nullable:false, blank:false)
createdAt(nullable:true)
updatedAt(nullable:true)
}

static mapping = {
table 'activities'
createdBy column:'created_by'
updatedBy column:'updated_by'
version false
}

}



1. Location

Both Rails and Grails have a specific place where you can keep your domain objects. In Rails, you keep it in the app/models directory (the Models directory in your NetBeans project), whereas in Grails it's in grails-app/domain directory (the Domain Classes folder in the NetBeans project).

2. Purpose

In both cases, the purpose of the domain objects represent the most important concepts in your application. Additionally, they typically are "persistence capable" (e.g. you can persist an instance w/ a single call), and they provide for a fairly simple specification of relationships w/ other domain objects, as well as allow for specifying validation rules.




NetBeans provides fairly basic support for creating the domain objects : you get a little wizard that asks for the name of the domain object and it creates the Groovy class for you. One of the cool things about how NetBeans handles Grails is that it doesn't create any new metadata (e.g. there's no project directory created), and because the NetBeans project system is based on Ant, the NetBeans project simply delegates the creation of the domain class to the Grails Ant scripts.

Note that when you're looking at the differences between the Grails and the Rails classes, you will notice that (by design), the Grails class is much more focused on the domain, whereas the Rails class is much closer to the database. Thus, for example, you will notice that in the last line of the Rails validation, it references the "competency_group_id". I would imagine this is where my lack of knowledge of the Rails CoC (convention over configuration) bit me in the back, but in a number of places (that I'll mention), the Rails code is allowed to reference "assumed" column names (based on the CoC), which is generally confusing, and also seems to be very refactoring unfriendly (e.g. what if at some point in life, I needed to have a slightly different colum name - would I have to hunt down all references to that column)???

3. Class properties & Relationships


class Activity {

String title, instructionText
Date createdAt, updatedAt


static hasMany = [activityItems : ActivityItem, userNotes : UserNote, activityItemAssets:ActivityItemAsset]
static belongsTo = [competencyGroup:CompetencyGroup, course : Course, createdBy : User, updatedBy : User]

}


Migrating the class properties & relationships is pretty straightforward. For each "simple" property in the Rails class, you can declare a corresponding one in the Groovy class. Declaring the equivalent relationships in the Grails class is also quite straightforward, as the relationship names are pretty much the same. Although there is nothing in Groovy that prevents you from using the Rails naming conventions for properties (e.g. user_name), the Groovy convention is very much like in Java - CamelCase.

This is probably due to my Java background, but I found the ability to declare a class member variable anywhere in the Ruby class much less readable than the equivalent Groovy class. What I mean is that, in the example above, the ":title" property is not mentioned anywhere before the validation constraint. Thus, in order to figure out the properties, you need to examine not only the relationships but also all validation constraints. Although in the Groovy class, the declaration of the relationships similarly defines properties in the class, but at least in validation, mapping, etc. you definitely need to refer to properties that are declared somewhere. I guess this probably comes down to preference, but in my opinion, looking at the Grails class, I can see all the available properties at a glance.

In a very similar manner, I find that even the relationship declarations are very much more readable in Grails. One glance, and I can recognize all relationship types (e.g. one to one, many-to-one, whatever) and the properties corresponding to those relationships. In contrast, although the same can be accomplished in the Rails declarations (e.g. if you specify all has_one mentions one after another), not all Rails model classes that I had to look at followed such a convention.

4. Validation

static constraints = {
title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])
instructionText(nullable:false, blank:false)
createdAt(nullable:true)
updatedAt(nullable:true)
}

Once again, migrating the Rails validations is pretty straightforward, although not all Rails validations had a 1:1 translation in Grails. This is where the validation section of the Grails manual came in very handy, expecially during the first steps in the conversion when I wasn't really sure how to convert from one constraint in Rails to its Grails counterpart.

One thing to notice that is subtle but different between the Grails and Rails validations. In Grails, if a class property is not explicitly declared as "nullable", it is by default required. On quite a few occasions during the conversion, after initially migrating the explicit Rails constraints, I found myself going back to the domain class in order to make some of the Grails domain class fields optional.

One final point on validation is the title uniqueness constraint. In Rails it looks like :

validates_uniqueness_of :title, :scope => [:competency_group_id]


And in Grails it is :

title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])


The thing to notice here is that Rails directly goes to the colum name, whereas Grails just uses the property name declared in the class.

Once again, I find the Grails validation section much more readable, as all constraints are organized in one section, and they're organized around the concepts that a user cares about. Thus, when I'm thinking about the validations that apply to a title, I can specify all constraints in the title constraints, compared to the Rails style, where the declarations are focused around the constraints (e.g. when you are thinking about a concept in the domain, do you think "Hm, let me figure out which properties of this class might need a format constraint?", or do you think "Hm, let me see, a title, what kind of constraints might it need, maybe a format constraint?"

5. Mapping into the database



static mapping = {
table 'activities'
createdBy column:'created_by'
updatedBy column:'updated_by'
version false
}




As you see in the "mapping" section of the Grails class, there were a few attributes that needed to mapped explicitly. The reason I had to do this is that at least during the initial migration, it was preferable that the Rails and Grails app work off the same database schema, so that the two apps can be tested side-by-side on the same data.

The mapping of the Grails app into the Rails database was pretty straightforward, as they follow very similar naming conventions for naming the database columns. First, Rails has the habit of converting the domain class names to plural for the table names, thus I had to add the mapping in the Grails app to point to the same tables. Additionally, the Rails class explicitly stated that the foreign keys that connect the user and activity are "created_by" and "updated_by", whereas the default Grails naming convention for the foreign keys would have been "created_by_id" and "updated_by_id", hence the additional mapping.

You will additionally notice the explicit disabling of the "version" column in the Grails class. The issue here is that by default, Grails uses a "version" column in order to allow Hibernate to do optimistic locking in transactions. Note that removing the version column from the Grails app has its penalties; however, at least in the initial implementation it was more important to have the same database schema, than to focus on performance. Although initially (before disabling the column) Grails very gracefully handled the addition of the new "version" column to the database, it became an issue when the column was added on a populated database, and the version would receive a null default value. Thus, had I decided to keep it for each table, I would have had to update the version column manually to contain 0 (so that Grails could increment as necessary, otherwise a NPE came up when Grails pulled a null from the database).

It is recommended that such a version column is restored after the initial migration period in order to allow Hibernate to make use of its optimistic locking performance optimizations.

6. Non persistent attributes
By default, all attributes defined in the class are persisted in the database. Now, in the example of the User class below, the cleartext password really shouldn't be persisted. Rails gets around it by declaring it as a virtual attribute:

class User < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password

end



The Grails domain class takes a slightly different approach, where the field is still declared, but is also mentioned in a special class attribute to indicate to Grails that the field shouldn't be persisted , and the 'password false' in the mapping section to indicate that a column shouldn't be created in the database.


class User {
static mapping = {
table 'users'
password false
}

static transients = [ "password"]

}


7. Persistence events

class Foo {
def beforeInsert = {
makeActivationCode()
}
}

As mentioned above, Rails supports the ability to automatically hook into the persistence events , in the case above, to execute the specified closure at a particular point in the persistence lifecycle. Grails doesn't support this out of the box, but it is extremely easy to accomplish the same functionality by installing the Grails Hibernate Events Plugin, and then specify a couple of specially named closures

All in all, throughout the whole conversion I had the feeling that when the Grails folks sat down to figure out how to do things, they put a little bit of thought into how developers actually work with domain classes, what's readable, and what's not, whereas the Rails approach has a little bit more of a "hacked up" feel to it.

Thursday, October 23, 2008

Converting legacy Rails apps to Grails (with NetBeans) : General Project Setup

It is hard to describe the pleasure of writing the title above, especially the "legacy" part :-) Although lately the jumping up and down of Ruby & Rails fanboys has subsided a little, after people started realizing that Rails is not going to kill anything (much less Java, hell if PHP people start going back to PHP that says A LOT!!!). So, a few years later I got a chance to chime in on the subject.

Anyway, the specific reason for writing this post is that I had a chance to take an existing Rails app, and move it to Grails. Some of the reasons for even attempting is that as much effort and hype has gone into Ruby and Rails, the (C)Ruby is not even close in being able to integrate with everything else like Java can. When you throw into the mix that even JRuby is starting to be faster than (C)Ruby, and that Groovy kicks the pants out of JRuby, there's also a performance story to be told for Grails. Finally, when you throw in the existence of a clear migration path in performance sensitive Groovy/Grails components directly into java (e.g. moving Groovy controllers to Spring MVC controllers, or Groovy services to Java services), migrating from Rails to Grails for integration for performance purposes is just a no brainer.

Anyway, enough of this high level stuff. I'm sure that if some Rails fanboys get a hold of this article, I'll probably get flamed with claims how Ruby & Rails can perform, and who knows what else, but I'll just leave the high level stuff at what I said above, and I'll focus on on comparing what I saw in Rails from the point of view of a Grails (and long term Java) developer. Additionally, I will be using a daily NetBeans 6.5 build (very close to RC1) to illustrate the steps I take along the way.

So, let's get started.

1. First, getting started with a project. In both Grails and Rails it's very simple to get started with the facilities NetBeans provides. For both Grails and Rails , NetBeans 6.5 provides wizards for creating a new project.




Now, a few comments on the contents of the screenshots. First of all, NetBeans 6.5 ships directly with Groovy & Grails support for the "Java" packaged download (e.g. from http://download.netbeans.org/netbeans/6.5/rc/). In contrast, for the Ruby/Rails support, you have to either download the Ruby package from the same location, or after downloading the Java installer / package, you have to go to the plugin center and install the Ruby/Rails plugin (either way, quite easy). Secondly, when you look at the options for Ruby/Rails projects, you might think that there are more options w/ Ruby and Rails, but it is actually quite deceiving. The reason that there are no separate options to create a "new Grails app w/ existing sources" is that NetBeans totally rocks and can directly open an existing Grails project without having to add any additional project data. On the Groovy front, you can just add Groovy scripts and classes into any Java project, so the extra options in the project menu are just not needed.

So, at first glance, very marginally and despite the outstanding NetBeans Ruby/Rails support, Grails scores the first point for me.

2. Second, I had to figure out what the general setup of a Rails app. Here's what a typical project structure looks like in both Grails and Rails:




Now, comparing the two project structures, it's very easy to get around the Rails app if you're familiar w/ a Grails project layout. A couple of things to note in the Rails project structure :
* The root of the web app is in the "Public" directory in the Rails app, while in the Grails project it's in the "Web Application" project folder.
* There is a "helpers" folder in the Rails app, which initially puzzled me. In most general terms, the helpers folder contains "controller helpers". Roughly speaking, the functionality that existed in "helpers" in the Rails app eventually founds its way in taglibs in the Grails app. Although I'm not 100% certain of the convention and usage of these helper methods in the Rails app, it seemed like the Rails active scaffold seemed to use some parts of what's in the helpers (however, i could be wrong).
* There is the "Migrations" folder in the Rails project, which seems to be generally useful and not present in the Grails app. Now, I would imagine that such a database centric functionality might not be that relevant in a Grails app, as a Grails app really isn't as aware of the database (as it deals w/ the domain model and not database columns as the Rails app does). Still, it seems like because Grails apps end up very much data driven, some method of managing the schema modifications could be generally useful (although, I really don't have any specific suggestions of what such a tool might be).
* (although you don't see this in the screenshots) If you had plugins installed into your Grails app, you would have a "Plugins" folder, which is roughly equivalent to the Rails "Vendors" foler.
* Finally, the Grails app has a folder called "Services" for creating transactional services , for which the Rails app doesn't have an explicit counterpart. More on transaction handling later.

All in all, in the rest of the project layout, they're very similar, and if one knows the one framework, it's pretty straightforward to grok the other one.

Alright, this is the general setup stuff so far. I have a lot more to write about : plugins, ajax support, services, transactions, the whole nine yards. Just to give you a sneak preview, the bottom line is that migrating a Rails app to Grails works very nicely, although not without a few minor hurdles to jump over. More on that in the next post, stay tuned...

Tuesday, July 08, 2008

Switch your test to Groovy ? Maybe not (yet).

When I attended the No Fluff Just Stuff conference last year, all speakers were pushing Groovy as an excellent choice for everybody's unit testing needs. And it is true that Groovy does bring a number of cool features to the testing party : expressiveness, ability to test private methods, mocks are almost built in the language.. So it's cool, no question about it.


In order for a tool to be effective as a unit testing tool, two things need to be true of the tool:

  • The tool has to be expressive when reading and writing the tests. In other words, the way the test are written should clearly express the purpose of the test.


  • When tests fail, the failures should very clearly pinpoint the reason for the failure and should help the developer immediately know the cause of the failure




Now, how does Groovy measure up ?

One of the big advantages of using an integrated framework like Grails (when I say integrated, I mean a framework that give you out of the box the whole stack : Ajax, web layer / controller, service layer, persistence) is that it is extremely easy to start writing tests. You just write "grails create-test" on the command line, and you already have the shell for the test. Then, when you want to run the test, you can easily run the test by just running "grails test-app", and voilla, all your unit and integration tests are off and running.

So, on the first criteria, Groovy really shines. Expressing the intent of the test with Groovy or verifying a particular test condition is way much better than doing the same in plain on Java. Often, reading a test written in Groovy is so much easier to understand what the test writer had in mind, it takes way less code to write and maintain. All in all, Groovy rocks here.

Now, the second part, pinpointing the cause of the error, I can't say much beyond what the stacktrace below says. Here are a couple of questions on the stacktrace below:

1. Can you make out where exactly the test fails ?
2. If this was a stack trace out of an error, would you be able to make out where the error occured ?
3. Can you figure out what classes are collaborating in your test ?

I think the answer to all three questions is probably a "no", or "it's not easy". For me, this is quite a show stopper for moving all my tests to Groovy (which might have previously been in Java). One thing about plain Java is just that : it's plain and simple to follow along. You see a stacktrace, and you immediately know what, how, who went wrong. Now, I could certainly agree that if you take Java, weave in some aspects, or throw in some interceptors (as in the case of EJB), the stacktrace can easily resemble what's below.

P.S. One final thing about NFJS and the push to use Groovy everywhere : it really bugs me that all of smart people who spoke in favor of using Groovy as a testing tool, just kept quiet about things like this. It bugs me very much that when an alternative like this was suggested, I was just given the positive side of the story, and the negatives were not mentioned at all (and mind you, there were definitely questions like "When would you not use Groovy for testing?). So, there seem to be two disappointing options here : either the speakers had not really used Groovy for testing and were pushing without having done any testing in Groovy themselves, or even worse, knew about these warts but intentionally kept shtumm on it




Cannot cast object 'com.company.foobar.RandomClassToTest@194e3fe' with class 'com.company.foobar.RandomClassToTest' to class 'java.util.List'

org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.company.foobar.RandomClassToTest@194e3fe' with class 'com.company.foobar.RandomClassToTest' to class 'java.util.List'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:340)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:628)
at config.foo.WorkflowConfigTests.transitionsWithStatus(WorkflowConfigTests.groovy:90)
at config.foo.WorkflowConfigTests.this$5$transitionsWithStatus(WorkflowConfigTests.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at config.foo.WorkflowConfigTests.validateSingleTransition(WorkflowConfigTests.groovy:64)
at config.foo.WorkflowConfigTests.this$5$validateSingleTransition(WorkflowConfigTests.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:867)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at config.foo.WorkflowConfigTests$_testExistingTransitionRules_closure1.doCall(WorkflowConfigTests.groovy:56)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Closure.call(Closure.java:305)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1041)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1018)
at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:51)
at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:765)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:753)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at config.foo.WorkflowConfigTests.testExistingTransitionRules(WorkflowConfigTests.groovy:55)
at org.codehaus.groovy.grails.support.GrailsTestSuite.runTest(GrailsTestSuite.java:72)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:765)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:753)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at TestApp_groovy$_run_closure8_closure18_closure19_closure20.doCall(TestApp_groovy:222)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure8_closure18_closure19_closure20.doCall(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Closure.call(Closure.java:287)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeClosure(ScriptBytecodeAdapter.java:598)
at TestApp_groovy$_run_closure10_closure27_closure28.doCall(TestApp_groovy:353)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.Closure.call(Closure.java:292)
at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:48)
at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:72)
at $Proxy20.doInTransaction(Unknown Source)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:128)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:765)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:753)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at TestApp_groovy$_run_closure10_closure27.doCall(TestApp_groovy:365)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure10_closure27.call(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeClosure(ScriptBytecodeAdapter.java:598)
at TestApp_groovy$_run_closure8_closure18_closure19.doCall(TestApp_groovy:220)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Closure.call(Closure.java:305)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.withStream(DefaultGroovyMethods.java:8161)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.withOutputStream(DefaultGroovyMethods.java:7738)
at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:51)
at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:765)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:753)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at TestApp_groovy$_run_closure8_closure18.doCall(TestApp_groovy:195)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Closure.call(Closure.java:305)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.withStream(DefaultGroovyMethods.java:8161)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.withOutputStream(DefaultGroovyMethods.java:7738)
at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:51)
at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:765)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:753)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at TestApp_groovy$_run_closure8.doCall(TestApp_groovy:194)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure8.call(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeClosure(ScriptBytecodeAdapter.java:598)
at TestApp_groovy$_run_closure10.doCall(TestApp_groovy:338)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:94)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure10.doCall(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Script.invokeMethod(Script.java:87)
at groovy.lang.MetaClassImpl.invokeMethodOnGroovyObject(MetaClassImpl.java:934)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:881)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:94)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrent0(ScriptBytecodeAdapter.java:109)
at TestApp_groovy$_run_closure3.doCall(TestApp_groovy:116)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:94)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure3.doCall(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Script.invokeMethod(Script.java:87)
at groovy.lang.MetaClassImpl.invokeMethodOnGroovyObject(MetaClassImpl.java:934)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:881)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:94)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrent0(ScriptBytecodeAdapter.java:109)
at TestApp_groovy$_run_closure1.doCall(TestApp_groovy:62)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:94)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at TestApp_groovy$_run_closure1.doCall(TestApp_groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at groovy.lang.Closure.call(Closure.java:292)
at groovy.lang.Closure.call(Closure.java:287)
at groovy.lang.Closure.run(Closure.java:368)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.DelegatingMetaClass.invokeMethod(DelegatingMetaClass.java:142)
at org.codehaus.gant.GantMetaClass.invokeMethod(GantMetaClass.java:79)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0(ScriptBytecodeAdapter.java:195)
at gant.Gant.dispatch(Gant.groovy:271)
at gant.Gant.this$2$dispatch(Gant.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:675)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at gant.Gant.invokeMethod(Gant.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at gant.Gant.processTargets(Gant.groovy:436)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at gant.Gant.processArgs(Gant.groovy:372)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
at org.codehaus.groovy.grails.cli.GrailsScriptRunner.callPluginOrGrailsScript(GrailsScriptRunner.groovy:204)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:1094)
at groovy.lang.ExpandoMetaClass.invokeStaticMethod(ExpandoMetaClass.java:957)
at org.codehaus.groovy.runtime.InvokerHelper.invokeStaticMethod(InvokerHelper.java:800)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeStaticMethodN(ScriptBytecodeAdapter.java:212)
at org.codehaus.groovy.grails.cli.GrailsScriptRunner.main(GrailsScriptRunner.groovy:124)
at org.codehaus.groovy.grails.cli.support.GrailsStarter.rootLoader(GrailsStarter.java:140)
at org.codehaus.groovy.grails.cli.support.GrailsStarter.main(GrailsStarter.java:169)

Thursday, May 15, 2008

Tapestry5 NetBeans Quickstart

I have been following the development of Tapestry 5 closely for the last couple of months, and I even got the first Tapestry 5 book that came out. I'm a big Tapestry fan, and I've been looking forward to the new Tapestry 5 release. I even lucked out and had a chance to talk to Howard in person:



One of the cool things that keeps drawing me towards Tapestry is the goal to make it really easy and intuitive to work with. One of the goals that I remember hearing for Tapestry 5 was to make it that easy, that it would compete more w/ Grails & Rails and not so much w/ traditional Java Web app frameworks (e.g. Struts, Struts2, etc)

However, when I first took a look at Tapestry 5, I was a little disappointed by the six line Maven command that one had to type in when starting a new project (e.g. see http://tapestry.apache.org/tapestry5/tutorial1/first.html):

mvn archetype:create
-DarchetypeGroupId=org.apache.tapestry
-DarchetypeArtifactId=quickstart
-DgroupId=org.apache.tapestry
-DartifactId=tutorial1
-DpackageName=org.apache.tapestry.tutorial


Compare that with Grails:


grails create-app

Welcome to Grails 1.0 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /usr/local/java/grails
Application name not specified. Please enter:
FooApp
---------------
[propertyfile] Updating property file: /home/polrtex/temp/FooApp/application.properties
Created Grails Application at /home/polrtex/temp/FooApp



It is true that one doesn't create an application all that often, and copy-and-pasting
these initial 5-6 lines would not be that big of a deal. However, for a newcomer, the Tapestry 5 experience has to be really smooth and starting a new app should be a breeze.

So, you would say, what does this have to do with NetBeans ?

Here it comes. As usual, NetBeans rocks everyone's socks, by lowering the entry barrier into starting an application. Here are the first steps (equivalent to the first two pages of the Tapestry tutorial : http://tapestry.apache.org/tapestry5/tutorial1/env.html). Here is what you need to do:
( Assuming that you already have a good install of NetBeans 6.1).

  1. Install the NetBeans Maven plugin by going into Tools -> Plugins . Type "maven" in the upper-left corner search box and install the plugin. The result should look something like this:
  2. Create a new Maven project
  3. Expand the "Archetypes from remote Maven repositories" and scroll down to find the Tapestry5 quickstart archetype . You might want to pay close attention to the description and select the latest archetypes (in my case, for 5.0.11)
  4. Now that you have the nice wizard, fill out the configuration attributes to specify the group id, the location of the project, the name of the project, etc.
  5. When you hit Finish, Maven will start downloading all the dependencies and setting up your project.
  6. Your new and shiny project is ready to go. Go into the project Properties and select your desired server to deploy to:
  7. Run the project from the project context menu and you're up and running !!!!



One very cool option that makes the Tapestry 5 setup almost identical to the Grails setup is the ability to run the application in Jetty by going to the command line and running:
mvn jetty:run

The upside of doing this is that after you run it, there is no deployment step. Tapestry supports reloading of the page templates as well as the page classes. As a result, whenever you make a change to a page, you can save it, refresh the browser and see the changes . Similarly, when you make a change to a class, compile it and the changes are immediately visible to the application. Such a setup really cuts down on waiting for the app to deploy.

So now, looking back a little bit, this setup is indeed very competitive w/ the Grails setup. In Grails, you would download the Grails distribution, and then would run the "grails create-app" command to create the new project. Grails would then prompt you for some properties (e.g. project name) and you're done. For Tapestry, it's quite similar; however, instead of downloading the Tapestry distribution, you would simply download Maven2 (or in the case of NetBeans, you would install the NetBeans plugin). Even better for Tapestry, if you already have Maven install, then starting a new app becomes a zero cost operation : you just specify the command line args (or better yet, use NetBeans to create the new app) and you're on your way, all dependencies already in place.

So, so far it's a tie between Grails and Tapestry. Grals vs Tapestry = 1:1

Enjoy your shiny and new Tapestry 5 app in NetBeans !!!

Friday, May 02, 2008

Quick File Chooser in NetBeans 6.1

I'm on a roll today, but I thought I'd drop this tip as well.

I'm a big fan of the NetBeans Quick File Chooser plugin, it makes it oh-so-easy to open up a file without having to detach my fingers from the keyboard. So, if you Google for the plugin, you'll find Tor's recommendation of the same; however, you can't find it on the Plugin Center . So, what if you really wanted to use the Quick File Chooser, in NetBeans 6.1, what should you do ? In the past, I've always copied the plugin with my netbeans preferences from the previous version that I've used, and it's worked OK. However, I decided on a clean NetBeans install on my home laptop, and I couldn't get my hands on it.

So, I followed a tip from Octavian's blog , added the Netbeans Latest Build update center, and ... voila, the Quick File Chooser is in the list of available plugins. Since it doesn't depend on any of the features of 6.1+ code, it works great in 6.1 as well.

Groovy HTML Encode

I kinda thought that in the past I had used a special Groovy method to encode something as HTML. I mostly need that when I need to post something to Blogger (which completely baffles me - why isn't there a better way of copy-and-pasting html/xml in blogger content ???) . I do know that in Grails, there are a couple of special methods that you can use if you want to encode something as html, json, or something else. However, I'm not using Grails right now, so that's not always the best option.

After wasting 15 minutes trying to remember exactly how I did it in the past, I realized that I was using StringEscapeUtils from Apache commons-lang. So, having remembered that, it just works like magic, here is what I use to convert my xml/html samples in order to post them on Blogger:

this.class.classLoader.rootLoader.addURL(new File("/usr/local/java/grails-1.0/lib/commons-lang-2.1.jar").toURL())


xml = '''
<module id="id" version="0.0.1" package="package"> </module>
'''
org.apache.commons.lang.StringEscapeUtils.escapeHtml(xml).split("\n").each() {println it }

, which outputs the following :
&lt;module id=&quot;id&quot; version=&quot;0.0.1&quot; package=&quot;package&quot;&gt; &lt;/module&gt;

, which is what I paste into blogger and it works like magic !!!

No more running around like a headless chicken trying to find some service online that will convert my html/xml snippets !


Freeform projects NetBeans JUnit test results : binding output to source code

As I had mentioned in a prior post a while back, it is pretty straightforward to bind the output of a Freeform Project JUnit task to the NetBeans JUnit test results. However, there are a couple of minor tweaks that I find myself making and forgetting, so, I thought I'd drop a blog entry, even just as a reminder for myself.

First, make sure that the JUnit task as a showoutput="true" attribute and has a formatter that explicitly states that doesn't use a file, e.g.

<formatter usefile="false" type="brief"/>

Emphasis on not using a file : e.g. you can use an xml formatter and it would work; however, if you don't specify the usefile="false" attribute, everything goes to the file and NetBeans doesn't get a chance to capture the output and display the results in a nice JUnit test results tree. As a result, the best combination ends up being a combination of a xml formatter that outputs to a file, and a brief formatter that doesn't, e.g. :


<junit fork="yes" printsummary="withOutAndErr" showoutput="true" errorProperty="test.failed" failureProperty="test.failed" filtertrace="false">
<formatter type="xml">
<formatter usefile="false" type="brief" />
<classpath refid="whatever-path-id">
</classpath>
</junit>

The second important part of working with the JUnit results in NetBeans is to make sure that when a test fails, when you click on the failure in the JUnit results, you want NetBeans to take you to the right line in the source code:

In order to accomplish that, make sure that you properly set the output directory for your test cases in your NetBeans project with the UI or project.xml:

  • In the Project Properties UI


  • In the nbproject/project.xml
...
<java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/2">\n ....
<compilation-unit>
<package-root>test/integration</package-root>
<unit-tests/>
<classpath mode="compile">${test.completion.classpath}</classpath>
<built-to>dest/test/unit</built-to>
<source-level>1.5</source-level>
</compilation-unit>
....
</java-data>
....

Saturday, April 26, 2008

Symantec Horror Story : "The Symantec Experience"

After a 6 months hiatus in the antivirus subscription of one of the laptops at the house, I finally decided to bite the bullet and either get the upgrade or subscribe to the antivirus updates. I was quite reluctant to do it for quite a bit ( I just absolutely hate being blackmailed into buying a product), but after listening to Security Now, I finally decided that it's not worth risking a security breach of the windows machine (btw, I'm entirely embarrassed to admit that I do own a Windows machine) . My Better Half is too attached to her ICQ client (despite the million better alternatives) and a few other pieces of software that she never uses, and so far I haven't been able to convince her to switch to Linux. On the positive side, my son has been a Linux user since he's 3 years old and he's happily playing a whole bunch of nice games like SuperTux, PPRacer, and many more others..).

Anyway, I digress. So, I finally decided to bit the bullet and part with 50 of my hard earned dollars. So, I go to the Symantec website (I did have a trial version that came with my access point), and here my woes really started.

First, while I'm ordering the software, they try to sell me "A Service" where they'd keep my download for a year. I figured, hey, I could download it and burn it to a CD, and I don't need to shell out $10. So , I happily click on the link and download an executable, I get a setup.exe and I start it up. Lo and behold, it's not the software, it's a "Download Manager" !!! In a later part of the saga, I asked their tech support what the service is all about and I can store the little download manager and reinstall the software 6 months from now, and they gladly told me that I could just download the trial version and use my registration code and I'd be good. So, WHY did they recommend the "Download Service" for 10 bucks ??? Hm, first signs of fishiness start showing...

Alright, so I downloaded the software, and started it up. As I mentioned before, the download manager started doing its job and in about half an hour the whole deal was downloaded. Now, while I was waiting for half an hour for the download to finish, why not make myself useful and see if I can clean up some of the old software from the machine. At one time the Norton Internet antivirus decided that it was done downloading and it has to start installing now! Well, as bad luck would have it, at the same time I was uninstalling an older JRE version. So, Norton decides to die and tell me that it can't install while something else is installing. I said fine, continued w/ my JRE uninstall, thinking that, stupid me, I'll just restart the Download Manager and it will resume the install.

Not that easy !!! I start the setup.exe again and it just tells me that the download is finished and just closes. WHAT?!!??? Did I just pay fifty bucks for this?? I didn't exactly order a download manager, I just wanted the damn software, but hey, they're smart and decided to give me a download manager instead. Alright, that works too, but at least make sure that the download manager knows how to start the installer, puh-puh-please !

So, next action. What can I do ? Phone, internet suport forum, online chat ? I decide to go with the online chat. However, Symantec decides to be fresh again ! Instead of just firing up some AJAX little gizmo to just exchange a couple of words with their technician, what do I have to do ? Of course, I have to download and install an application, that would install and register an ActiveX in my system, so that when I go into their "live chat" feature, it can look as if it is running in the web browser. Not only that, but the little piece of crappy software only happens to work in Internet Explorer !!! Is this coming from a company that is claiming that will protect my security ? "Oh, just install this little piece of software, we're just going to chat, disregard all the security warnings about ActiveX and such". What a bunch of losers !!

So, I once again stoop to their level and download the ActiveX and install the thing. By now, I'm quite pissed, I'm running IE, allowing ActiveX garbage on my machine. I finally get to the chat page, and you'd think with the ActiveX it would be something really fancy and slick. Nope. Just a text field for me to enter my comments, and a text area for the current conversation. The crown jewel of this work of art are three radio buttons to select what the technican can do "Nothing", "View Only" and "Full Control". You might think that a security company would be concerned and would give the users some choice to protect themselves . No such luck. The default is on "Full Control". And I thought that best practice in security would have been to give the user a choice and let them choose if someone else would poke around their machine, and worse off, I thought Symantec would have thought of that. Another level of disappointment reached !

So, I start talking to the dude (Krishnan) who takes his sweet time to answer my questions. I type in 5 things, wait for 10 minutes and only then he decides that I'm worth his attention. First, I ask him a couple of things about how disappointing the experience is so far, and then I get to the meat of things : I ask him why I can't install the product. The solution turns out to be "easy" : I just go to the Symantec site and download and run a tool (yup, an .exe), and what do you think it does ?? Ta-da !! It cleans up whatever the Download Manager downloaded so that I can start the Download Manager again and let it download for another half hour. Isn't this brilliant ?? Anybody with a brain stem would have figured this out : hey, why not include the "Download Cleanup" functionality into the Download Manager ? I mean, it's not like they shipped the product to me 6 months ago and don't have a chance to patch in this functionality : I downloaded the Download Manager 10 minutes ago !

Alright, next phase in the saga. I clean up my download, start the download again (yep, another 30 minutes down the drain) and I'm chattling w/ Krishnan about how I can leave some feedback to Symantec to help them improve their product. I could certainly just bitch about it (like I'm doing now) and and let them continue having a crappy product, but hey, I decided I'll give them a shout. So, while I'm still chatting w/ the dude, I went to their feedback page (of course still in IE7) and then... IE CRASHES and BURNS !!! Now, I'd guess it wasn't just a fault of IE, for some reason it's the ActiveX that I was using (of course, this is just speculation, I was so mad I didn't go digging through logs and such). Now I'm just flipping out : I'm using IE, ActiveX running, with a product that just goes out of its way trying to prevent me from using it, after being blackmailed for fifty bucks to run a shitty OS on my sweetheart's laptop, and it just died !!!

At this point, I'm lost for words. On one side, I could go and talk to tech support again, and possibly lose another hour dealing with them, or I could just try doing it on my own. But I'm suffiiently disappointed and I really want to try and ask them if I can cancel my order. Note, I said "ask them if I can", not "tell them to cancel". So, I go back into their online chat support center (ActiveX and all), and I start talking to a new dude (hm, was it Prasad??). I explained the problem that I had and I ask him to tell me what the options are to cancel my order. I specifically told him "Don't cancel the order yet, just tell me what the options are". I give him address and order number to look up the info. Once again, Dude takes his time, no hurry for him, he's getting paid for his time. Just as I'm about to ask him what's going on, he cheerfully informs me that... I'm all set, my order is cancelled and that it might take a few days for the refund to process. WHAT ???!!?? I specifically told the dude, DON'T cancel the order. Just as I'm explaining to the dude that I didn't want to cancel the order, he disconnects and reconnects from the chat session a couple of times, not mentioning a thing about it (no sorry, I got disconnected, nothing).

So, let me recap here. I'm working in Windows, that sucks. I part with my money to fix the crummy OS and prevent it from being 0wn3d every other day, and the antivirus company tries to take my dough for a useless download service (by default), so I'm even more bummed out. Then, I try to install something that should be a total no brainer, it doesn't care about installing the software, it cares about downloading it only. Hm... I'm fuming ! Then, the security company makes me jump through 100 insecure hoops including installing ActiveX-s who want full control by default of my desktop, running executables just downloaded from the web (albeit from their site, supposedly secure), and giving full remote control to a dude I don't know sitting somewhere in India. I'm starting to flip out now !!! How many times did I give somebody a chance to r00t my machine, I wonder ? Somewhere along the way, the antivirus company's software crashes my browser, and to top it off, the customer support people just blow me off, take their sweet time in addressing my issues, and in the end just cancel the order despite the fact that I told them not to !!!!

So, I ask , WHY ? I can see that my fifty bucks are not going to break a billion dollar company (or however big it is, it is big). But is that a reason to totally dis me and let it be known that they don't care ? Is that a reason to introduce glaring security issues along the way (ActiveX, executables, giving full control to my box by default) just based on the fact that they are "the security experts" - e.g. what if someone compromised THEIR site, how many of their customers would be compromised along the way ? Is that a reason for the customer support to not pay attantion to what I'm saying and disrespect me by just answering once every 5 minutes ?

Anyway, that's my story. I just blows me away that such a large industry is built on the side of a bug ridden OS, and to make it all more perverse, the culprit of the whole situation is also in the same industry (e.g. Microsoft OneCare product charges for services that prevent attackers from exploiting bugs in the OS that Microsoft itself built). Now, wouldn't it make sense that if one bought a Microsoft product, such protection would come as a part of the OS, instead of selling you a faulty OS, and then selling you a service to fix it. It's an interesting conflict of interest : would Microsoft make more money if they fix the OS and make it less exploitable (thus losing money on Antivirus support), or would they make more money selling a crummy defect-ridden OS and then selling Antivirus products for it ?

This is the end of my story. My conclusion : I'm so glad I use Linux !! Rock on Fedora, I'm looking forward to Fedora 9 !!

Monday, April 07, 2008

Apple Brainwashing puzzle

I write software for a living. As a result a lot of my favorite software developers at work and in my communities (e.g. Tapestry, Grails, NetBeans) are Mac users. On one hand, I accept the fact that, hey, they like their Mac, just as much as I am passionate and like my NetBeans for example. However, there are a couple of things that I haven't been able to grasp yet.

All of the people I'm talking about are very smart and very independent. Yet, at the same time, Apple has managed to run some kind of brainwashing trick, where anything Apple comes up with and anything Apple says is taken at face value. Here are a couple of glaring examples:

  • A year or however long time ago, Apple decides to do their iPhone thing. Great, nice device, cool glitzy graphics, all that. All of a sudden, everyone from a large number of my coworkers to the people on the Oscars buys an iPhone. That is all well, but the more puzzling part is how most people just swallow the garbage coming from Apple without questioning it. For example, I bet that 90% of the iPhone users still think that iPhone was the first device that had a full blown browser that doesn't need the content to be specifically formatted for mobile devices ( while I've been happily browsing on my Nokia E61 for 1-2 years before the iPhone came out, mind you with the E61 happily running a KHTML derived browser, the same browser that Safari extends as well).
  • Apple decides to release their SDK and to allow 3rd party Devs to distribute their apps for iTunes. That is all great, nice thinking, good way to screw the walled garden of the network operators (ironically, by creating a new walled garden, this time controlled by Apple). So, all of a sudden, everyone believes ( I spoke to a very smart coworker of mine, listened to the Java Posse) that this is the first attempt where a non-operator sets up a distribution mechanism where 3rd party devs can certify and distribute their apps. Rewind to 2-3 years before the iPhone SDK launch, and I was happily exploring the Nokia Catalogs (on my phone) that has a whole bunch of free and commercial applications for download (that have nothing to do with the operator, the same "independent" software distribution channel that Apple claims to have invented).

The list can go on and on. What really puzzles me is that Apple can throw these outrageously false claims, and all of these smart and independently thinking people that I know just eat it up, without questioning it for a second. What drove me over the edge was when I was listening to a Java Posse interview where they were discussing the release of the iPhone SDK and how Apple supposedly "owned" the device and that Apple was within their right to lock the device as much as they want and go to great lengths to restrict what kind of software could be on it (e.g. starting from the "locked" phones, going through the clauses in the iPhone dev kit that prohibits its usage for creation of VMs, including Java). That is the biggest steaming pile of BS that I've heard . Yeah, I know that when you get the software you don't actually "own" it, you license it, but I can't believe my ears : if I had spent $400-$500 on a device you better believe it that I will want to have as much control over it, I OWN the goddamn thing. Now, if it was someone other than Apple (e.g. think Microsoft), all of these smart people would be up in arms calling for boycotts of the device and the company and who knows what else. But when it's Apple, people just suck it up.

So, people, let get our senses back and start thinking a little bit more critically about the garbage that comes out of Apple's brainwashing machine. I know they make nice products and all, but let's not lose our senses every time they say something and examine it for what it really is : a company pushing their products and very selfishly looking out for its own interests.

Thursday, February 28, 2008

Thesis presentation

Below is the content of the presentation that I gave on my thesis about a year ago. It clearly is missing a lot of the content to explain what each slide is all about, but it nicely gives the general idea of what the solution is like: