I love Grails, but sometimes, the magic is a little too much.
I was trying to debug a particularly strange oddity, and as far as I could tell, Grails was reusing a dirty command object. That's not possible, right? Further investigation showed that no, it's not using a dirty command object, well, not really. Its only doing what it's supposed to: dependency injection and convention over configuration.
Here is the simplest form of the problem I have been able to put together.
The controller:
def debug = { DebugCommand d -> render new JSON(d) }
The command objects: I have nested commands, with DebugCommand being the outer command (used by the controller) and DebugMapCommand, a map holding some values. I'm using a LazyMap since that's why I used in my real-life problem.
public class DebugCommand { int someNum DebugMapCommand debugMapCommand = new DebugMapCommand() }
@Validateable public class DebugMapCommand { Map things = MapUtils.lazyMap([:], FactoryUtils.instantiateFactory(String)) }
What happens here is that multiple calls to the controller/action result data being accumulated in the LazyMap between calls:
nancyd $ curl 'http://localhost:8080/library/rpc/debug?someNum=5\&debugMapCommand.things\[2\]=5' {"class":"com.example.DebugCommand", "debugMapCommand":{"class":"com.example.DebugMapCommand", "things":{"2":"5"}}, "someNum":5} nancyd $ curl 'http://localhost:8080/library/rpc/debug?someNum=5\&debugMapCommand.things\[1\]=5' {"class":"com.example.DebugCommand", "debugMapCommand":{"class":"com.example.DebugMapCommand", "things":{"2":"5","1":"5"}}, "someNum":5} nancyd $ curl 'http://localhost:8080/library/rpc/debug?someNum=5\&debugMapCommand.things\[elephants\]=5' {"class":"com.example.DebugCommand", "debugMapCommand":{"class":"com.example.DebugMapCommand", "things":{"2":"5","1":"5","elephants":"5"}}, "someNum":5}
If I gave a new value for a map entry, the new value was used.
So, what's happening? The DebugCommand's reference to the DebugMapCommand is called debugMapCommand, and Grails thinks I want a DebugMapCommand injected, so it created a singleton, and passed it to all my DebugCommand instances. Oops.
Trying to prove this wasn't too easy. It would seem that a number of factors are necessary for this particular issue to manifest:
- The field name must be the same as the class name with the first letter lowercased
- The inner/sub command, DebugMapCommand, must be annotated with @Validateable
- The inner/sub command must be in a package that is searched for "validateable" classes (in Config.groovy, you have to have grails.validateable.packages = ['com.example.yourpackage', ...])
So, what's the lesson here?
Don't name your fields after their class.
Even I love the grails, it is one of the most flexible and powerful framework. I haven't faced any such problems still yet but will keep in mind if any such error occurs.
ReplyDeleteFrankly i dont have idea about this type of commands.but great to see this post and really learn some different things.
ReplyDeleteNo doubts there is flexibility of grails but now-a-days there are more languages and framework are available which provide better facility.
ReplyDelete