Validating Map values using Grails constraints

In a recent Grails project, we came up with a requirement where we could collect arbitrary input data from a form and validate them in the Grails controller.

There are several validation techniques and initially I was thinking of creating various standard validation closures (like isEmpty, isNull, hasLength etc) on the map key name and execute them at runtime. By my coworker proposed an idea to use the Grails constraints directly instead of standard validation closures. I liked the idea, because a) it allows reuse of existing constraints and validate it the “Grails-way” and b) It is trivial to create custom constraints.

CommandObject

@Validateable
class DynamicFields {
  Map responses = [:]
}

Controller

class DynamicFieldsController {
def submit() {
  DynamicFields fields = new DynamicFields()
  fields.responses.putAll(params)

  //for illustration, imagine the fields.responses has a key called "firstName" whose value has to be validated
  ConstrainedProperty constrainedProperty = new ConstrainedProperty(DynamicFields, "firstName", String)
  constrainedProperty.applyConstraint("blank", false)
  constrainedProperty.validate(fields, params.firstName, fields.getErrors()) //getErrors() is available  because of @Validateable
  fields.errors.each { println it }
}
}

Problem

The (pseudo) execution stack now is validate() -> processValidate() -> AbstractConstraint.rejectValue -> BeanWrapperImpl.getPropertyValue() -> java.beans.PropertyDescriptor.getMethod() that throws a “NotReadablePropertyException”.

Obviously the java beans framework cannot find the firstName property. I tried several variations on the DynamicFields class: propertyMissing(), @Override getProperty(), setProperty(), metaclass.getProperty(), even AbstractConstraint.metaClass.rejectValue() – none of them were respected by the java beans. Well obviously, because the Grails magic does all this via GroovyObjectSupport, but the underlying Java Beans framework does not know about it.

Solution

Remember that Errors and BindingResult are interfaces and the concrete implementation is provided by AbstractBindingResult and its subclasses. By default Spring uses the BeanPropertyBindingResult for tying back the validation error to the field. From the hierarchy of these classes, I saw the MapBindingResult class, which binds the validation to a target map. Just what I wanted.

So I changed the controller to

DynamicFields fields = new DynamicFields()
fields.responses.putAll(params)
fields.setErrors(new MapBindingResult(fields.responses, DynamicFields.class.getName()))

This cleanly tied the errors to the individual map keys.

Displaying the errors also is trivial:

<g:hasErrors bean="${fields?.errors}">
<g:eachError><p>${it}</p></g:EachError>
</g:hasErrors/>

A cleaner solution is probably to create an AST that ensures that the setErrors (MapBindingResult) is done always or a better way of injecting MapBindingResult into Errors.

In fact, even without Map responses object, using Groovy’s Expando, one can directly store values in the DynamicFields class and validate them. Power of dynamic programming – ensuring valid data is collected even for “non-existent” attributes !

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: