org.hibernate.validator.ClassValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-validator-legacy Show documentation
Show all versions of hibernate-validator-legacy Show documentation
Following the DRY (Don't Repeat Yourself) principle, Hibernate Validator let's you express your domain
constraints once (and only once) and ensure their compliance at various level of your system automatically.
//$Id: ClassValidator.java 15765 2009-01-09 14:56:30Z hardy.ferentschik $
package org.hibernate.validator;
import java.beans.Introspector;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import org.hibernate.AssertionFailure;
import org.hibernate.Hibernate;
import org.hibernate.MappingException;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Component;
import org.hibernate.annotations.common.reflection.Filter;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMember;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
import org.hibernate.util.IdentitySet;
import org.hibernate.validator.interpolator.DefaultMessageInterpolatorAggregator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Engine that take a bean and check every expressed annotation restrictions
*
* @author Gavin King
* @author Emmanuel Bernard
*/
public class ClassValidator implements Serializable {
//TODO Define magic number
private static final Logger log = LoggerFactory.getLogger( ClassValidator.class );
private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[]{};
private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages";
private static final String VALIDATOR_MESSAGE = "ValidatorMessages";
private static final Set INDEXABLE_CLASS = new HashSet();
static {
INDEXABLE_CLASS.add( Integer.class );
INDEXABLE_CLASS.add( Long.class );
INDEXABLE_CLASS.add( String.class );
}
static {
Version.touch(); //touch version
}
private final Class beanClass;
private transient ResourceBundle messageBundle;
private transient ResourceBundle defaultMessageBundle;
private transient boolean isUserProvidedResourceBundle;
private transient ReflectionManager reflectionManager;
private final transient Map childClassValidators;
private transient List beanValidators;
private transient List memberValidators;
private transient List memberGetters;
private transient List childGetters;
private transient DefaultMessageInterpolatorAggregator defaultInterpolator;
private transient MessageInterpolator userInterpolator;
private static final Filter GET_ALL_FILTER = new Filter() {
public boolean returnStatic() {
return true;
}
public boolean returnTransient() {
return true;
}
};
/**
* create the validator engine for this bean type
*/
public ClassValidator(Class beanClass) {
this( beanClass, (ResourceBundle) null );
}
/**
* create the validator engine for a particular bean class, using a resource bundle
* for message rendering on violation
*/
public ClassValidator(Class beanClass, ResourceBundle resourceBundle) {
this( beanClass, resourceBundle, null, new HashMap(), null );
}
/**
* create the validator engine for a particular bean class, using a custom message interpolator
* for message rendering on violation
*/
public ClassValidator(Class beanClass, MessageInterpolator interpolator) {
this( beanClass, null, interpolator, new HashMap(), null );
}
/**
* Not a public API
*/
public ClassValidator(
Class beanClass, ResourceBundle resourceBundle, MessageInterpolator interpolator,
Map childClassValidators, ReflectionManager reflectionManager
) {
this.reflectionManager = reflectionManager != null ? reflectionManager : new JavaReflectionManager();
XClass beanXClass = this.reflectionManager.toXClass( beanClass );
this.beanClass = beanClass;
this.messageBundle = resourceBundle == null ?
getDefaultResourceBundle() :
resourceBundle;
this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
this.userInterpolator = interpolator;
this.childClassValidators = childClassValidators != null ?
childClassValidators :
new HashMap();
initValidator( beanXClass, this.childClassValidators );
}
@SuppressWarnings("unchecked")
protected ClassValidator(
XClass beanXClass, ResourceBundle resourceBundle, MessageInterpolator userInterpolator,
Map childClassValidators, ReflectionManager reflectionManager
) {
this.reflectionManager = reflectionManager;
this.beanClass = reflectionManager.toClass( beanXClass );
this.messageBundle = resourceBundle == null ?
getDefaultResourceBundle() :
resourceBundle;
this.defaultMessageBundle = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
this.userInterpolator = userInterpolator;
this.childClassValidators = childClassValidators;
initValidator( beanXClass, childClassValidators );
}
private ResourceBundle getDefaultResourceBundle() {
ResourceBundle rb;
try {
//use context class loader as a first citizen
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if ( contextClassLoader == null ) {
throw new MissingResourceException( "No context classloader", null, VALIDATOR_MESSAGE );
}
rb = ResourceBundle.getBundle(
VALIDATOR_MESSAGE,
Locale.getDefault(),
contextClassLoader
);
}
catch (MissingResourceException e) {
log.trace( "ResourceBundle {} not found in thread context classloader", VALIDATOR_MESSAGE );
//then use the Validator Framework classloader
try {
rb = ResourceBundle.getBundle(
VALIDATOR_MESSAGE,
Locale.getDefault(),
this.getClass().getClassLoader()
);
}
catch (MissingResourceException ee) {
log.debug(
"ResourceBundle ValidatorMessages not found in Validator classloader. Delegate to {}",
DEFAULT_VALIDATOR_MESSAGE
);
//the user did not override the default ValidatorMessages
rb = null;
}
}
isUserProvidedResourceBundle = true;
return rb;
}
private void initValidator(
XClass xClass, Map childClassValidators
) {
beanValidators = new ArrayList();
memberValidators = new ArrayList();
memberGetters = new ArrayList();
childGetters = new ArrayList();
defaultInterpolator = new DefaultMessageInterpolatorAggregator();
defaultInterpolator.initialize( messageBundle, defaultMessageBundle );
//build the class hierarchy to look for members in
childClassValidators.put( xClass, this );
Collection classes = new HashSet();
addSuperClassesAndInterfaces( xClass, classes );
for ( XClass currentClass : classes ) {
Annotation[] classAnnotations = currentClass.getAnnotations();
for ( int i = 0; i < classAnnotations.length ; i++ ) {
Annotation classAnnotation = classAnnotations[i];
Validator beanValidator = createValidator( classAnnotation );
if ( beanValidator != null ) beanValidators.add( beanValidator );
handleAggregateAnnotations(classAnnotation, null);
}
}
//Check on all selected classes
for ( XClass currClass : classes ) {
List methods = currClass.getDeclaredMethods();
for ( XMethod method : methods ) {
createMemberValidator( method );
createChildValidator( method );
}
List fields = currClass.getDeclaredProperties(
"field", GET_ALL_FILTER
);
for ( XProperty field : fields ) {
createMemberValidator( field );
createChildValidator( field );
}
}
}
private void addSuperClassesAndInterfaces(XClass clazz, Collection classes) {
for ( XClass currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) {
if ( ! classes.add( currClass ) ) return;
XClass[] interfaces = currClass.getInterfaces();
for ( XClass interf : interfaces ) {
addSuperClassesAndInterfaces( interf, classes );
}
}
}
private boolean handleAggregateAnnotations(Annotation annotation, XMember member) {
Object[] values;
try {
Method valueMethod = annotation.getClass().getMethod( "value" );
if ( valueMethod.getReturnType().isArray() ) {
values = (Object[]) valueMethod.invoke( annotation );
}
else {
return false;
}
}
catch (NoSuchMethodException e) {
return false;
}
catch (Exception e) {
throw new IllegalStateException( e );
}
boolean validatorPresent = false;
for ( Object value : values ) {
if ( value instanceof Annotation ) {
annotation = (Annotation) value;
Validator validator = createValidator( annotation );
if ( validator != null ) {
if ( member != null ) {
//member
memberValidators.add( validator );
setAccessible( member );
memberGetters.add( member );
}
else {
//bean
beanValidators.add( validator );
}
validatorPresent = true;
}
}
}
return validatorPresent;
}
@SuppressWarnings("unchecked")
private void createChildValidator( XMember member) {
if ( member.isAnnotationPresent( Valid.class ) ) {
setAccessible( member );
childGetters.add( member );
XClass clazz;
if ( member.isCollection() || member.isArray() ) {
clazz = member.getElementClass();
}
else {
clazz = member.getType();
}
if ( !childClassValidators.containsKey( clazz ) ) {
//ClassValidator added by side effect (added to childClassValidators during CV construction)
new ClassValidator( clazz, messageBundle, userInterpolator, childClassValidators, reflectionManager );
}
}
}
private void createMemberValidator(XMember member) {
boolean validatorPresent = false;
Annotation[] memberAnnotations = member.getAnnotations();
for ( Annotation methodAnnotation : memberAnnotations ) {
Validator propertyValidator = createValidator( methodAnnotation );
if ( propertyValidator != null ) {
memberValidators.add( propertyValidator );
setAccessible( member );
memberGetters.add( member );
validatorPresent = true;
}
boolean agrValidPresent = handleAggregateAnnotations( methodAnnotation, member );
validatorPresent = validatorPresent || agrValidPresent;
}
if ( validatorPresent && !member.isTypeResolved() ) {
log.warn( "Original type of property {} is unbound and has been approximated.", member );
}
}
private static void setAccessible(XMember member) {
if ( !Modifier.isPublic( member.getModifiers() ) ) {
member.setAccessible( true );
}
}
@SuppressWarnings("unchecked")
private Validator createValidator(Annotation annotation) {
try {
ValidatorClass validatorClass = annotation.annotationType().getAnnotation( ValidatorClass.class );
if ( validatorClass == null ) {
return null;
}
Validator beanValidator = validatorClass.value().newInstance();
beanValidator.initialize( annotation );
defaultInterpolator.addInterpolator( annotation, beanValidator );
return beanValidator;
}
catch (Exception e) {
throw new IllegalArgumentException( "could not instantiate ClassValidator", e );
}
}
public boolean hasValidationRules() {
return beanValidators.size() != 0 || memberValidators.size() != 0;
}
/**
* apply constraints on a bean instance and return all the failures.
* if bean
is null, an empty array is returned
*/
public InvalidValue[] getInvalidValues(T bean) {
return this.getInvalidValues( bean, new IdentitySet() );
}
/**
* apply constraints on a bean instance and return all the failures.
* if bean
is null, an empty array is returned
*/
@SuppressWarnings("unchecked")
protected InvalidValue[] getInvalidValues(T bean, Set