Build a Custom Validator in Grails with a Plugin

January 25, 2008 - 7 minute read -
groovy grails plugins validation

Grails is a really nice MVC framework inspired by Ruby on Rails. The difference is that Grails is built using Groovy and Java and leverages existing, well known frameworks as it's foundation. It is essentially a fairly thin convention-over-configuration and integration layer on top of Spring and Hibernate. The documentation for common scenarios is pretty good, but once you get outside of the common cases it deteriorates pretty quickly. This is part of my contribution I guess.

Constraints and Validation

Grails has a fairly good set of built in constraints to use to validate your domain objects. This allows you to decorate your Domain with constraints which are validated prior to persisting the objects.
class Address {
    String street
    String city
    String state
    static constraints = {
        street(nullable:true)
        city(blank:false)
        state(size:2..2)
    }
}

You can find a comprehensive list of the built in validations on the Grails website. It includes the ability to create a custom validator for a domain class if the built in ones do not suffice. So you can write a validator inline to your domain object.

class Student {
    String name
    Calendar dateOfBirth
    Calendar enrollmentDate
    static constraints = {
        name(blank:false)
        dateOfBirth(blank:false, validator: { val, obj ->
            // Ensure that the date of birth is before the enrollment date
            return ! val.after(obj.enrollmentDate)
        }))
    }
}

This validator is just a closure, so you could write it external to the class and reuse it among many different domain classes. I was interested to know how to create a "first-class" validator though that looked and acted just like the built in ones.

Create a Plugin

Grails is essentially built as a series of plugins. Everything from Hibernate support to Service Injection is implemented as a Plugin. Because of this it obviously must have a strong plugin system that allows you to do almost anything. So I decided to look at using a plugin to create a shared validation.

So, first: Generate a plugin. On the command line issue the command grails create-plugin MyApp. This will create a directory containing everything you need to construct a new plugin. The directory really looks like a mini Grails application. In fact you can run it exactly as a grails app which is great for testing.

This will create a src/ directory in the plugin as well. We can create our validator in there. You can create some directories under src/groovy or src/java to create a package. Use groovy or java depending on which language you want to use to implement your validator.

Create your Validator

Grails provides org.codehaus.groovy.grails.validation.AbstractConstraint as a simple base class to inherit from to get the basic functionality. Of course if you want to implement the whole thing yourself you just need to implement the interface org.codehaus.groovy.grails.validation.Constraint. That would be a lot of work, so just inherit from the AbstractConstraint.

Here's an example of a constraint used to validate a Postal Code (or zip code). This is a simple implementation that supports validating Canadian and US postal codes.

package net.zorched.validation
import org.codehaus.groovy.grails.validation.AbstractConstraint
import org.springframework.validation.Errors
class PostalCodeConstraint extends AbstractConstraint {
    private static final String DEFAULT_NOT_POSTAL_CODE_MESSAGE_CODE = "default.not.postalCode.message";
    public static final String POSTAL_CODE_CONSTRAINT = "postalCode";
    private boolean postalCode;
    private US = { postalCode->
        postalCode ==~ /\d{5}/
    }
    private CA = { postalCode->
        postalCode ==~ /[A-Z]\d[A-Z] \d[A-Z]\d/
    }
    public void setParameter(Object constraintParameter) {
        if(!(constraintParameter instanceof Boolean))
        throw new IllegalArgumentException("Parameter for constraint ["
               +POSTAL_CODE_CONSTRAINT+"] of property ["
               +constraintPropertyName+"] of class ["
               +constraintOwningClass+"] must be a boolean value");
        this.postalCode = ((Boolean)constraintParameter).booleanValue();
        super.setParameter(constraintParameter);
    }
    protected void processValidate(Object target, Object propertyValue, Errors errors) {
        if (! validPostalCode(target, propertyValue)) {
            def args = (Object[]) [constraintPropertyName, constraintOwningClass,
            propertyValue]
            super.rejectValue(target, errors, DEFAULT_NOT_POSTAL_CODE_MESSAGE_CODE,
                "not." + POSTAL_CODE_CONSTRAINT, args);
        }
    }
    boolean supports(Class type) {
        return type != null && String.class.isAssignableFrom(type);
    }
    String getName() {
        return POSTAL_CODE_CONSTRAINT;
    }
    boolean validPostalCode(target, propertyValue) {
        def country = "US"
        if (target.metaClass.hasMetaProperty("country")) {
            country = target.country
        }
        println country
        this."$country"(propertyValue)
    }
}

The name property is the key used to match your validator in your domain class to this Constraint instance.

class Address {
    String street
    String city
    String state
    String postalCode
    String country = "US"
    static constraints = {
        street(nullable:true)
        city(blank:false)
        state(size:2..2)
        postalCode(blank:false,postalCode:true)
    }
}

Unit Test It

You'll notice that I wrote the main algorithm for doing the validation as its own method. That will make it easy for me to write Unit Tests to confirm the functionality of my validator before I implement it in a domain class. So, now when someone comes back and wants to support Zip+4 in the US, I'll can write a test, see that it fails and then fix my Constraint to support that case as well.

class PostalCodeConstraintTests extends GroovyTestCase {
  void test_us_postal_code_succeeds_for_valid() {
    def postalCodeConstraint = new PostalCodeConstraint()
    def address = new Address(country:"US", postalCode:"53212")
    assert postalCodeConstraint.validPostalCode(address, address.postalCode)
  }
  void test_us_postal_code_fails_for_canada() {
    def postalCodeConstraint = new PostalCodeConstraint()
    def address = new Address(country:"US", postalCode:"A1A 3E3")
    assert ! postalCodeConstraint.validPostalCode(address, address.postalCode)
  }
  void test_ca_postal_code_succeeds_for_valid() {
    def postalCodeConstraint = new PostalCodeConstraint()
    def address = new Address(country:"CA", postalCode:"A1A 1D3")
    assert postalCodeConstraint.validPostalCode(address, address.postalCode)
  }
}
class Address {
  String country
  String postalCode
}

Wire It Into Grails

The only thing left to do is to register your Constraint so that Grails knows about it. When you created the plugin, grails generated a plugin "bootstrap" class for you. You can do that by registering your class with the ConstrainedProperty class provided by the Grails framework.

import net.zorched.validation.PostalCodeConstraint
import org.codehaus.groovy.grails.validation.ConstrainedProperty
class MyAppPlugin {
    def version = 0.1
    def author = "Geoff Lane"
    def description = '''
This plugin adds specific functionality to the application for my app.
'''
    def dependsOn = [:]
  def loadAfter = ['controllers']
    def doWithSpring = {
        ConstrainedProperty.registerNewConstraint(
            PostalCodeConstraint.POSTAL_CODE_CONSTRAINT,
            PostalCodeConstraint.class);
    }
}

ConstrainedProperty basically keeps a map of the constraint keys to the class type that implements the constraint.

Conclusion

Grails Plugins basically allow you to do anything that you might want to create an easily packaged piece of reusable functionality. I didn't find a anything in the documentation about how to build validators like the built in ones, so I read a bunch of source code. Now you don't have to, except for fun.

UPDATE: Please check out the Grails Custom Constraints plugin that automates all of this at http://grails.org/plugin/constraints