Jul 6, 2009

Make our AspectJ Validation more Awesome

In the previous post, I had discussion with Stevo. He gave me some pointers for improving the validation aspect. So I did some modifications to make the validation aspect cooler. Thanks Stevo!!

Here is the modified @Validateable annotation:

package com.mycompany.myapp.aspect.bind;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validateable {
String[] validators() default {};
}
With this modification, we don't need to provide class type to @Validateable. The validators value is optional. If we leave the validators value empty, our validation aspect will use uncapitalize(beanName) + "Validator" as the default validator value.

Below is the modified Validation aspect:

package com.mycompany.myapp.aspect.bind;

import java.lang.annotation.Annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.apache.commons.lang.StringUtils
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;

@Aspect
public class Validation implements ApplicationContextAware {
private ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
if (this.applicationContext == null)
this.applicationContext = applicationContext;
}

private Validator getValidator(String validator) {
return (Validator) applicationContext.getBean(validator);
}

@Before("execution(* (@org.springframework.stereotype.Controller *..*Controller).*(..)) && " +
"@annotation(validateable)")
public void validate(JoinPoint joinPoint, Validateable validateable) throws Throwable {
Object target = null;
BindingResult result = null;

// Get validators' id/names
String[] validators = validateable.validators();

// Get the arguments
Object[] args = joinPoint.getArgs();

// Get annotations of the arguments
Annotation[][] annotations = ((MethodSignature) joinPoint.getSignature()).getMethod()
.getParameterAnnotations();

// Check arguments array and retrieve the target bean and the BindingResult object
for (int i = 0; i < args.length; i++) {
if (target != null && result != null)
break;
else if (args[i] instanceof BindingResult)
result = (BindingResult) args[i];
else if (annotations[i].length > 0) {
for (Annotation annotation: annotations[i]) {
if (annotation.annotationType()
.isAssignableFrom(ModelAttribute.class)) {
target = args[i];
break;
}
}
}
}

if (validators.length == 0)
validators = new String[] {
StringUtils.uncapitalize(
target.getClass().getSimpleName())
.concat("Validator") };

for (String validator: validators) {
// Get the validator bean from application context and validate the target bean
getValidator(validator).validate(target, result);

if (result.hasErrors())
break;

}
}
}
There are some modifications in the validate() method:
  • The validate() method uses @Before instead of @Around. It will accept the normal join point.
  • The validate() method doesn't use Architecture.controllerExecution() pointcut. It uses a new pointcut to locate any class annotated with @Controller (I'm not sure if this approach will have performance issue. I usually use the previous approach where the pointcut is declared verbosely.)
  • The validate() method uses parameter annotations to locate the position of target bean. We can use AspectJ annotated parameter expression to retrieve the target bean but there is one problem: The position of BindingResult object and the target bean can be random!! This is why we stick to tradional way to locate the annotated parameter.
And with the new @Validateable annotation, you don't need to provide the class type and the validators value:

@RequestMapping(method=RequestMethod.POST, params="create")
@Validateable
public String create(@ModelAttribute("example") Example example,
BindingResult bindingResult, WebRequest webRequest) {
if (bindingResult.hasErrors()) {
System.out.println("AspectJ validation is amazing!!!");
}

return null;
}
Because we don't provide validators value, the default validators value will be exampleValidator ^^

Jul 4, 2009

AspectJ Validation for Spring annotated controller

Spring 2.5.x introduces annotated controller that makes web development with Spring MVC a lot easier. No more XML is required for each controller you define.

Spring MVC's annotated controller reminds me of Ben Parker's famous quote: "With great power comes great responsibility". What's the great responsibility you will have for using the powerful annotated controller? The validation!!

With the old base-controller-classes approach you can just inject validators to your form controller and Spring MVC will take care of the validation for you. With annotated controller, this validation mechanism is removed. The common way to do validation with annotated controller is to inject Validator objects into your annotated controller and use its validate() method on the bean you want to validate. It's painful to write these validation steps in every request method I want to validate. The more of these validation codes I have in my method body, the uglier my method will be (Imagine if you have to use multiple validators to validate a single bean).

Luckily, with Spring 2.x's @Aspect, we can write a nice validation mechanism for our annotated controllers. If you want your request method to be validated, you just add annotation on top of your method and condition to check if the binding result has errors.

