Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception handling in Grails controllers

Tags:

I know how to do generic exception handling in Grails using UrlMappings and an ErrorController for generic exception handling, so that if an exception escapes a controller the user will be sent to a generic error page and the exception will be logged. I also know how to use try/catch blocks to handle specific exceptions and attempt to recover from them.

But in most controllers, I just want to give the user a slightly more specific error message if an exception occurs. So in the create action, I want to tell the user that the item wasn't created. Or in the import action, I want to tell the user that the import failed. Right now, the controllers look like:

class ThingController {
  def create = {
    try {
      // The real controller code, which quickly hands it off to a service
    } catch (Exception e) {
      handleException(e, "There was an error while attempting to create the Thing")
    }
  }

  def delete = {
    try {
      // The real controller code, which quickly hands it off to a service
    } catch (Exception e) {
      handleException(e, "There was an error while attempting to delete the Thing")
    }
  }

  private void handleException(Exception e, String message) {
    flash.message = message
    String eMessage = ExceptionUtils.getRootCauseMessage(e)
    log.error message(code: "sic.log.error.ExceptionOccurred", args: ["${eMessage}", "${e}"])
    redirect(action:index)
  }
}

Note that the catch blocks don't do anything different based on the type or content of the exception; they're just giving a slightly more descriptive error message based on the controller. The "real" controller code is usually 6-10 lines, so having an additional 4 lines of code just to change the error message seems excessive. In addition, the CodeNarc "CatchException" rule complains, which reinforces my opinion that there has to be a better way to do this. I assume other Grails applications have similar requirements. What is the idiomatic way to specify different error messages based on which action the exception bubbled out of?

I'm interested in answers that come from experience with a particular way of solving this problem, or even better, link to codebases where I can see the solution in practice.

like image 939
Tom Panning Avatar asked Jun 19 '13 13:06

Tom Panning


2 Answers

Grails has the mechanism for general handling controller exceptions. You can do this inside a dedicated Error controller. Regular controllers don’t need to use try/catch.

Controller:

class ThingController {
    def create() {
        def id = params.id as Long

        if (id == null) {
            throw new MissingPropertyException("thingId")
        }
        // The real controller code, which mostly parses things out and hands it
        // off to a service.
        // Service methods can throws exception

    }
}

Add handling 500 error in UrlMappings:

class UrlMappings {

    static mappings = {
        // Exception handling in ErrorController
        "500"(controller: "error")
    }
}

ErrorController:

class ErrorController {

    def index() {

        def exception = request.exception.cause
        def message = ExceptionMapper.mapException(exception)
        def status = message.status

        response.status = status
            render(view: "/error", model: [status: status, exception: exception])
    }
}

You can handle REST and non-REST exceptions using this approach. Also there is Declarative Exception Handling plugin but I don’t have an

Update

You can get the specific error messages in Error controller. When in controller throw new RuntimeException ("There was an error while attempting to delete the Thing"), then in error controller request.exception.cause.message will show message: "There was an error while attempting to delete the Thing".

like image 130
Andriy Budzinskyy Avatar answered Sep 18 '22 11:09

Andriy Budzinskyy


See also How to know from where was thrown error 500 (Grails)

I create custom error pages based on annotations on the controllers, giving common exception handling procedures across several controllers.

class ErrorsController {
def index() {
    def initialController = request.exception?.className
    if (initialController) {
        def controller = grailsApplication.getArtefact("Controller", initialController).getReferenceInstance()
        // do some rendering based on the annotations
        render "Controller: ${initialController}, annotations ${controller.getClass().getDeclaredAnnotations()}"
        return
    }
    render 'no initial controller'
}
like image 22
Igor Avatar answered Sep 20 '22 11:09

Igor