Last month I blogged about how to leverage Spring Validators to check client information delivered in AJAX calls (you should have read it before proceeding here). I got the original idea after reading a very good article at java.net. The result, in my opinion, was very useful but it presented two problems
With that idea in my mind and, by the way, with the intention of also solving the points above I started working a little more with the code already available. The goals were:
- It forces the developer to create the Validator using a predefined schema
- The Validator had to implement a new interface (ClassAwareValidator)
With that idea in my mind and, by the way, with the intention of also solving the points above I started working a little more with the code already available. The goals were:
- Define a generic Validator that could check any object
- But not all objects should be checked by it
- The Validator could be used to check just a field or the whole object
- The Validator should be usable in AJAX (with DWR)
- The Validator should work side by side with other Validators in the system
- The Validator should support Spring MVC forms
@Target(ElementType.FIELD)
public @interface StringConstraint {
public boolean required() default true;
public String regexp() default "";
public int maxLength() default 0;
}
@Target(ElementType.FIELD)
public @interface IntegerConstraint {
public boolean required() default true;
public int minValue() default 0;
public int maxValue() default 0;
}
public @interface StringConstraint {
public boolean required() default true;
public String regexp() default "";
public int maxLength() default 0;
}
@Target(ElementType.FIELD)
public @interface IntegerConstraint {
public boolean required() default true;
public int minValue() default 0;
public int maxValue() default 0;
}
Those should be enough to start. Later some other ones could be created to test dates or decimals, for example. The code is very clear but to explain it a little more:
- The string annotation can be applied to any field and it will check that the field is not null, it matches a regular expression and it does not span over a number of characters.
- The integer annotation defines a minimum and maximum values for the field
protected Class extends Annotation> getValidationAnnotation(Field field) {
Class extends Annotation> validAnnotation = null;
if (field != null)
for (Annotation annotation: field.getAnnotations())
if (validAnnotation == null)
if (validAnnotations.contains(annotation.annotationType()))
validAnnotation = annotation.annotationType();
return validAnnotation;
}
public boolean supports(Class aClass) {
boolean supports = false;
if ((aClass != null) & (validAnnotations != null)) {
for (Field field : aClass.getDeclaredFields())
if (!supports) supports |= (getValidationAnnotation(field) != null);
}
return supports;
}
Class extends Annotation> validAnnotation = null;
if (field != null)
for (Annotation annotation: field.getAnnotations())
if (validAnnotation == null)
if (validAnnotations.contains(annotation.annotationType()))
validAnnotation = annotation.annotationType();
return validAnnotation;
}
public boolean supports(Class aClass) {
boolean supports = false;
if ((aClass != null) & (validAnnotations != null)) {
for (Field field : aClass.getDeclaredFields())
if (!supports) supports |= (getValidationAnnotation(field) != null);
}
return supports;
}
The Validator is loaded with the annotations that needs to check (with an init method). To test an object it reads all the fields and checks if at least one of them is annotated with one of the annotations considered valid. If the class is supported it just needs to check each annotated field
public void validate(Object object, Errors errors) {
if ((object == null) | (errors == null))
throw new IllegalArgumentException();
if (!this.supports(object.getClass()))
throw new IllegalArgumentException();
for (Field field : object.getClass().getDeclaredFields())
validateField(field, object, errors);
}
protected void validateFieldAsString(Field field, Object object, Errors errors) {
StringConstraint req = field.getAnnotation(StringConstraint.class);
if (req.required() && validateNull(field, object))
errors.rejectValue(field.getName(), errorCodes[0]);
field.setAccessible(true);
stringField = field.get(object).toString();
if ((stringField != null) & (req.regexp().length() > 0)) {
Pattern pattern = Pattern.compile(req.regexp());
Matcher matcher = pattern.matcher(stringField);
if (!matcher.matches())
errors.rejectValue(field.getName(), errorCodes[1], req.regexp());
}
if ((stringField != null) & (req.maxLength() > 0)) {
if (stringField.length() > req.maxLength())
errors.rejectValue(
field.getName(),
errorCodes[3],
Integer.toString(req.maxLength()));
}
}
if ((object == null) | (errors == null))
throw new IllegalArgumentException();
if (!this.supports(object.getClass()))
throw new IllegalArgumentException();
for (Field field : object.getClass().getDeclaredFields())
validateField(field, object, errors);
}
protected void validateFieldAsString(Field field, Object object, Errors errors) {
StringConstraint req = field.getAnnotation(StringConstraint.class);
if (req.required() && validateNull(field, object))
errors.rejectValue(field.getName(), errorCodes[0]);
field.setAccessible(true);
stringField = field.get(object).toString();
if ((stringField != null) & (req.regexp().length() > 0)) {
Pattern pattern = Pattern.compile(req.regexp());
Matcher matcher = pattern.matcher(stringField);
if (!matcher.matches())
errors.rejectValue(field.getName(), errorCodes[1], req.regexp());
}
if ((stringField != null) & (req.maxLength() > 0)) {
if (stringField.length() > req.maxLength())
errors.rejectValue(
field.getName(),
errorCodes[3],
Integer.toString(req.maxLength()));
}
}
Of course, a validateFieldXXX is needed for each type of annotation. I've just posted the String one as an example. There's one point left and it is modifying the validation service so it can use this new Validator (and by the way fix the little annoyances described above)
protected Validator getValidator(Class clazz) {
Validator validValidator = null;
if (clazz != null) {
for (Validator validator : this.validators)
if ((validValidator == null) && (validator.supports(clazz)))
validValidator = validator;
}
return validValidator;
}
public String validateField(String fieldName, String fieldValue) throws Exception {
String message = null;
if ((fieldName == null) || (fieldName.indexOf(".") < 0))
throw new DWRBindException(fieldName);
String className = fieldName.substring(0, fieldName.lastIndexOf("."));
Class> clazz = this.getClassFromName(className);
Validator validator = this.getValidator(clazz);
if (validator != null) {
String field = fieldName.substring(fieldName.lastIndexOf(".") + 1);
String capitalizedField = StringUtils.capitalize(field);
Object formBackingObject = clazz.newInstance();
Field f = clazz.getDeclaredField(field);
Object value = this.getValueObject(f.getType(), fieldValue);
f.setAccessible(true);
f.set(formBackingObject, value);
Errors errors = validateObject(formBackingObject);
FieldError error = errors.getFieldError(field);
if (error != null) message = error.getCode();
}
return message;
}
public Errors validateObject(Object object) throws Exception {
Errors errors = null;
if (object != null) {
Validator validator = this.getValidator(object.getClass());
if (validator != null) {
errors = new DirectFieldBindingResult(object, "command");
Class[] validationArgs = new Class[] { Object.class, Errors.class };
String methodName = "validate";
Method method = validator.getClass().getMethod(methodName, validationArgs);
if (method != null)
method.invoke(validator, new Object[] { object, errors });
}
}
return errors;
}
Validator validValidator = null;
if (clazz != null) {
for (Validator validator : this.validators)
if ((validValidator == null) && (validator.supports(clazz)))
validValidator = validator;
}
return validValidator;
}
public String validateField(String fieldName, String fieldValue) throws Exception {
String message = null;
if ((fieldName == null) || (fieldName.indexOf(".") < 0))
throw new DWRBindException(fieldName);
String className = fieldName.substring(0, fieldName.lastIndexOf("."));
Class> clazz = this.getClassFromName(className);
Validator validator = this.getValidator(clazz);
if (validator != null) {
String field = fieldName.substring(fieldName.lastIndexOf(".") + 1);
String capitalizedField = StringUtils.capitalize(field);
Object formBackingObject = clazz.newInstance();
Field f = clazz.getDeclaredField(field);
Object value = this.getValueObject(f.getType(), fieldValue);
f.setAccessible(true);
f.set(formBackingObject, value);
Errors errors = validateObject(formBackingObject);
FieldError error = errors.getFieldError(field);
if (error != null) message = error.getCode();
}
return message;
}
public Errors validateObject(Object object) throws Exception {
Errors errors = null;
if (object != null) {
Validator validator = this.getValidator(object.getClass());
if (validator != null) {
errors = new DirectFieldBindingResult(object, "command");
Class[] validationArgs = new Class[] { Object.class, Errors.class };
String methodName = "validate";
Method method = validator.getClass().getMethod(methodName, validationArgs);
if (method != null)
method.invoke(validator, new Object[] { object, errors });
}
}
return errors;
}

4 comentarios:
Hi Jose, tHx 4 your incredibly fine work ! I've not found source code in the war u posted... Would it B possible 4 U ...
I'm really anthousiastic about your work : I'm workin' on Spring, DWR and Alfresco too... and all your notes do interest me. I discover annotations. Seems 2 B very powerful... Your "paper" about logging was intersting. I'll get it a try asap.
So true...thank you! I will upload the sources tomorrow (Monday) when I have access to my laptop. Sorry for the delay.
Can u upload ur source to help other ppl to understand ur wonderful contribution? Thanks!
cheap tramadol
cheap celebrex
cheap meridia
cheap fioricet
cheap valium
cheap zoloft
cheap cialis
cheap paxil
Diflucan Valtrex
cheap propecia
Publicar un comentario en la entrada