views:

399

answers:

2

The following code will throw a grails.validation.ValidationException if the save fails for some reason. But the result is a generic error. How can I know the actual cause of the error so I can report it back to the user?

 def addChild(cName,Parent theParent) {
    println "add child: ${cName}"
    def theChild = new Child(name:cName,parent:theParent)
    theChild.save(failOnError:true)
    return theChild
}

This is the stack trace returned. I happen to know that it's caused by violating a unique contsraint, because I caused it on purpose, but there's nothing in the trace to indicate that was cause vs. some other constraint violation.

org.codehaus.groovy.runtime.InvokerInvocationException: grails.validation.ValidationException: Validation Error(s) Occurred During Save

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646)

    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:436)

    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:374)

    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)

    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)

    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)

    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)

    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)

    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)

    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)

    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)

    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)

    at java.lang.Thread.run(Thread.java:619)

Caused by: grails.validation.ValidationException: Validation Error(s) Occurred During Save

    at AddRecordsService.addChild(AddRecordsService.groovy:30)

    at AddRecordsService$addChild.callCurrent(Unknown Source)

    at AddRecordsService.addAll(AddRecordsService.groovy:11)

    at AddRecordsService$$FastClassByCGLIB$$e47d68f4.invoke(<generated>)

    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)

    at AddRecordsService$$EnhancerByCGLIB$$cdfdcc61.addAll(<generated>)

    at AddRecordsService$addAll.call(Unknown Source)

    at AddrecordController$_closure2.doCall(AddrecordController.groovy:14)

    at AddrecordController$_closure2.doCall(AddrecordController.groovy)

    ... 32 more

Update

OK, it seems that at least for now, the only way to get a transaction to roll back AND figure out what caused the error is to check if the save failed, get the failedobject.errors and throw a RuntimeException. But now how do you pass the errors back to the calling controller? The following doesn't work.

   def addChild(cName,Parent theParent) {
        println "add child: ${cName}"
        def theChild = new Child(name:cName,parent:theParent)

       //theChild.save(failOnError:true)
       //theChild.save()


        if(!theChild.save()){
            println theChild.errors
             throw new RuntimeException(theChild.errors)
            //throw new RuntimeException('unable to save child')
        } 


        return theChild
    }
A: 

I would think that the ValidationException has a reference to the object that failed validation? If it does then check the errors property for the list of errors.

If not you will need to catch it and then check theChild.errors for the errors.

leebutts
Thanks for your reply. It got me to do a better search.It looks like this will be taken care of in 1.2RC1http://jira.codehaus.org/browse/GRAILS-5146
Brad Rhoads
A: 

Granted, the save(failOnError:true) approach currently yields too little information about what validation caused the problem. (Do you really want to parse exception messages in the controller anyway?) However, there are multiple alternatives you might consider to deal with two different issues, transaction rollback and communicating validation error information.

One alternative is to avoid the persistence of the data altogether. First, call theChild.validate() to see if theChild can be saved. If no validations fail, validate() returns true, so call theChild.save(). If validation errors occur, they will be recorded in the errors object on theChild. The newly created child returned to the calling controller can then be inspected for errors or simply displayed to the user in the view.

Another alternative is to not use declarative transactions; i.e. set static transactional = false on the service. Then, your code could manage the transaction itself; something like this:

Child.withTransaction { txStatus ->
   try {
     child.save(failOnError:true)
   } catch (ValidationException e) {
     // rollback, but swallow exception, requiring caller to check child for errors
     txStatus.setRollbackOnly()
   }
}

Finally, your question implies you want the controller to do something with the errors information, such as rendering the original page so a user can correct entries. Since theChild is what's validated, returning the child won't work when an exception is being thrown (your own or ValidationException). However, the child will have errors populated during the save() call. Instead of passing in a String and Parent, then instantiating a Child in the service, you should consider instantiating the Child in the caller and passing it in by reference. Then, when the save() fails, the child's errors will be populated, even if the save(failOnError:true) results in a ValidationException. The caller can catch the ValidationException and then pass the child back as part of the model for the view.

Personally, I think the first alternative (call validate() first) or the last (instantiate the child outside the service) is preferable to managing transactions manually. In Grails, the declarative transaction management is all or nothing for the entire service. That is, if you set static transactional = false for the service, you'll have to manually manage all transactions in every method of the service. The KISS principle should be applied here.

MadJack