@Validateable(type=Example.class, validators="exampleValidator")
public String create(@ModelAttribute("example") Example example,
BindingResult bindingResult, WebRequest webRequest) {

if (bindingResult.hasErrors())/*do something here*/;

return null;
}

First, write the @Validateable annotation:

package com.mycompany.myapp.aspect.bind;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validateable {
Class type();
String[] validators();
}
@Validateable accepts two values: the target bean's class type and validator beans' name/id.

Next, write the Architecture aspect for your application:

package com.mycompany.myapp.aspect.system;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Architecture {
@Pointcut("execution(* com.mycompany.myapp..*Controller*.*(..))")
public final void controllerExecution() {}
}
The Architecture aspect will intercept any call that triggers any controller's method.

Then write the validation aspect:

package com.mycompany.myapp.aspect.bind;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;

@Aspect
public class Validation implements ApplicationContextAware {
private ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
if (this.applicationContext == null)
this.applicationContext = applicationContext;
}

private Validator getValidator(String validator) {
return (Validator) applicationContext.getBean(validator);
}

@Around("com.mycompany.myapp.aspect.system.Architecture.controllerExecution() && " +
"@annotation(validateable)")
public Object validate(ProceedingJoinPoint joinPoint, Validateable validateable) throws Throwable {
Object target = null;
BindingResult result = null;

// Get the class type and validators' id/names
Class type = validateable.type();
String[] validators = validateable.validators();

// Get the arguments
Object[] args = joinPoint.getArgs();

// The bindingResultIndex will hold the index of BindingResult object
Integer bindingResultIndex = null;

// Check arguments array and retrieve the target bean and the BindingResult object
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof BindingResult) {
if (bindingResultIndex == null) {
result = (BindingResult) args[i];
bindingResultIndex = new Integer(i);
}
} else if (type.isInstance(args[i])) {
target = args[i];
} else if (target != null && result != null)
break;
}

for (String validator: validators) {
// Get the validator bean from application context and validate the target bean
getValidator(validator).validate(target, result);

if (result.hasErrors()) {
// Assign the BindingResults back to the arguments array
args[bindingResultIndex.intValue()] = result;
break;
}
}

return joinPoint.proceed(args);
}
}
Validation aspect will intercept any call to a controller's request methods annotated with @Validateable annotation. It will validate the target bean and set the validation results to the BindingResult object.

Declare the validation aspect as a Spring bean in one of your context:

<aop:aspectj-autoproxy />
<bean class="com.mycompany.myapp.aspect.bind.Validation" factory-method="aspectOf" />

Create the target bean:

package com.mycompany.myapp.mymodule.example.model;

public class Example {
private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

Write a simple validator for your bean:

package com.mycompany.myapp.mymodule.example.validator;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import com.mycompany.myapp.mymodule.example.model.Example;

@Component("exampleValidator")
public class ExampleValidator implements Validator {
public boolean supports(Class arg) {
// TODO Auto-generated method stub
return Example.class.isAssignableFrom(arg);
}

public void validate(Object object, Errors errors) {
// TODO Auto-generated method stub
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "value", "field.required");
}
}

Finally, add @Validateable annotation to the request method in your annotated controller:

@RequestMapping(method=RequestMethod.POST, params="create")
@Validateable(type=Example.class, validators="exampleValidator")
public String create(@ModelAttribute("example") Example example,
BindingResult bindingResult, WebRequest webRequest) {

if (bindingResult.hasErrors()) {
System.out.println("AspectJ Validation is amazing!!!");
}
return null;
}

Don't forget to add AspectJ plugin to your pom.xml. If you use ant, you can use AspectJ's Ant task

Note: You can check an improved version of this validation system in the next post

Jan 3, 2009

Infinite Goodies

Albert Einstein once said: "Only two things are infinite, the universe and human stupidity, and I'm not sure about the former".

IMHO, he was wrong. Both universe and human stupidity might be infinite. But there a lot of infinite things besides the two things he mentioned, especially the good things: Creativity, Imagination, Innovation, Knowledge, and Possibility. If these five things are finite, we would be living in stone age these days. Those are the only five infinite goodies I know, and I believe there are more.

The blog title is unrelated to whats inside, so don't expect too much of infinite goodies in my blog ;p