grailsgroovyclosuresaopgrails-services

How to wrap all Grails service methods with a Groovy closure?


Grails 2.4.x here.

I have a requirement that all the methods of all my Grails services, generated by grails create-service <xyz>, be "wrapped"/intercepted with the following logic:

try {
    executeTheMethod()
} catch(MyAppException maExc) {
    log.error(ExceptionUtils.getStackTrace(maExc))
    myAppExceptionHandler.handleOrRethrow(maExc)
}

Where:

So obviously this wrapper code needs to include import statements for those classes as well.

So for example if I have a WidgetService that looks like this:

class WidgetService {
    WidgetDataService widgetDataService = new WidgetDataService()

    Widget getWidgetById(Long widgetId) {
        List<Widget> widgets = widgetDataService.getAllWidgets()
        widgets.each {
            if(it.id.equals(widgetId)) {
                return it
            }
        }

        return null
    }
}

Then after this Groovy/Grails/closure magic occurs I need the code to behave as if I had written it like:

import groovy.util.logging.Slf4j
import org.apache.commons.lang3.exception.ExceptionUtils
import com.example.myapp.MyAppExceptionHandler

@Slf4j
class WidgetService {
    WidgetDataService widgetDataService = new WidgetDataService()

    MyAppExceptionHandler myAppExceptionHandler = new MyAppExceptionHandler()

    Widget getWidgetById(Long widgetId) {
        try {
            List<Widget> widgets = widgetDataService.getAllWidgets()
            widgets.each {
                if(it.id.equals(widgetId)) {
                    return it
                }
            }

            return null
        } catch(MyAppException maExc) {
            log.error(ExceptionUtils.getStackTrace(maExc))
            myAppExceptionHandler.handleOrRethrow(maExc)
        }
    }
}

Any ideas as to how I might be able to achieve this? I'm worried that a pure Groovy closure might interfere somehow with whatever Grails is doing to its services under the hood at runtime (since they are all classes that don't explicitly extend a parent class).


Solution

  • Here is what I was trying to pin point in my comment:

    package com.example
    
    import groovy.util.logging.Log4j
    
    @Log4j
    trait SomeTrait {
    
        def withErrorHandler(Closure clos) {
            try {
                clos()
            } catch(Exception e) {
                log.error e.message
                throw new ApplicationSpecificException(
                    "Application Specific Message: ${e.message}"
                )
            }
        }
    }
    

    Service class:

    package com.example
    
    class SampleService implements SomeTrait {
    
        def throwingException() {
            withErrorHandler {
                throw new Exception("I am an exception")
            }
        }
    
        def notThrowingException() {
            withErrorHandler {
                println "foo bar"
            }
        }
    }
    

    Test:

    package com.example
    
    import grails.test.mixin.TestFor
    import spock.lang.Specification
    
    @TestFor(SampleService)
    class SampleServiceSpec extends Specification {
    
        void "test something"() {
            when:
            service.throwingException()
    
            then:
            ApplicationSpecificException e = thrown(ApplicationSpecificException)
            e.message == "Application Specific Message: I am an exception"
        }
    
        void "test something again"() {
            when:
            service.notThrowingException()
    
            then:
            notThrown(Exception)
        }
    }
    

    Here is the sample app.

    Grails 3.0.9 but it should not matter. this is applicable for Grails 2.4.*