/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.internal.engine.constraintvalidation; import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintValidatorFactory; import javax.validation.constraints.Null; import javax.validation.metadata.ConstraintDescriptor; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.TypeHelper; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; /** * Manager in charge of providing and caching initialized {@code ConstraintValidator} instances. * * @author Hardy Ferentschik */ public class ConstraintValidatorManager { private static final Log LOG = LoggerFactory.make(); /** * Dummy {@code ConstraintValidator} used as placeholder for the case that for a given context there exists * no matching constraint validator instance */ private static ConstraintValidator<?, ?> DUMMY_CONSTRAINT_VALIDATOR = new ConstraintValidator<Null, Object>() { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { return false; } }; /** * The explicit or implicit default constraint validator factory. We always cache {@code ConstraintValidator} instances * if they are created via the default instance. Constraint validator instances created via other factory * instances (specified eg via {@code ValidatorFactory#usingContext()} are only cached for the least recently used * factory */ private final ConstraintValidatorFactory defaultConstraintValidatorFactory; /** * The most recently used non default constraint validator factory. */ private volatile ConstraintValidatorFactory mostRecentlyUsedNonDefaultConstraintValidatorFactory; /** * Used for synchronizing access to {@link #mostRecentlyUsedNonDefaultConstraintValidatorFactory} (which can be * null itself). */ private final Object mostRecentlyUsedNonDefaultConstraintValidatorFactoryMutex = new Object(); /** * Cache of initialized {@code ConstraintValidator} instances keyed against validates type, annotation and * constraint validator factory ({@code CacheKey}). */ private final ConcurrentHashMap<CacheKey, ConstraintValidator<?, ?>> constraintValidatorCache; /** * Creates a new {@code ConstraintValidatorManager}. * * @param constraintValidatorFactory the validator factory */ public ConstraintValidatorManager(ConstraintValidatorFactory constraintValidatorFactory) { this.defaultConstraintValidatorFactory = constraintValidatorFactory; this.constraintValidatorCache = new ConcurrentHashMap<>(); } /** * @param validatedValueType the type of the value to be validated. Cannot be {@code null}. * @param descriptor the constraint descriptor for which to get an initialized constraint validator. Cannot be {@code null} * @param constraintFactory constraint factory used to instantiate the constraint validator. Cannot be {@code null}. * @param <A> the annotation type * * @return an initialized constraint validator for the given type and annotation of the value to be validated. * {@code null} is returned if no matching constraint validator could be found. */ public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(Type validatedValueType, ConstraintDescriptorImpl<A> descriptor, ConstraintValidatorFactory constraintFactory) { Contracts.assertNotNull( validatedValueType ); Contracts.assertNotNull( descriptor ); Contracts.assertNotNull( constraintFactory ); CacheKey key = new CacheKey( descriptor.getAnnotation(), validatedValueType, constraintFactory ); @SuppressWarnings("unchecked") ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key ); if ( constraintValidator == null ) { constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintFactory ); constraintValidator = cacheValidator( key, constraintValidator ); } else { LOG.tracef( "Constraint validator %s found in cache.", constraintValidator ); } return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator; } private <A extends Annotation> ConstraintValidator<A, ?> cacheValidator(CacheKey key, ConstraintValidator<A, ?> constraintValidator) { // we only cache constraint validator instances for the default and most recently used factory if ( key.constraintFactory != defaultConstraintValidatorFactory && key.constraintFactory != mostRecentlyUsedNonDefaultConstraintValidatorFactory ) { synchronized ( mostRecentlyUsedNonDefaultConstraintValidatorFactoryMutex ) { if ( key.constraintFactory != mostRecentlyUsedNonDefaultConstraintValidatorFactory ) { clearEntriesForFactory( mostRecentlyUsedNonDefaultConstraintValidatorFactory ); mostRecentlyUsedNonDefaultConstraintValidatorFactory = key.constraintFactory; } } } @SuppressWarnings("unchecked") ConstraintValidator<A, ?> cached = (ConstraintValidator<A, ?>) constraintValidatorCache.putIfAbsent( key, constraintValidator ); return cached != null ? cached : constraintValidator; } @SuppressWarnings("unchecked") private <A extends Annotation> ConstraintValidator<A, ?> createAndInitializeValidator(Type validatedValueType, ConstraintDescriptorImpl<A> descriptor, ConstraintValidatorFactory constraintFactory) { ConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType ); ConstraintValidator<A, ?> constraintValidator; if ( validatorDescriptor == null ) { constraintValidator = (ConstraintValidator<A, ?>) DUMMY_CONSTRAINT_VALIDATOR; } else { constraintValidator = validatorDescriptor.newInstance( constraintFactory ); initializeValidator( descriptor, constraintValidator ); } return constraintValidator; } private void clearEntriesForFactory(ConstraintValidatorFactory constraintFactory) { Iterator<Entry<CacheKey, ConstraintValidator<?, ?>>> cacheEntries = constraintValidatorCache.entrySet().iterator(); while ( cacheEntries.hasNext() ) { Entry<CacheKey, ConstraintValidator<?, ?>> cacheEntry = cacheEntries.next(); if ( cacheEntry.getKey().getConstraintFactory() == constraintFactory ) { constraintFactory.releaseInstance( cacheEntry.getValue() ); cacheEntries.remove(); } } } public void clear() { for ( Map.Entry<CacheKey, ConstraintValidator<?, ?>> entry : constraintValidatorCache.entrySet() ) { entry.getKey().getConstraintFactory().releaseInstance( entry.getValue() ); } constraintValidatorCache.clear(); } public ConstraintValidatorFactory getDefaultConstraintValidatorFactory() { return defaultConstraintValidatorFactory; } public int numberOfCachedConstraintValidatorInstances() { return constraintValidatorCache.size(); } /** * Runs the validator resolution algorithm. * * @param validatedValueType The type of the value to be validated (the type of the member/class the constraint was placed on). * * @return The class of a matching validator. */ private <A extends Annotation> ConstraintValidatorDescriptor<A> findMatchingValidatorDescriptor(ConstraintDescriptorImpl<A> descriptor, Type validatedValueType) { Map<Type, ConstraintValidatorDescriptor<A>> availableValidatorDescriptors = TypeHelper.getValidatorTypes( descriptor.getAnnotationType(), descriptor.getMatchingConstraintValidatorDescriptors() ); List<Type> discoveredSuitableTypes = findSuitableValidatorTypes( validatedValueType, availableValidatorDescriptors.keySet() ); resolveAssignableTypes( discoveredSuitableTypes ); if ( discoveredSuitableTypes.size() == 0 ) { return null; } if ( discoveredSuitableTypes.size() > 1 ) { throw LOG.getMoreThanOneValidatorFoundForTypeException( validatedValueType, discoveredSuitableTypes ); } Type suitableType = discoveredSuitableTypes.get( 0 ); return availableValidatorDescriptors.get( suitableType ); } private <A extends Annotation> List<Type> findSuitableValidatorTypes(Type type, Iterable<Type> availableValidatorTypes) { List<Type> determinedSuitableTypes = newArrayList(); for ( Type validatorType : availableValidatorTypes ) { if ( TypeHelper.isAssignable( validatorType, type ) && !determinedSuitableTypes.contains( validatorType ) ) { determinedSuitableTypes.add( validatorType ); } } return determinedSuitableTypes; } private <A extends Annotation> void initializeValidator(ConstraintDescriptor<A> descriptor, ConstraintValidator<A, ?> constraintValidator) { try { constraintValidator.initialize( descriptor.getAnnotation() ); } catch (RuntimeException e) { throw LOG.getUnableToInitializeConstraintValidatorException( constraintValidator.getClass(), e ); } } /** * Tries to reduce all assignable classes down to a single class. * * @param assignableTypes The set of all classes which are assignable to the class of the value to be validated and * which are handled by at least one of the validators for the specified constraint. */ private void resolveAssignableTypes(List<Type> assignableTypes) { if ( assignableTypes.size() == 0 || assignableTypes.size() == 1 ) { return; } List<Type> typesToRemove = new ArrayList<>(); do { typesToRemove.clear(); Type type = assignableTypes.get( 0 ); for ( int i = 1; i < assignableTypes.size(); i++ ) { if ( TypeHelper.isAssignable( type, assignableTypes.get( i ) ) ) { typesToRemove.add( type ); } else if ( TypeHelper.isAssignable( assignableTypes.get( i ), type ) ) { typesToRemove.add( assignableTypes.get( i ) ); } } assignableTypes.removeAll( typesToRemove ); } while ( typesToRemove.size() > 0 ); } private static final class CacheKey { private final Annotation annotation; private final Type validatedType; private final ConstraintValidatorFactory constraintFactory; private final int hashCode; private CacheKey(Annotation annotation, Type validatorType, ConstraintValidatorFactory constraintFactory) { this.annotation = annotation; this.validatedType = validatorType; this.constraintFactory = constraintFactory; this.hashCode = createHashCode(); } public ConstraintValidatorFactory getConstraintFactory() { return constraintFactory; } @Override public boolean equals(Object o) { if ( this == o ) { return true; } if ( o == null || getClass() != o.getClass() ) { return false; } CacheKey cacheKey = (CacheKey) o; if ( annotation != null ? !annotation.equals( cacheKey.annotation ) : cacheKey.annotation != null ) { return false; } if ( constraintFactory != null ? !constraintFactory.equals( cacheKey.constraintFactory ) : cacheKey.constraintFactory != null ) { return false; } if ( validatedType != null ? !validatedType.equals( cacheKey.validatedType ) : cacheKey.validatedType != null ) { return false; } return true; } @Override public int hashCode() { return hashCode; } private int createHashCode() { int result = annotation != null ? annotation.hashCode() : 0; result = 31 * result + ( validatedType != null ? validatedType.hashCode() : 0 ); result = 31 * result + ( constraintFactory != null ? constraintFactory.hashCode() : 0 ); return result; } } }