Recently, I have been working on some code that does not use any of the GORM capabilities in Grails, but instead controllers call to a service layer which interacts with web services hosted on an ESB. The domain classes that are in use are not understood by Grails to be the domain artefact type and as such, do not inherit certain traits. One of the traits that is missing is the ability to cascade validation from the object being validated down to any child objects. Let me illustrate.
Let’s say that we have a class Person which has some properties. One of these is a residential address, which is of type Address.
import grails.validation.Validateable @Validateable class Person { String firstName String lastName Address residenceAddress static constraints = { firstName blank: false //other constraints lastName blank: false //other constraints } } @Validateable class Address { String line1 String line2 String city String state String postalCode static constraints = { line1 blank: false //other constraints city blank: false //other constraints state blank: false //other constraints postalCode blank: false //other constraints } }
Since the Person class is a POGO and not a Grails domain artefact, the first thing we need to do to make this class capable of being validated during Grails controller binding is to mark the class with @Validateable. Then we can add the static constraints block and it will be used to evaluate/validate the properties during binding.
Issues arise when we need to validate the residenceAddress property of Person. How can we accomplish this? One way to do this is to manually validate the properties, and report any errors.
class MyController { // ... def personSubmit(Person p) { // ... // p is already validated, except for residenceAddress def anyErrors = p.hasErrors() || !p.residenceAddress.validate() if(anyErrors) { // report errors ... } } }
The problem with the above approach is two-fold. First, it doesn’t scale. We shouldn’t be validating the parent object and then again validating a child property. Imagine if this class had multiple child properties that were themselves object that again contained properties that were objects. What a nightmare to validate. Second, since you are manually invoking the validate method out of sequence with what is in the constraints block, any errors are reported back within their own object and there is no way to know what the original sequence was for the errors. This is particularly annoying when you need to display errors to a user that were part of a form submission and they are listed out in an order inconsistent with the layout of the form.
So, I decided that I would write some code that would would provide the functionality that is missing with POGOs.
import org.codehaus.groovy.grails.validation.* import org.springframework.validation.* class CascadeValidationConstraint extends AbstractVetoingConstraint { public static final String NAME = "cascadeValidation" @Override String getName() { NAME } @Override boolean supports(Class type) { true } @Override public void setParameter(Object constraintParameter) { if (!(constraintParameter instanceof Boolean)) { throw new IllegalArgumentException( """Parameter for constraint [$name] of property [$constraintPropertyName] of class [$constraintOwningClass] must be a Boolean """ ) } super.setParameter(constraintParameter) } @Override protected boolean skipNullValues() { return true } @Override protected boolean processValidateWithVetoing( Object target, Object propertyValue, Errors errors) { if (!propertyValue.validate()) { propertyValue.errors.fieldErrors.each { String field = "${propertyName}.${it.field}" def fieldError = new FieldError( target.errors.objectName, field, it.rejectedValue, it.bindingFailure, it.codes, it.arguments, it.defaultMessage ) errors.addError(fieldError) } return false } return true } }
What the above constraint does is validate the given property, and if it doesn’t pass validation according to the constraints block defined on that class, adds each error as a FieldError to the property’s parent object at the correct field location. Then you just need to register your custom constraint. You can do this in Config.groovy, Bootstrap.groovy, or during plug-in initialization if you build this into a plug-in.
import org.codehaus.groovy.grails.validation ConstrainedProperty.registerNewConstraint( CascadeValidationConstraint.NAME, CascadeValidationConstraint.class )
Now our Person class can be defined as follows and all validations are handled as expected (note the highlighted line below).
import grails.validation.Validateable @Validateable class Person { String firstName String lastName Address residenceAddress static constraints = { firstName blank: false //other constraints lastName blank: false //other constraints residenceAddress cascadeValidation: true } } @Validateable class Address { String line1 String line2 String city String state String postalCode static constraints = { line1 blank: false //other constraints city blank: false //other constraints state blank: false //other constraints postalCode blank: false //other constraints } }