Skip to content

Instantly share code, notes, and snippets.

@robfletcher
Created June 29, 2010 15:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robfletcher/457351 to your computer and use it in GitHub Desktop.
Save robfletcher/457351 to your computer and use it in GitHub Desktop.
package com.github.robfletcher.grails.validation
import org.codehaus.groovy.grails.validation.AbstractConstraint
import org.springframework.validation.Errors
class AcyclicConstraint extends AbstractConstraint {
static final String DEFAULT_MESSAGE_CODE = "default.acyclic.violation.message"
static final String NAME = "acyclic"
private boolean validateConstraint
protected void processValidate(Object target, Object propertyValue, Errors errors) {
if (validateConstraint && propertyValue) {
if (isCyclic(target, propertyValue)) {
def args = [constraintPropertyName, constraintOwningClass, propertyValue] as Object[]
rejectValue(target, errors, DEFAULT_MESSAGE_CODE, "${NAME}.violation", args)
}
}
}
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 value")
}
validateConstraint = constraintParameter
super.setParameter(constraintParameter)
}
boolean supports(Class type) { true }
String getName() { NAME }
private boolean isCyclic(original, node) {
boolean cyclic = false
while (node != null) {
if (node.id == original.id) {
cyclic = true
break
}
node = node."$propertyName"
}
return cyclic
}
}
package com.github.robfletcher.grails.validation
import grails.test.GrailsUnitTestCase
import org.apache.commons.lang.math.RandomUtils
import org.springframework.validation.BindException
import org.springframework.validation.Errors
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.hasItem
class AcyclicConstraintTests extends GrailsUnitTestCase {
AcyclicConstraint constraint
TestBean target
Errors errors
void setUp() {
super.setUp()
constraint = new AcyclicConstraint()
constraint.owningClass = TestBean
constraint.propertyName = "parent"
constraint.parameter = true
target = new TestBean(id: RandomUtils.nextLong())
errors = new BindException(target, "target")
}
void testNullValueIsValid() {
constraint.validate target, target.parent, errors
assertFalse "Should have no errors", errors.hasErrors()
}
void testSelfRelationIsInvalid() {
target.parent = target
constraint.validate target, target.parent, errors
assertTrue "Should have errors", errors.hasErrors()
assertThat "Error codes on 'parent' property", errors.getFieldErrors("parent").code, hasItem("acyclic.violation")
}
void testCircularRelationshipIsInvalid() {
target.parent = new TestBean(id: RandomUtils.nextLong(), parent: target)
constraint.validate target, target.parent, errors
assertTrue "Should have errors", errors.hasErrors()
assertThat "Error codes on 'parent' property", errors.getFieldErrors("parent").code, hasItem("acyclic.violation")
}
void testAcyclicRelationshipIsValid() {
target.parent = new TestBean(id: RandomUtils.nextLong())
constraint.validate target, target.parent, errors
assertFalse "Should not have errors", errors.hasErrors()
}
void testCircularRelationshipIsValidIfConstraintIsDisabled() {
target.parent = new TestBean(id: RandomUtils.nextLong(), parent: target)
constraint.parameter = false
constraint.validate target, target.parent, errors
assertFalse "Should not have errors", errors.hasErrors()
}
}
class TestBean {
long id
TestBean parent
boolean equals(Object obj) {
if (this.is(obj)) return true
if (obj == null) return false
if (!(obj instanceof TestBean)) return false
return id == obj.id
}
int hashCode() {
return id.hashCode()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment