2013年1月15日 星期二

Integrate Bean Validation (JSR303) with Struts2 -- SpringValidationInterceptor

Integrate Bean Validation (JSR303) with Struts2 -- Outline
整個整合的核心就在這支interceptor裡面,將整個Action傳給Validator進行驗證,若有錯誤就將錯誤塞到FieldError裡面,對熟悉Struts2的人來說應該不難。須額外注意的是如果Action是CGLIB proxy的話,需要轉換成target object進行驗證,因為Spring AOP生成的proxy object裡面field並不會有值。
com.gss.gmo.cao.struts2.interceptor.SpringValidationInterceptor
package com.gss.gmo.cao.struts2.interceptor;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.springframework.aop.support.AopUtils.getTargetClass;
import static org.springframework.aop.support.AopUtils.isCglibProxy;
import static org.springframework.util.ReflectionUtils.findField;
import static org.springframework.util.ReflectionUtils.getField;
import static org.springframework.util.ReflectionUtils.makeAccessible;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import lombok.Setter;

import org.apache.struts2.interceptor.validation.SkipValidation;

import com.gss.gmo.cao.struts2.interceptor.validation.ValidateFieldConfig;
import com.gss.gmo.cao.struts2.interceptor.validation.ValidateGroupConfig;
import com.gss.gmo.cao.struts2.interceptor.validation.ValidateMessageFormatter;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;

/**
 * Use Spring and JSR 303 to validate action data.
 *
 * @author linus_chien
 *
 */
public class SpringValidationInterceptor extends MethodFilterInterceptor {

    private static final long serialVersionUID = 1L;

    @Setter
    private Validator validator;

    private ReflectionProvider reflectionProvider;

    @Inject
    public void setReflectionProvider(ReflectionProvider prov) {
        this.reflectionProvider = prov;
    }

    @Override
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        Object originalAction = invocation.getAction();
        if (!(originalAction instanceof ActionSupport)) {
            return invocation.invoke();
        }

        Object targetAction = originalAction;
        if (isCglibProxy(originalAction)) {
            targetAction = getTargetClass(originalAction).newInstance();
            reflectionProvider.copy(originalAction, targetAction, invocation.getInvocationContext().getContextMap(), null, null);
        }

        Method method = targetAction.getClass().getDeclaredMethod(invocation.getProxy().getMethod(), new Class[] {});
        if (method.isAnnotationPresent(SkipValidation.class)) {
            return invocation.invoke();
        }

        ActionSupport originalActionSupport = (ActionSupport) originalAction;
        ActionSupport targetActionSupport = (ActionSupport) targetAction;

        ValidateGroupConfig groupConfig = method.getAnnotation(ValidateGroupConfig.class);
        Class<?>[] groups = {};
        if (groupConfig != null) {
            groups = groupConfig.groups();
        }

        ValidateFieldConfig fieldConfig = method.getAnnotation(ValidateFieldConfig.class);
        if (fieldConfig != null) {
            for (String field : fieldConfig.fields()) {
                if (isNotBlank(field)) {
                    validate(originalActionSupport, targetActionSupport, field, groups);
                }
            }
        } else {
            validate(originalActionSupport, targetActionSupport, "", groups);
        }

        return invocation.invoke();
    }

    /**
     * @param originalActionSupport
     * @param targetActionSupport
     * @param propertyPathPrefix
     * @param groups
     */
    private void validate(ActionSupport originalActionSupport, ActionSupport targetActionSupport, String propertyPathPrefix, Class<?>... groups) {
        Object target;
        if (isBlank(propertyPathPrefix)) {
            target = targetActionSupport;
        } else {
            Field targetField = findField(targetActionSupport.getClass(), propertyPathPrefix);
            if (targetField == null) {
                return;
            }
            makeAccessible(targetField);
            target = getField(targetField, targetActionSupport);
            if (target == null) {
                return;
            }
        }

        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target, groups);

        for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
            String propertyPath = constraintViolation.getPropertyPath().toString();
            if (isNotBlank(propertyPathPrefix)) {
                propertyPath = propertyPathPrefix + "." + propertyPath;
            }
            String errorMessage = constraintViolation.getMessage();
            if (originalActionSupport instanceof ValidateMessageFormatter) {
                errorMessage = ((ValidateMessageFormatter) originalActionSupport).format(originalActionSupport.getText(propertyPath, ""), errorMessage);
            }
            originalActionSupport.addFieldError(propertyPath, errorMessage);
        }
    }

}