1. Introduction

The Command Plugin gives Grails a convention for command objects by adding a new artefact type. It also adds an AST transformation to eliminate some of the boilerplate for dealing with errors from command objects.

2. Version History

  • 4.0.0

    • Upgraded to work with Grails 3.3.x

    • Added htmlEnforce constraint

    • Added built-in version of cascade constraint until it’s updated.

    • Updated the ErrorHandler annotation, found an issue when applying it at a class level, so now it ignores methods that start with $

  • 3.0.3

    • Fixing bug introduced in last version with the controller bound errors not having a validate method.

  • 3.0.2

    • Fixing the default error handler for the audo binding in 3.0.0 to do a validate rather than checking hasErrors.

  • 3.0.0/3.0.1

    • Added auto binding for url parameters to command objects, when using the default error handler. since this can break non default error handlers I bumped the version to 3.0.0.

  • 2.0.1

    • Changed responseCode to errorHandlerResponseCode in ControllerEnhancer.groovy, so its not so generic.

  • 2.0.0

    • Removed autowired Grails Application from command object artifacts. This is a minor breaking change, but it is a good idea as it will cut down on unnecessary injection, which will reduce memory used for the command object.

    • Added new built-in constraints for command/domain objects:

      • notMatches - opposite of matches.

      • blackListAnd - a black list of regexes, combined with AND.

      • blackListOR - a black list of regexes, combined with OR.

      • whiteListAnd - a white list of regexes, combined with AND.

      • whiteListOR - a white list of regexes, combined with OR

  • 1.0.4

    • Updating the plugin to include the cascade plugin, as it works well with this plugin.

    • Adding GDSL if you decide to use the global transform and not explicitly have Validateable.

    • Updating the controller enhancer so that you have a choice of respond or render to deal with conflicts with gson views.

    • Making the GrailsApplication from the trait injector transient to play nice with the cascade plugin.

  • 1.0.3

    • This release, fixes a typo issue that I’m not sure how I didn’t catch, because in a different environment, the code didn’t run. I also added a Global AST going toward more convention over configuration, just need to figure out GDSL to round that out(next release).

  • 1.0.2

    • Adding an appropriate 409 response code that can be overridden with a config change.

  • 1.0.1

  • 1.0

    • Initial Release including the ErrorsHandler annotation.

3. Getting Started

  1. Add the following dependency: .build.gradle

compile "org.grails.plugins:command:3.0.3"
  1. Run grails create-command:

grails create-command-object <package>.<commandName>

or for the default package

grails create-command-object <commandName>
  1. If this is the first command you’ve created, refresh the Gradle build. This will add the command folder to the source set. You can manually add the folder to the source set by right clicking on the folder, selecting "Mark Directory as", and selecting "Sources Root". In a future release I’ll look for a better way to automate, and eliminate this step.

4. Optional Configuration

There are two option configurations, that you can set in your config(application.groovy,runtime,groovy,application.yml):

  1. command.response.code - this allows you to set the response code used when validation fails for your command. The default is 409

  2. command.response.return - this determines for the default error handler, if it should return like:

respond errors, [formats: ['json', 'xml']]

or

render errors as JSON

the default is true and uses the first example, but you may want to consider the second example if you are using GSON views.

4.1. Example application

5. The @ErrorsHandler Annotation

The @ErrorsHandler annotation injects a call to the default error handling method, which is injected by the plugin. The annotation can either be applied to the controller class itself or to individual actions. If the annotation is applied at the class level it will be injected to each action, however applying it at the action level will override the class level behavior. The annotation can also be passed an optional name of an alternate method to call. The error handling functionality will not be applied to private methods or methods annotated with @SkipErrorsHandler.

If you use parameters outside of a command object, and those parameters have binding errors, those will be included in the list sent to the error handler, but for each parameter you will have to include an entry in your i18n message bundle. For example:

params.<Your parameter name here>.conversion.error = Your error massage for <Your parameter name> had an error binding.

Starting in 3.0.0 bound parameters, will be checked against Command objects. if a command object has a property of the same name as a bound parameter, and it’s values is not set, then the error handler will set that parameter to the value of the bound parameter. This is a helpful workaround for URL parameters, which won’t be bound to command objects, if there is a body with parameters. https://github.com/grails/grails-core/issues/9707

Because of this change you will have to update any custom error handlers to take an additional map parameter.

