Skip to content

Rough Book

random musings

Menu
  • About Me
  • Contact
  • Projects
    • bAdkOde
    • CherryBlossom
    • FXCalendar
    • Sulekha
Menu

Integrating Regula with Spring 3.0.x MVC

Posted on February 21, 2011February 21, 2011 by vivin

Now the hard part. Getting validation information. This part of the integration involves a lot of reflection (and some recursion, especially in the case of compound constraints and @Valid). The basic strategy is as follows:

1. For each constraint property in the class:

   2. If the property is cascaded, get the class of the property and call this method again (recursion).

   3. If the property is not cascaded, for each constraint attached to the property:

      4. If the constraint is a composing constraint recursively identify and record each composing constraint within this constraint.
      5. if the constraint is not a composing constraint, simply record it.

The actual code that does all this is slightly more verbose though:

ValidationConstraintsService.java:
[sourcecode lang="java"]
package net.vivin.regula.validation.service;

import net.vivin.regula.validation.constraint.ConstraintDefinition;
import net.vivin.regula.validation.constraint.ConstraintInstance;
import net.vivin.regula.validation.constraint.ConstraintParameter;

import org.apache.commons.lang.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.type.ClassMetadata;
import org.springframework.stereotype.Service;

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.PropertyDescriptor;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class ValidationConstraintsService {

@Autowired
private LocalValidatorFactoryBean validator;

private Map>> classToPropertyToConstraintInstancesMap = new LinkedHashMap>>();
private Map> classToCompoundConstraintDefinitionMap = new HashMap>();

private String getFriendlyNameForProperty(String propertyName) {
String[] parts = StringUtils.splitByCharacterTypeCamelCase(propertyName);

for(int i = 0; i < parts.length ; i++) { parts[i] = parts[i].toLowerCase().replaceAll("\\.", " "); } parts[0] = StringUtils.capitalize(parts[0]); return StringUtils.join(parts, " ").replaceAll("\\s+", " "); } /* The following method uses a recursive algorithm that inspects a class and identifies any annotations (and their parameters) that are attached to a member field. If any field is annotated with a @Valid annotation, the method figures out the type of that field (which should be a complex type) and performs the same inspection on the class that represents that type. The clazz parameter is the current class we're working on i.e., the class who's validation constraints we want The second parameter holds the prefix. This is used to build a path. So that can get something like "object.fieldName" The third parameter (propertyConstraintInstancesMap) is a map that is keyed by property. The value is a set of ConstraintInstance objects. The last parameter maintains a set of compound-constraint definitions that we encounter in this class. */ private void _getConstraints(Class clazz, String prefix, Map> propertyToConstraintInstancesMap, Map compoundConstraintDefinitionMap) {
BeanDescriptor classDescriptor = validator.getConstraintsForClass(clazz);

for(PropertyDescriptor propertyDescriptor : classDescriptor.getConstrainedProperties()) {

Set constraintInstances = new HashSet();

String propertyName;

if(StringUtils.isEmpty(prefix)) {
propertyName = propertyDescriptor.getPropertyName();
}

else {
propertyName = prefix + "." + propertyDescriptor.getPropertyName();
}

String friendlyName = getFriendlyNameForProperty(propertyName);

if(propertyDescriptor.isCascaded()) {
String newPrefix;

if(StringUtils.isEmpty(prefix)) {
newPrefix = StringUtils.uncapitalize(propertyDescriptor.getPropertyName());
}

else {
newPrefix = prefix + "." + propertyDescriptor.getPropertyName();
}

_getConstraints(propertyDescriptor.getElementClass(), newPrefix, propertyToConstraintInstancesMap, compoundConstraintDefinitionMap);
}

else {
for(ConstraintDescriptor constraintDescriptor : propertyDescriptor.getConstraintDescriptors()) {

ConstraintInstance validationConstraintInstance = createConstraintFromDescriptor(constraintDescriptor);

if(constraintDescriptor.getComposingConstraints().size() > 0) {
handleComposingConstraints(validationConstraintInstance, constraintDescriptor, compoundConstraintDefinitionMap);
}

validationConstraintInstance.addParameter(new ConstraintParameter("label", friendlyName, "String"));
constraintInstances.add(validationConstraintInstance);
}
}

if(constraintInstances.size() > 0) {
propertyToConstraintInstancesMap.put(propertyName, constraintInstances);
}
}
}

/* this method uses an algorithm similar to the one above to get class-level constraints (i.e., validation constraints attached to a bean */
private void getClassLevelConstraints(Class clazz, Map> propertyToConstraintInstancesMap, Map compoundConstraintDefinitionMap) {
String friendlyName = StringUtils.uncapitalize(clazz.getSimpleName());
Set constraintInstances = new HashSet();

BeanDescriptor classDescriptor = validator.getConstraintsForClass(clazz);
Set> classLevelConstraintDescriptors = classDescriptor.getConstraintDescriptors();

for(ConstraintDescriptor constraintDescriptor : classLevelConstraintDescriptors) {

ConstraintInstance validationConstraintInstance = createConstraintFromDescriptor(constraintDescriptor);

if(constraintDescriptor.getComposingConstraints().size() > 0) {
handleComposingConstraints(validationConstraintInstance, constraintDescriptor, compoundConstraintDefinitionMap);
}

validationConstraintInstance.addParameter(new ConstraintParameter("label", friendlyName, "String"));
validationConstraintInstance.setClassLevelConstraint(true);

constraintInstances.add(validationConstraintInstance);
}

if(constraintInstances.size() > 0) {
propertyToConstraintInstancesMap.put(friendlyName, constraintInstances);
}

}

/* this method creates a constraint instance from a ConstraintDescriptor object */
private ConstraintInstance createConstraintFromDescriptor(ConstraintDescriptor constraintDescriptor) {

Annotation annotation = constraintDescriptor.getAnnotation();
ConstraintInstance validationConstraintInstance = new ConstraintInstance(annotation.annotationType().getSimpleName());
Map attributes = new LinkedHashMap(constraintDescriptor.getAttributes());

for(Map.Entry entry : attributes.entrySet()) {
if(!"payload".equals(entry.getKey())) {
validationConstraintInstance.addParameter(createConstraintParameter(annotation, entry));
}
}

return validationConstraintInstance;
}

/* This method recursively figures out the composing constraints of a constraint, and adds it to the compound-constraint definition set */
private void handleComposingConstraints(ConstraintInstance parentConstraintInstance, ConstraintDescriptor parentDescriptor, Map compoundConstraintDefinitionMap) {

ConstraintDefinition parentConstraintDefinition = new ConstraintDefinition(parentConstraintInstance);

/* definitely not the way I would like to do it, but there seems to be no way other way to identify hibernate or java
validation-constraints that are compound-constraints themselves
*/
if(parentDescriptor.getAnnotation().annotationType().getName().indexOf("javax.validation") < 0 && parentDescriptor.getAnnotation().annotationType().getName().indexOf("org.hibernate") < 0) { parentConstraintDefinition.setReportAsSingleViolation(parentDescriptor.isReportAsSingleViolation()); for(ConstraintDescriptor composingDescriptor : parentDescriptor.getComposingConstraints()) {

ConstraintInstance composingConstraintInstance = createConstraintFromDescriptor(composingDescriptor);

if(composingDescriptor.getComposingConstraints().size() > 0) {
handleComposingConstraints(composingConstraintInstance, composingDescriptor, compoundConstraintDefinitionMap);
}

parentConstraintDefinition.addComposingConstraint(composingConstraintInstance);
}

if(compoundConstraintDefinitionMap.get(parentConstraintDefinition.getName()) != null) {
ConstraintDefinition existing = compoundConstraintDefinitionMap.get(parentConstraintDefinition.getName());

for(ConstraintInstance composing : existing.getComposingConstraints()) {
parentConstraintDefinition.addComposingConstraint(composing);
}
}

compoundConstraintDefinitionMap.put(parentConstraintDefinition.getName(), parentConstraintDefinition);
}
}

//There is no straightforward way to translate some parameter values into a string. This is especially true in the case of the @Pattern annotation where
//the flags parameter is of type Pattern.Flag[] and the value is a array of Pattern.Flag[] objects (enums actually). So here, we inspect the parameters
//and their values to perform specific translations if needed
private ConstraintParameter createConstraintParameter(Annotation annotation, Map.Entry entry) {
String parameterName = entry.getKey();
Object parameterValue = entry.getValue();
String parameterType = parameterValue.getClass().getSimpleName();

String annotationName = annotation.annotationType().getSimpleName();

if("Pattern".equals(annotationName) && "flags".equals(parameterName)) {

String flags = "";

for(javax.validation.constraints.Pattern.Flag flag : (javax.validation.constraints.Pattern.Flag[]) parameterValue) {
//The only flags that make sense in Javascript are i, m, and g. i and m map to CASE_INSENSITIVE
//and MULTILINE. g doesn't map to a flag enum in the Java world, and the remaining Java enums
//don't map to anything in the Javascript world
switch(flag) {
case CASE_INSENSITIVE:
flags += "i";
break;

case MULTILINE:
flags += "m";
break;
}
}

parameterValue = flags;
parameterType = "String";

}

else if("message".equals(parameterName)) {

if(parameterValue.toString().startsWith("{") && parameterValue.toString().endsWith("}")) {
parameterValue = parameterValue.toString().replace("{", "").replace("}", "");
}
}

else if("groups".equals(parameterName)) {
String groups = "";

for(Class clazz : (Class[]) parameterValue) {
groups += clazz.getSimpleName() + ",";
}

Pattern pattern = Pattern.compile(",$", Pattern.DOTALL);
Matcher matcher = pattern.matcher(groups);
groups = matcher.replaceFirst("");

if(StringUtils.isEmpty(groups)) {
groups = Default.class.getSimpleName();
}

parameterValue = "[" + groups + "]";
parameterType = "Array";
}

return new ConstraintParameter(parameterName, parameterValue.toString(), parameterType);
}

public ClassConstraintInformation getValidationConstraints(Class clazz) {
Map> propertyToConstraintInstancesMap = classToPropertyToConstraintInstancesMap.get(clazz);
Map compoundConstraintDefinitionMap = classToCompoundConstraintDefinitionMap.get(clazz);

if(propertyToConstraintInstancesMap == null) {
propertyToConstraintInstancesMap = new LinkedHashMap>();
compoundConstraintDefinitionMap = new LinkedHashMap();

_getConstraints(clazz, "", propertyToConstraintInstancesMap, compoundConstraintDefinitionMap);
getClassLevelConstraints(clazz, propertyToConstraintInstancesMap, compoundConstraintDefinitionMap);

classToPropertyToConstraintInstancesMap.put(clazz, propertyToConstraintInstancesMap);
classToCompoundConstraintDefinitionMap.put(clazz, compoundConstraintDefinitionMap);
}

if(compoundConstraintDefinitionMap == null) {
compoundConstraintDefinitionMap = new LinkedHashMap();
}

return new ClassConstraintInformation(
new HashMap>(propertyToConstraintInstancesMap),
new HashSet(compoundConstraintDefinitionMap.values())
);
}
}
[/sourcecode]

