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.