Example Usage:
    package test.command

    import com.virtualdogbert.ast.ErrorsHandler
    import grails.converters.JSON

    @ErrorsHandler
    class TestController {

        def index(TestCommand test) {
            //some controller code
        }

        @ErrorsHandler(handler = 'someOtherErrorHandler') //over rides the default.
        def list(TestCommand test) {
            //some controller code
        }

        @SkipErrorsHandler //Skips the error handler injection from the class annotation.
        def list(TestCommand test) {
            //some controller code
        }

        //Your error handler
        private boolean someOtherErrorHandler(List commandObjects) {
            List errors = commandObjects.inject([]) { result, commandObject ->

                if (commandObject.hasErrors()) {
                    result + (commandObject.errors as JSON)
                } else {
                    result
                }

            } as List

            if (errors) {
                //Do something
                return true
            }
            //possibly do something else
            return false
        }
    }
Code injected by the transformation into the affected actions:
    if(errorsHandler( [<every commandObject in the actions parameter list>], Map allOtherParameters )){ return null }
Default error handler injected into all controllers:
    boolean errorsHandler(List commandObjects, Map otherParams = [:]) {
        List errors = commandObjects.inject([]) { result, commandObject ->

            otherParams.each { otherParam ->
                if(commandObject.hasProperty(otherParam.key) && commandObject."$otherParam.key" == null){
                    commandObject."$otherParam.key" = otherParam.value
                }

            }
            if (commandObject.hasErrors()) {
                result + (commandObject.errors)
            } else {
                result
            }

        } as List

        if (errors) {
            response.status = errorHandlerResponseCode

            if (returnAsRespond) {
                respond errors, [formats: ['json', 'xml']]
            } else {
                render errors as JSON
            }

            return true
        }

        return false
    }

You can override the response code by setting command.response.code int your configuration.

6. Additional Constraints

The Command plugin adds 5 additional constraints that you can use to validate properties on command and domain objects. The errors returned can be overridden by overriding the default message codes in your i18n bundles.

6.1. White List And Constraint

This allows you to specify a list of regexes, and the value of the property must satisfy all of them, because the checks are combined with an AND like this:

boolean validation = checks.inject(true){ boolean result, String check ->
    result && propertyValue ==~ Pattern.compile(check)
}

if (!validation) {
Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String testWhiteListAnd

    static constraints = {
        testWhiteListAnd whiteListAnd:['(?:auto|initial)', '(?:initial|inherit|transparent)']
    }
}
default message code:

default.not.whiteListAnd.message

6.2. White List Or Constraint

This allows you to specify a list of regexes, and the value of the property must satisfy one of the regexes in the list, because the checks are combined with an OR like this:

boolean validation = checks.inject(false){ boolean result, String check ->
    result || propertyValue ==~ Pattern.compile(check)
}

if (!validation) {
Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String testWhiteListOr

    static constraints = {
        testWhiteListOR whiteListOr:['(?:auto|initial)', '(?:initial|inherit|transparent)']
    }
}
default message code:

default.not.whiteListOr.message

6.3. Black List And Constraint

This allows you to specify a list of regexes, and the value of the property must not satisfy all of them, because the checks are combined with and AND like this:

boolean validation = checks.inject(true){ boolean result, String check ->
    result && !(propertyValue ==~ Pattern.compile(check))
}

if (!validation) {
Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String testBlackListAnd

    static constraints = {
        testBlackListAnd blackListAnd:['(?:auto|initial)', '(?:initial|inherit|transparent)']
    }
}
default message code:

default.not.whiteListAnd.message

6.4. Black List OR Constraint

This allows you to specify a list of regexes, and the value of the property must not satisfy one of the regexes in the list, because the checks are combined with an OR like this:

boolean validation = checks.inject(false){ boolean result, String check ->
    result || !(propertyValue ==~ Pattern.compile(check))
}

if (!validation) {
Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String testBlackListOR

    static constraints = {
        testBlackListOR blackListOr:['(?:auto|initial)', '(?:initial|inherit|transparent)']
    }
}
default message code:

default.not.whiteListAnd.message

6.5. Not Matches Constraint

This allows you to specify a regex, and if it does match you will get an error, the opposite of the built-in matches constraint.

boolean validation = propertyValue ==~ Pattern.compile(check)

//if the value matches the regex add an error
if (validation) {
Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String testNotMatches

    static constraints = {
        testNotMatches notMatches:'(?:auto|initial)'
    }
}
default message code:

default.not.matches.message

6.6. htmlEnforce Constraint

This allows you to use the OWASP Html Sanitizer to validate an HTML string.

6.7. Configuration

By default the plugin sets:

html_sanitizer_policy= HtmlPolicy.POLICY_DEFINITION

However you can override that in your application.groovy with your own policy definition.

Example Usage:
package test.command

import grails.validation.Validateable

class Test3Command implements  Validateable{

    String html

    static constraints = {
        html htmlEnforce: true
    }
}
default message code:

default.htmlEnforce.messagee