Pages: 1 2 3 4

1 thought on “Integrating Regula with Spring 3.0.x MVC”

  1. littlebearz says:
    March 1, 2011 at 2:18 pm

    Interesting, I hate spam, but I’m thinking of creating something similar to dumb ai where it ask fun question rather than the mono tone that we see today 🙁

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Archives

  • February 2023
  • April 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • May 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • June 2017
  • March 2017
  • November 2016
  • August 2016
  • July 2016
  • June 2016
  • February 2016
  • August 2015
  • July 2014
  • June 2014
  • March 2014
  • December 2013
  • November 2013
  • September 2013
  • July 2013
  • June 2013
  • March 2013
  • February 2013
  • January 2013
  • October 2012
  • July 2012
  • June 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • July 2011
  • June 2011
  • May 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • August 2008
  • March 2008
  • February 2008
  • November 2007
  • July 2007
  • June 2007
  • May 2007
  • March 2007
  • December 2006
  • October 2006
  • September 2006
  • August 2006
  • June 2006
  • April 2006
  • March 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • February 2005
  • October 2004
  • September 2004
  • August 2004
  • July 2004
  • June 2004
  • May 2004
  • April 2004
  • March 2004
  • February 2004
  • January 2004
  • December 2003
  • November 2003
  • October 2003
  • September 2003
  • July 2003
  • June 2003
  • May 2003
  • March 2003
  • February 2003
  • January 2003
  • December 2002
  • November 2002
  • October 2002
  • September 2002
  • August 2002
  • July 2002
  • June 2002
  • May 2002
  • April 2002
  • February 2002
  • September 2001
  • August 2001
  • April 2001
  • March 2001
  • February 2001
  • January 2001
  • December 2000
  • November 2000
  • October 2000
  • August 2000
  • July 2000
  • June 2000
  • May 2000
  • March 2000
  • January 2000
  • December 1999
  • November 1999
  • October 1999
  • September 1999
©2023 Rough Book | Built using WordPress and Responsive Blogily theme by Superb
All original content on these pages is fingerprinted and certified by Digiprove