When I try to bind a JSON payload on to an existing domain object that has a nullable reference to another domain object, the binding fails if the in-bound JSON indicates null for the reference. This causes the persistence attempt to fail with this error:
| Error 2011-12-21 10:21:24,202 ["http-bio-8080"-exec-3] ERROR hibernate.AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
Message: null id in server.Uploader entry (don't flush the Session after an exception occurs)
Line | Method
->> 105 | update in server.SessionController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 722 | run in java.lang.Thread
| Error 2011-12-21 10:21:24,206 ["http-bio-8080"-exec-3] ERROR errors.GrailsExceptionResolver - AssertionFailure occurred when processing request: [POST] /Server/session/2
null id in server.Uploader entry (don't flush the Session after an exception occurs). Stacktrace follows:
Message: null id in server.Uploader entry (don't flush the Session after an exception occurs)
Line | Method
->> 105 | update in server.SessionController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 1110 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 603 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 722 | run in java.lang.Thread
Here's the domain objects in question:
class Session {
DateTime dateCreated
DateTime lastUpdated
String ip
String state
static hasOne = [uploader:Uploader]
static constraints = {
ip blank: false
state blank: false
uploader nullable: true, unique: true
}
}
class Uploader {
DateTime dateCreated
DateTime lastUpdated
Session session
String firstName
String lastName
String organization
String phoneNumber
String emailAddress
static constraints = {
firstName blank: false
lastName blank: false
organization blank: false
}
}
Here's the controller code that throws the error:
def update() {
def sessionInstance = Session.get(params.id)
if (!sessionInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'session.label', default: 'Session'), params.id])
redirect(action: "list")
return
}
if (params.version) {
def version = params.version.toLong()
if (sessionInstance.version > version) {
sessionInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'session.label', default: 'Session')] as Object[],
"Another user has updated this Session while you were editing")
render(view: "edit", model: [sessionInstance: sessionInstance])
return
}
}
sessionInstance.properties = params
if (!sessionInstance.save(flush: true)) { // <---- Error thrown here
render(view: "edit", model: [sessionInstance: sessionInstance])
return
}
flash.message = message(code: 'default.updated.message', args: [message(code: 'session.label', default: 'Session'), sessionInstance.id])
redirect(action: "show", id: sessionInstance.id)
}
This is the JSON payload that causes problems:
{
"id":2,
"dateCreated":"2011-12-21T09:33:33-06:00",
"ip":"127.0.0.1",
"lastUpdated":"2011-12-21T09:33:33-06:00",
"state":"closed",
"uploader":null
}
but this JSON payload works just fine:
{
"class": "server.Session",
"id":2,
"dateCreated":"2011-12-21T09:33:33-06:00",
"ip":"127.0.0.1",
"lastUpdated":"2011-12-21T09:33:33-06:00",
"state":"closed",
"uploader":null
}
What happens is that the uploader
property of the session is getting set to "server.Uploader : null"
. This is the .dump()
of the sessionInstance
object after sessionInstance.properties = params
:
<server.Session@713c6968 dateCreated=2011-12-21T09:33:33.000-06:00 lastUpdated=2011-12-21T09:33:33.000-06:00 ip=127.0.0.1 state=closed errors=grails.validation.ValidationErrors: 0 errors id=2 version=1 uploader=server.Uploader : null>
Everything works correctly when the "class": "server.Session"
property is in the JSON payload, but without it, everything breaks down. I would think that the data binding would be able to handle this, since it maps the rest of the properties just fine, but seems to just fail horribly for the reference.
Lastly, the reason I ran into this is because I am trying to build a Griffon client against a REST API, and it seems that the HTTPBuilder is stripping out the "class": "server.Session"
when it converts the original JSON response into a net.sf.json.JSONObject
, since the over-the-wire response has the property in it, but the dump of the net.sf.json.JSONObject
doesn't, nor does the subsequent JSON request payload.
- Is this a bug in how Grails data-binds when there is no "class" property of a JSON object?
- Is this a bug in how HTTPBuilder parses a JSON response?
- Is it a bug in how HTTPBuilder outputs JSON from a net.sf.json.JSONObject?
- Am I just doing something completely wrong?
Update
I have found a few additional bits of information:
- Removing the unique constraint doesn't change the behavior
- If the
uploader
field is non-null and the in-bound JSON object is trying to set it tonull
, the property is not changed (and no error occurs). But, if the"class": "server.Session"
property is in the in-bound JSON payload, it changes tonull
as expected.