blob: 25e3b41a96489ff0a3db5be03c990c6e9e926f3a [file] [log] [blame]
/*
* Copyright 1999-2012 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.validation.support.jvalidation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ByteMemberValue;
import javassist.bytecode.annotation.CharMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.DoubleMemberValue;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.FloatMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.LongMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.ShortMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import javax.validation.Constraint;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.bytecode.ClassGenerator;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.validation.Validator;
/**
* JValidator
*
* @author william.liangf
*/
public class JValidator implements Validator {
private static final Logger logger = LoggerFactory.getLogger(JValidator.class);
private final Class<?> clazz;
private final javax.validation.Validator validator;
@SuppressWarnings({ "unchecked", "rawtypes" })
public JValidator(URL url) {
this.clazz = ReflectUtils.forName(url.getServiceInterface());
String jvalidation = url.getParameter("jvalidation");
ValidatorFactory factory;
if (jvalidation != null && jvalidation.length() > 0) {
factory = Validation.byProvider((Class)ReflectUtils.forName(jvalidation)).configure().buildValidatorFactory();
} else {
factory = Validation.buildDefaultValidatorFactory();
}
this.validator = factory.getValidator();
}
public void validate(Invocation invocation) throws Exception {
String methodName = invocation.getMethodName();
String methodClassName = clazz.getName() + "$" + toUpperMethoName(methodName);
Class<?> methodClass = null;
try {
methodClass = Class.forName(methodClassName, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
}
Set<ConstraintViolation<?>> violations = new HashSet<ConstraintViolation<?>>();
Method method = clazz.getMethod(methodName, invocation.getParameterTypes());
Object parameterBean = getMethodParameterBean(clazz, method, invocation.getArguments());
if (parameterBean != null) {
if (methodClass != null) {
violations.addAll(validator.validate(parameterBean, Default.class, clazz, methodClass));
} else {
violations.addAll(validator.validate(parameterBean, Default.class, clazz));
}
}
for (Object arg : invocation.getArguments()) {
validate(violations, arg, clazz, methodClass);
}
if (violations.size() > 0) {
throw new ConstraintViolationException("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations, violations);
}
}
private void validate(Set<ConstraintViolation<?>> violations, Object arg, Class<?> clazz, Class<?> methodClass) {
if (arg != null && ! isPrimitives(arg.getClass())) {
if (Object[].class.isInstance(arg)) {
for (Object item : (Object[]) arg) {
validate(violations, item, clazz, methodClass);
}
} else if (Collection.class.isInstance(arg)) {
for (Object item : (Collection<?>) arg) {
validate(violations, item, clazz, methodClass);
}
} else if (Map.class.isInstance(arg)) {
for (Map.Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) {
validate(violations, entry.getKey(), clazz, methodClass);
validate(violations, entry.getValue(), clazz, methodClass);
}
} else {
if (methodClass != null) {
violations.addAll(validator.validate(arg, Default.class, clazz, methodClass));
} else {
violations.addAll(validator.validate(arg, Default.class, clazz));
}
}
}
}
private static boolean isPrimitives(Class<?> cls) {
if (cls.isArray()) {
return isPrimitive(cls.getComponentType());
}
return isPrimitive(cls);
}
private static boolean isPrimitive(Class<?> cls) {
return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class
|| Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls);
}
private static Object getMethodParameterBean(Class<?> clazz, Method method, Object[] args) {
if (! hasConstraintParameter(method)) {
return null;
}
try {
String upperName = toUpperMethoName(method.getName());
String parameterSimpleName = upperName + "Parameter";
String parameterClassName = clazz.getName() + "$" + parameterSimpleName;
Class<?> parameterClass;
try {
parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
ClassPool pool = ClassGenerator.getClassPool(clazz.getClassLoader());
CtClass ctClass = pool.makeClass(parameterClassName);
ClassFile classFile = ctClass.getClassFile();
classFile.setVersionToJava5();
ctClass.addConstructor(CtNewConstructor.defaultConstructor(pool.getCtClass(parameterClassName)));
// parameter fields
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterTypes.length; i ++) {
Class<?> type = parameterTypes[i];
Annotation[] annotations = parameterAnnotations[i];
AnnotationsAttribute attribute = new AnnotationsAttribute(classFile.getConstPool(), AnnotationsAttribute.visibleTag);
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAnnotationPresent(Constraint.class)) {
javassist.bytecode.annotation.Annotation ja = new javassist.bytecode.annotation.Annotation(
classFile.getConstPool(), pool.get(annotation.annotationType().getName()));
Method[] members = annotation.annotationType().getMethods();
for (Method member : members) {
if (Modifier.isPublic(member.getModifiers())
&& member.getParameterTypes().length == 0
&& member.getDeclaringClass() == annotation.annotationType()) {
Object value = member.invoke(annotation, new Object[0]);
MemberValue memberValue = createMemberValue(
classFile.getConstPool(), pool.getCtClass(member.getReturnType().getCanonicalName()), value);
ja.addMemberValue(member.getName(), memberValue);
}
}
attribute.addAnnotation(ja);
}
}
String fieldName = method.getName() + "Argument" + i;
CtField ctField = CtField.make("public " + type.getCanonicalName() + " " + fieldName + ";", pool.getCtClass(parameterClassName));
ctField.getFieldInfo().addAttribute(attribute);
ctClass.addField(ctField);
}
parameterClass = ctClass.toClass();
}
Object parameterBean = parameterClass.newInstance();
for (int i = 0; i < args.length; i ++) {
Field field = parameterClass.getField(method.getName() + "Argument" + i);
field.set(parameterBean, args[0]);
}
return parameterBean;
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
return null;
}
}
private static boolean hasConstraintParameter(Method method) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations != null && parameterAnnotations.length > 0) {
for (Annotation[] annotations : parameterAnnotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAnnotationPresent(Constraint.class)) {
return true;
}
}
}
}
return false;
}
private static String toUpperMethoName(String methodName) {
return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
}
// Copy from javassist.bytecode.annotation.Annotation.createMemberValue(ConstPool, CtClass);
private static MemberValue createMemberValue(ConstPool cp, CtClass type, Object value) throws NotFoundException {
if (type == CtClass.booleanType)
return new BooleanMemberValue((Boolean) value, cp);
else if (type == CtClass.byteType)
return new ByteMemberValue((Byte) value, cp);
else if (type == CtClass.charType)
return new CharMemberValue((Character) value, cp);
else if (type == CtClass.shortType)
return new ShortMemberValue((Short) value, cp);
else if (type == CtClass.intType)
return new IntegerMemberValue((Integer) value, cp);
else if (type == CtClass.longType)
return new LongMemberValue((Long) value, cp);
else if (type == CtClass.floatType)
return new FloatMemberValue((Float) value, cp);
else if (type == CtClass.doubleType)
return new DoubleMemberValue((Double) value, cp);
else if (type.getName().equals("java.lang.Class"))
return new ClassMemberValue(((Class<?>) value).getName(), cp);
else if (type.getName().equals("java.lang.String"))
return new StringMemberValue((String) value, cp);
else if (type.isArray()) {
CtClass arrayType = type.getComponentType();
int len = Array.getLength(value);
MemberValue[] members = new MemberValue[len];
for (int i = 0; i < len; i ++) {
members[i] = createMemberValue(cp, arrayType, Array.get(value, i));
}
ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cp);
arrayMemberValue.setValue(members);
return arrayMemberValue;
} else if (type.isInterface()) {
javassist.bytecode.annotation.Annotation info = new javassist.bytecode.annotation.Annotation(cp, type);
return new AnnotationMemberValue(info, cp);
} else {
// treat as enum. I know this is not typed,
// but JBoss has an Annotation Compiler for JDK 1.4
// and I want it to work with that. - Bill Burke
EnumMemberValue emv = new EnumMemberValue(cp);
emv.setType(type.getName());
return emv;
}
}
}