// $Id: ValidatorImpl.java 17860 2009-10-28 12:14:31Z hardy.ferentschik $
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.hibernate.validator.engine;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.MessageInterpolator;
import javax.validation.Path;
import javax.validation.TraversableResolver;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import com.googlecode.jtype.TypeUtils;
import org.hibernate.validator.engine.groups.Group;
import org.hibernate.validator.engine.groups.GroupChain;
import org.hibernate.validator.engine.groups.GroupChainGenerator;
import org.hibernate.validator.engine.resolver.SingleThreadCachedTraversableResolver;
import org.hibernate.validator.metadata.BeanMetaData;
import org.hibernate.validator.metadata.BeanMetaDataCache;
import org.hibernate.validator.metadata.BeanMetaDataImpl;
import org.hibernate.validator.metadata.ConstraintHelper;
import org.hibernate.validator.metadata.MetaConstraint;
import org.hibernate.validator.util.ReflectionHelper;
/**
* The main Bean Validation class. This is the core processing class of Hibernate Validator.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public class ValidatorImpl implements Validator {
/**
* The default group array used in case any of the validate methods is called without a group.
*/
private static final Class<?>[] DEFAULT_GROUP_ARRAY = new Class<?>[] { Default.class };
/**
* Used to resolve the group execution order for a validate call.
*/
private final transient GroupChainGenerator groupChainGenerator;
private final ConstraintValidatorFactory constraintValidatorFactory;
/**
* {@link MessageInterpolator} as passed to the constructor of this instance.
*/
private final MessageInterpolator messageInterpolator;
/**
* {@link TraversableResolver} as passed to the constructor of this instance.
* Never use it directly, always use {@link #getCachingTraversableResolver()} to retrieved the single threaded caching wrapper.
*/
private final TraversableResolver traversableResolver;
/**
* Passed at creation time of this validator instance.
*/
private final ConstraintHelper constraintHelper;
/**
* Used to get access to the bean meta data. Used to avoid to parsing the constraint configuration for each call
* of a given entity.
*/
private final BeanMetaDataCache beanMetaDataCache;
public ValidatorImpl(ConstraintValidatorFactory constraintValidatorFactory, MessageInterpolator messageInterpolator, TraversableResolver traversableResolver, ConstraintHelper constraintHelper, BeanMetaDataCache beanMetaDataCache) {
this.constraintValidatorFactory = constraintValidatorFactory;
this.messageInterpolator = messageInterpolator;
this.traversableResolver = traversableResolver;
this.constraintHelper = constraintHelper;
this.beanMetaDataCache = beanMetaDataCache;
groupChainGenerator = new GroupChainGenerator();
}
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
if ( object == null ) {
throw new IllegalArgumentException( "Validation of a null object" );
}
GroupChain groupChain = determineGroupExecutionOrder( groups );
GlobalExecutionContext<T> context = GlobalExecutionContext.getContextForValidate(
object, messageInterpolator, constraintValidatorFactory, getCachingTraversableResolver()
);
List<ConstraintViolation<T>> list = validateInContext( object, context, groupChain, null );
return new HashSet<ConstraintViolation<T>>( list );
}
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
if ( object == null ) {
throw new IllegalArgumentException( "Validated object cannot be null." );
}
sanityCheckPropertyPath( propertyName );
GroupChain groupChain = determineGroupExecutionOrder( groups );
List<ConstraintViolation<T>> failingConstraintViolations = new ArrayList<ConstraintViolation<T>>();
validateProperty(
object, PathImpl.createPathFromString( propertyName ), failingConstraintViolations, groupChain
);
return new HashSet<ConstraintViolation<T>>( failingConstraintViolations );
}
public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
if ( beanType == null ) {
throw new IllegalArgumentException( "The bean type cannot be null." );
}
sanityCheckPropertyPath( propertyName );
GroupChain groupChain = determineGroupExecutionOrder( groups );
List<ConstraintViolation<T>> failingConstraintViolations = new ArrayList<ConstraintViolation<T>>();
validateValue(
beanType, value, PathImpl.createPathFromString( propertyName ), failingConstraintViolations, groupChain
);
return new HashSet<ConstraintViolation<T>>( failingConstraintViolations );
}
/**
* {@inheritDoc}
*/
public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
return getBeanMetaData( clazz ).getBeanDescriptor();
}
public <T> T unwrap(Class<T> type) {
throw new ValidationException( "Type " + type + " not supported" );
}
private void sanityCheckPropertyPath(String propertyName) {
if ( propertyName == null || propertyName.length() == 0 ) {
throw new IllegalArgumentException( "Invalid property path." );
}
}
private GroupChain determineGroupExecutionOrder(Class<?>[] groups) {
if ( groups == null ) {
throw new IllegalArgumentException( "null passed as group name" );
}
// if no groups is specified use the default
if ( groups.length == 0 ) {
groups = DEFAULT_GROUP_ARRAY;
}
return groupChainGenerator.getGroupChainFor( Arrays.asList( groups ) );
}
/**
* Validates the given object using the available context information.
*
* @param value The value to validate.
* @param context Global context. Used amongst others to collect all failing constraints.
* @param groupChain Contains the information which and in which order groups have to be executed
* @param path The current path of the validation.
* @param <T> The root bean type.
* @param <V> The type of the current object on the validation stack.
*
* @return List of constraint violations or the empty set if there were no violations.
*/
private <T, U, V> List<ConstraintViolation<T>> validateInContext(U value, GlobalExecutionContext<T> context, GroupChain groupChain, PathImpl path) {
if ( value == null ) {
return Collections.emptyList();
}
path = PathImpl.createShallowCopy( path );
LocalExecutionContext<U, V> localExecutionContext = LocalExecutionContext.getLocalExecutionContext( value );
BeanMetaData<U> beanMetaData = getBeanMetaData( localExecutionContext.getCurrentBeanType() );
if ( beanMetaData.defaultGroupSequenceIsRedefined() ) {
groupChain.assertDefaultGroupSequenceIsExpandable( beanMetaData.getDefaultGroupSequence() );
}
// process first single groups. For these we can skip some object traversal, by first running all validations on the current bean
// before traversing the object.
Iterator<Group> groupIterator = groupChain.getGroupIterator();
while ( groupIterator.hasNext() ) {
Group group = groupIterator.next();
localExecutionContext.setCurrentGroup( group.getGroup() );
validateConstraintsForCurrentGroup( context, localExecutionContext, path );
}
groupIterator = groupChain.getGroupIterator();
while ( groupIterator.hasNext() ) {
Group group = groupIterator.next();
localExecutionContext.setCurrentGroup( group.getGroup() );
validateCascadedConstraints( context, localExecutionContext, path );
}
// now we process sequences. For sequences I have to traverse the object graph since I have to stop processing when an error occurs.
Iterator<List<Group>> sequenceIterator = groupChain.getSequenceIterator();
while ( sequenceIterator.hasNext() ) {
List<Group> sequence = sequenceIterator.next();
for ( Group group : sequence ) {
int numberOfViolations = context.getFailingConstraints().size();
localExecutionContext.setCurrentGroup( group.getGroup() );
validateConstraintsForCurrentGroup( context, localExecutionContext, path );
validateCascadedConstraints( context, localExecutionContext, path );
if ( context.getFailingConstraints().size() > numberOfViolations ) {
break;
}
}
}
return context.getFailingConstraints();
}
private <T, U, V> void validateConstraintsForCurrentGroup(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path) {
BeanMetaData<U> beanMetaData = getBeanMetaData( localExecutionContext.getCurrentBeanType() );
boolean validatingDefault = localExecutionContext.validatingDefault();
boolean validatedBeanRedefinesDefault = beanMetaData.defaultGroupSequenceIsRedefined();
// if we are not validating the default group there is nothing special to consider
if ( !validatingDefault ) {
validateConstraintsForNonDefaultGroup( globalExecutionContext, localExecutionContext, path );
return;
}
// if we are validating the default group we have to distinguish between the case where the main entity type redefines the default group and
// where not
if ( validatedBeanRedefinesDefault ) {
validateConstraintsForRedefinedDefaultGroupOnMainEntity(
globalExecutionContext, localExecutionContext, path, beanMetaData
);
}
else {
validateConstraintsForRedefinedDefaultGroup(
globalExecutionContext, localExecutionContext, path, beanMetaData
);
}
}
private <T, U, V> void validateConstraintsForRedefinedDefaultGroup(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path, BeanMetaData<U> beanMetaData) {
// in the case where the main entity does not redefine the default group we have to check whether the entity which defines the constraint does
for ( Map.Entry<Class<?>, List<MetaConstraint<U, ? extends Annotation>>> entry : beanMetaData.getMetaConstraintsAsMap()
.entrySet() ) {
Class<?> hostingBeanClass = entry.getKey();
List<MetaConstraint<U, ? extends Annotation>> constraints = entry.getValue();
List<Class<?>> defaultGroupSequence = getBeanMetaData( hostingBeanClass ).getDefaultGroupSequence();
for ( Class<?> defaultSequenceMember : defaultGroupSequence ) {
localExecutionContext.setCurrentGroup( defaultSequenceMember );
boolean validationSuccessful = true;
for ( MetaConstraint<U, ? extends Annotation> metaConstraint : constraints ) {
boolean tmp = validateConstraint(
globalExecutionContext, localExecutionContext, metaConstraint, path
);
validationSuccessful = validationSuccessful && tmp;
}
if ( !validationSuccessful ) {
break;
}
}
}
}
private <T, U, V> void validateConstraintsForRedefinedDefaultGroupOnMainEntity(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path, BeanMetaData<U> beanMetaData) {
// in the case where the main entity redefines the default group we can iterate over all constraints independent of the bean they are
// defined in. The redefined group sequence applies for all constraints.
List<Class<?>> defaultGroupSequence = beanMetaData.getDefaultGroupSequence();
for ( Class<?> defaultSequenceMember : defaultGroupSequence ) {
localExecutionContext.setCurrentGroup( defaultSequenceMember );
boolean validationSuccessful = true;
for ( MetaConstraint<U, ? extends Annotation> metaConstraint : beanMetaData.getMetaConstraintsAsList() ) {
boolean tmp = validateConstraint(
globalExecutionContext, localExecutionContext, metaConstraint, path
);
validationSuccessful = validationSuccessful && tmp;
}
if ( !validationSuccessful ) {
break;
}
}
}
private <T, U, V> void validateConstraintsForNonDefaultGroup(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path) {
BeanMetaData<U> beanMetaData = getBeanMetaData( localExecutionContext.getCurrentBeanType() );
for ( MetaConstraint<U, ? extends Annotation> metaConstraint : beanMetaData.getMetaConstraintsAsList() ) {
validateConstraint( globalExecutionContext, localExecutionContext, metaConstraint, path );
}
}
private <T, U, V> boolean validateConstraint(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, MetaConstraint<U, ?> metaConstraint, PathImpl path) {
boolean validationSuccessful = true;
PathImpl newPath;
if ( path == null ) {
newPath = PathImpl.createNewPath( metaConstraint.getPropertyName() );
}
else {
newPath = PathImpl.createShallowCopy( path );
if ( metaConstraint.getElementType() != ElementType.TYPE ) {
newPath.addNode( new NodeImpl( metaConstraint.getPropertyName() ) );
}
}
localExecutionContext.setPropertyPath( newPath );
if ( isValidationRequired( globalExecutionContext, localExecutionContext, metaConstraint ) ) {
Object valueToValidate = metaConstraint.getValue( localExecutionContext.getCurrentBean() );
localExecutionContext.setCurrentValidatedValue( ( V ) valueToValidate );
validationSuccessful = metaConstraint.validateConstraint( globalExecutionContext, localExecutionContext );
}
globalExecutionContext.markProcessed(
localExecutionContext.getCurrentBean(),
localExecutionContext.getCurrentGroup(),
localExecutionContext.getPropertyPath()
);
return validationSuccessful;
}
/**
* Validates all cascaded constraints for the given bean using the current group set in the execution context.
* This method must always be called after validateConstraints for the same context.
*
* @param globalExecutionContext The execution context
* @param localExecutionContext Collected information for single validation
* @param path The current path of the validation.
*/
private <T, U, V> void validateCascadedConstraints(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path) {
List<Member> cascadedMembers = getBeanMetaData( localExecutionContext.getCurrentBeanType() )
.getCascadedMembers();
for ( Member member : cascadedMembers ) {
Type type = ReflectionHelper.typeOf( member );
PathImpl newPath;
if ( path == null ) {
newPath = PathImpl.createNewPath( ReflectionHelper.getPropertyName( member ) );
}
else {
newPath = PathImpl.createShallowCopy( path );
newPath.addNode( new NodeImpl( ReflectionHelper.getPropertyName( member ) ) );
}
localExecutionContext.setPropertyPath( newPath );
if ( isCascadeRequired( globalExecutionContext, localExecutionContext, member ) ) {
Object value = ReflectionHelper.getValue( member, localExecutionContext.getCurrentBean() );
if ( value != null ) {
Iterator<?> iter = createIteratorForCascadedValue( localExecutionContext, type, value );
boolean isIndexable = isIndexable( type );
validateCascadedConstraint(
globalExecutionContext,
iter,
isIndexable,
localExecutionContext.getCurrentGroup(),
localExecutionContext.getPropertyPath()
);
}
}
}
}
/**
* Called when processing cascaded constraints. This methods inspects the type of the cascaded constraints and in case
* of a list or array creates an iterator in order to validate each element.
*
* @param context the validation context.
* @param type the type of the cascaded field or property.
* @param value the actual value.
*
* @return An iterator over the value of a cascaded property.
*/
private <U, V> Iterator<?> createIteratorForCascadedValue(LocalExecutionContext<U, V> context, Type type, Object value) {
Iterator<?> iter;
if ( ReflectionHelper.isIterable( type ) ) {
iter = ( ( Iterable<?> ) value ).iterator();
context.markCurrentPropertyAsIterable();
}
else if ( ReflectionHelper.isMap( type ) ) {
Map<?, ?> map = ( Map<?, ?> ) value;
iter = map.entrySet().iterator();
context.markCurrentPropertyAsIterable();
}
else if ( TypeUtils.isArray( type ) ) {
List<?> arrayList = Arrays.asList( ( Object[] ) value );
iter = arrayList.iterator();
context.markCurrentPropertyAsIterable();
}
else {
List<Object> list = new ArrayList<Object>();
list.add( value );
iter = list.iterator();
}
return iter;
}
/**
* Called when processing cascaded constraints. This methods inspects the type of the cascaded constraints and in case
* of a list or array creates an iterator in order to validate each element.
*
* @param type the type of the cascaded field or property.
*
* @return An iterator over the value of a cascaded property.
*/
private boolean isIndexable(Type type) {
boolean isIndexable = false;
if ( ReflectionHelper.isList( type ) ) {
isIndexable = true;
}
else if ( ReflectionHelper.isMap( type ) ) {
isIndexable = true;
}
else if ( TypeUtils.isArray( type ) ) {
isIndexable = true;
}
return isIndexable;
}
@SuppressWarnings("RedundantArrayCreation")
private <T> void validateCascadedConstraint(GlobalExecutionContext<T> context, Iterator<?> iter, boolean isIndexable, Class<?> currentGroup, PathImpl currentPath) {
Object value;
Integer index;
Object mapKey;
int i = 0;
while ( iter.hasNext() ) {
value = iter.next();
index = i;
if ( value instanceof Map.Entry ) {
mapKey = ( ( Map.Entry ) value ).getKey();
value = ( ( Map.Entry ) value ).getValue();
currentPath.getLeafNode().setKey( mapKey );
}
else if ( isIndexable ) {
currentPath.getLeafNode().setIndex( index );
}
if ( !context.isAlreadyValidated( value, currentGroup, currentPath ) ) {
GroupChain groupChain = groupChainGenerator.getGroupChainFor(
Arrays.asList(
new Class<?>[] {
currentGroup
}
)
);
validateInContext( value, context, groupChain, currentPath );
}
i++;
}
}
private <T> void validateProperty(T object, PathImpl propertyPath, List<ConstraintViolation<T>> failingConstraintViolations, GroupChain groupChain) {
@SuppressWarnings("unchecked")
final Class<T> beanType = ( Class<T> ) object.getClass();
Set<MetaConstraint<T, ?>> metaConstraints = new HashSet<MetaConstraint<T, ?>>();
Object hostingBeanInstance = collectMetaConstraintsForPath(
beanType, object, propertyPath.iterator(), metaConstraints
);
if ( hostingBeanInstance == null ) {
throw new IllegalArgumentException( "Invalid property path." );
}
if ( metaConstraints.size() == 0 ) {
return;
}
//this method is at the root of validateProperty calls, share the same cachedTR
TraversableResolver cachedResolver = getCachingTraversableResolver();
Iterator<Group> groupIterator = groupChain.getGroupIterator();
while ( groupIterator.hasNext() ) {
Group group = groupIterator.next();
validatePropertyForGroup(
object,
propertyPath,
failingConstraintViolations,
metaConstraints,
hostingBeanInstance,
group,
cachedResolver
);
}
Iterator<List<Group>> sequenceIterator = groupChain.getSequenceIterator();
while ( sequenceIterator.hasNext() ) {
List<Group> sequence = sequenceIterator.next();
int numberOfConstraintViolationsBefore = failingConstraintViolations.size();
for ( Group group : sequence ) {
validatePropertyForGroup(
object,
propertyPath,
failingConstraintViolations,
metaConstraints,
hostingBeanInstance,
group,
cachedResolver
);
if ( failingConstraintViolations.size() > numberOfConstraintViolationsBefore ) {
break;
}
}
}
}
private <T, U, V> void validatePropertyForGroup(
T object,
PathImpl path,
List<ConstraintViolation<T>> failingConstraintViolations,
Set<MetaConstraint<T, ?>> metaConstraints,
U hostingBeanInstance,
Group group,
TraversableResolver cachedTraversableResolver) {
int numberOfConstraintViolationsBefore = failingConstraintViolations.size();
BeanMetaData<T> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
List<Class<?>> groupList;
if ( group.isDefaultGroup() ) {
groupList = beanMetaData.getDefaultGroupSequence();
}
else {
groupList = new ArrayList<Class<?>>();
groupList.add( group.getGroup() );
}
for ( Class<?> groupClass : groupList ) {
for ( MetaConstraint<T, ?> metaConstraint : metaConstraints ) {
GlobalExecutionContext<T> context = GlobalExecutionContext.getContextForValidateProperty(
object,
messageInterpolator,
constraintValidatorFactory,
cachedTraversableResolver
);
LocalExecutionContext<U, V> localContext = LocalExecutionContext.getLocalExecutionContext(
hostingBeanInstance
);
localContext.setPropertyPath( path );
localContext.setCurrentGroup( groupClass );
if ( isValidationRequired( context, localContext, metaConstraint ) ) {
Object valueToValidate = metaConstraint.getValue( localContext.getCurrentBean() );
localContext.setCurrentValidatedValue( ( V ) valueToValidate );
metaConstraint.validateConstraint( context, localContext );
failingConstraintViolations.addAll( context.getFailingConstraints() );
}
}
if ( failingConstraintViolations.size() > numberOfConstraintViolationsBefore ) {
break;
}
}
}
@SuppressWarnings("unchecked")
private <T> void validateValue(Class<T> beanType, Object value, PathImpl propertyPath, List<ConstraintViolation<T>> failingConstraintViolations, GroupChain groupChain) {
Set<MetaConstraint<T, ?>> metaConstraints = new HashSet<MetaConstraint<T, ?>>();
collectMetaConstraintsForPath( beanType, null, propertyPath.iterator(), metaConstraints );
if ( metaConstraints.size() == 0 ) {
return;
}
//root of validateValue calls, share the same cached TraversableResolver
TraversableResolver cachedTraversableResolver = getCachingTraversableResolver();
// process groups
Iterator<Group> groupIterator = groupChain.getGroupIterator();
while ( groupIterator.hasNext() ) {
Group group = groupIterator.next();
validateValueForGroup(
beanType,
value,
propertyPath,
failingConstraintViolations,
metaConstraints,
group,
cachedTraversableResolver
);
}
// process sequences
Iterator<List<Group>> sequenceIterator = groupChain.getSequenceIterator();
while ( sequenceIterator.hasNext() ) {
List<Group> sequence = sequenceIterator.next();
int numberOfConstraintViolations = failingConstraintViolations.size();
for ( Group group : sequence ) {
validateValueForGroup(
beanType,
value,
propertyPath,
failingConstraintViolations,
metaConstraints,
group,
cachedTraversableResolver
);
if ( failingConstraintViolations.size() > numberOfConstraintViolations ) {
break;
}
}
}
}
private <U, V> void validateValueForGroup(
Class<U> beanType,
V value,
PathImpl path,
List<ConstraintViolation<U>> failingConstraintViolations,
Set<MetaConstraint<U, ?>> metaConstraints,
Group group,
TraversableResolver cachedTraversableResolver) {
int numberOfConstraintViolations = failingConstraintViolations.size();
BeanMetaData<U> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
List<Class<?>> groupList;
if ( group.isDefaultGroup() ) {
groupList = beanMetaData.getDefaultGroupSequence();
}
else {
groupList = new ArrayList<Class<?>>();
groupList.add( group.getGroup() );
}
for ( Class<?> groupClass : groupList ) {
for ( MetaConstraint<U, ?> metaConstraint : metaConstraints ) {
GlobalExecutionContext<U> context = GlobalExecutionContext.getContextForValidateValue(
beanType, messageInterpolator, constraintValidatorFactory, cachedTraversableResolver
);
LocalExecutionContext<U, V> localContext = LocalExecutionContext.getLocalExecutionContext( beanType );
localContext.setPropertyPath( path );
localContext.setCurrentGroup( groupClass );
localContext.setCurrentValidatedValue( value );
if ( isValidationRequired( context, localContext, metaConstraint ) ) {
metaConstraint.validateConstraint( context, localContext );
failingConstraintViolations.addAll( context.getFailingConstraints() );
}
}
if ( failingConstraintViolations.size() > numberOfConstraintViolations ) {
break;
}
}
}
/**
* Collects all <code>MetaConstraint</code>s which match the given path relative to the specified root class.
* <p>
* This method is called recursively.
* </p>
*
* @param clazz the class type to check for constraints.
* @param value While resolving the property path this instance points to the current object. Might be <code>null</code>.
* @param propertyIter an instance of <code>PropertyIterator</code> in order to iterate the items of the original property path.
* @param metaConstraints Set of <code>MetaConstraint</code>s to collect all matching constraints.
*
* @return Returns the bean hosting the constraints which match the specified property path.
*/
private <T> Object collectMetaConstraintsForPath(Class<T> clazz, Object value, Iterator<Path.Node> propertyIter, Set<MetaConstraint<T, ?>> metaConstraints) {
Path.Node elem = propertyIter.next();
final BeanMetaData<T> metaData = getBeanMetaData( clazz );
//use precomputed method list as ReflectionHelper#containsMember is slow
if ( !metaData.isPropertyPresent( elem.getName() ) ) {
throw new IllegalArgumentException(
"Invalid property path. There is no property " + elem.getName() + " in entity " + metaData.getBeanClass()
.getName()
);
}
if ( !propertyIter.hasNext() ) {
List<MetaConstraint<T, ? extends Annotation>> metaConstraintList = metaData.getMetaConstraintsAsList();
for ( MetaConstraint<T, ?> metaConstraint : metaConstraintList ) {
if ( elem.getName() != null && elem.getName().equals( metaConstraint.getPropertyName() ) ) {
metaConstraints.add( metaConstraint );
}
}
}
else {
List<Member> cascadedMembers = metaData.getCascadedMembers();
for ( Member m : cascadedMembers ) {
if ( ReflectionHelper.getPropertyName( m ).equals( elem.getName() ) ) {
Type type = ReflectionHelper.typeOf( m );
value = value == null ? null : ReflectionHelper.getValue( m, value );
if ( elem.isInIterable() ) {
if ( value != null && elem.getIndex() != null ) {
value = ReflectionHelper.getIndexedValue( value, elem.getIndex() );
}
else if ( value != null && elem.getKey() != null ) {
value = ReflectionHelper.getMappedValue( value, elem.getKey() );
}
else if ( value != null ) {
throw new IllegalArgumentException( "Property path must provide index or map key" );
}
type = ReflectionHelper.getIndexedType( type );
}
@SuppressWarnings("unchecked")
Class<T> valueClass = ( Class<T> ) ( value == null ? type : value.getClass() );
return collectMetaConstraintsForPath(
valueClass,
value,
propertyIter,
metaConstraints
);
}
}
}
return value;
}
private <U> BeanMetaData<U> getBeanMetaData(Class<U> beanClass) {
BeanMetaDataImpl<U> beanMetaData = beanMetaDataCache.getBeanMetaData( beanClass );
if ( beanMetaData == null ) {
beanMetaData = new BeanMetaDataImpl<U>( beanClass, constraintHelper, beanMetaDataCache );
beanMetaDataCache.addBeanMetaData( beanClass, beanMetaData );
}
return beanMetaData;
}
/**
* Must be called and stored for the duration of the stack call
* A new instance is returned each time
*
* @return The resolver for the duration of a full validation.
*/
private TraversableResolver getCachingTraversableResolver() {
return new SingleThreadCachedTraversableResolver( traversableResolver );
}
private boolean isValidationRequired(GlobalExecutionContext globalContext, LocalExecutionContext localContext, MetaConstraint metaConstraint) {
if ( !metaConstraint.getGroupList().contains( localContext.getCurrentGroup() ) ) {
return false;
}
boolean isReachable;
Path pathToObject = localContext.getPropertyPath().getPathWithoutLeafNode();
if ( pathToObject == null ) {
pathToObject = PathImpl.createNewPath( null );
}
try {
isReachable = globalContext.getTraversableResolver().isReachable(
localContext.getCurrentBean(),
localContext.getPropertyPath().getLeafNode(),
globalContext.getRootBeanClass(),
pathToObject,
metaConstraint.getElementType()
);
}
catch ( RuntimeException e ) {
throw new ValidationException( "Call to TraversableResolver.isReachable() threw an exception", e );
}
return isReachable;
}
private boolean isCascadeRequired(GlobalExecutionContext globalContext, LocalExecutionContext localContext, Member member) {
final ElementType type = member instanceof Field ? ElementType.FIELD : ElementType.METHOD;
boolean isReachable;
boolean isCascadable;
Path pathToObject = localContext.getPropertyPath().getPathWithoutLeafNode();
if ( pathToObject == null ) {
pathToObject = PathImpl.createNewPath( null );
}
try {
isReachable = globalContext.getTraversableResolver().isReachable(
localContext.getCurrentBean(),
localContext.getPropertyPath().getLeafNode(),
globalContext.getRootBeanClass(),
pathToObject,
type
);
}
catch ( RuntimeException e ) {
throw new ValidationException( "Call to TraversableResolver.isReachable() threw an exception", e );
}
try {
isCascadable = globalContext.getTraversableResolver().isCascadable(
localContext.getCurrentBean(),
localContext.getPropertyPath().getLeafNode(),
globalContext.getRootBeanClass(),
pathToObject,
type
);
}
catch ( RuntimeException e ) {
throw new ValidationException( "Call to TraversableResolver.isCascadable() threw an exception", e );
}
return isReachable && isCascadable;
}
}