Package org.hibernate.validator.internal.engine.constraintvalidation

Source Code of org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager$CacheKey

/*
* 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 java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.metadata.ConstraintDescriptor;

import org.hibernate.validator.internal.engine.ValueContext;
import org.hibernate.validator.internal.engine.valuehandling.UnwrapMode;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.StringHelper;
import org.hibernate.validator.internal.util.TypeHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.spi.valuehandling.ValidatedValueUnwrapper;

import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;

/**
* Manager in charge of providing and caching initialized {@code ConstraintValidator} instances.
*
* @author Hardy Ferentschik
*/
public class ConstraintValidatorManager {
  private static final Log log = LoggerFactory.make();

  /**
   * 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 least recently used non default constraint validator factory.
   */
  private ConstraintValidatorFactory leastRecentlyUsedNonDefaultConstraintValidatorFactory;

  /**
   * Cache of initalized {@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<CacheKey, ConstraintValidator<?, ?>>();
  }

  /**
   * @param valueContext the current value context. Cannot be {@code null} including the validated type.
   * @param descriptor the constraint descriptor for which to get an initalized constraint validator. Cannot be {@code null}
   * @param constraintValidatorFactory constraint factory used to instantiate the constraint validator. Cannot be {@code null}.
   * Could be {@code null}.
   * @param <V> the type of the value to be validated
   * @param <A> the annotation type
   *
   * @return an initialized constraint validator for the given type and annotation of the value to be validated.
   */
  public <V, A extends Annotation> ConstraintValidator<A, V> getInitializedValidator(ValueContext<?, V> valueContext,
                                             ConstraintDescriptorImpl<A> descriptor,
                                             ConstraintValidatorFactory constraintValidatorFactory) {
    Contracts.assertNotNull( valueContext );
    Type typeOfValidatedElement = valueContext.getDeclaredTypeOfValidatedElement();
    Contracts.assertNotNull( typeOfValidatedElement );
    Contracts.assertNotNull( descriptor );
    Contracts.assertNotNull( constraintValidatorFactory );

    UnwrapMode valueUnwrapMode = valueContext.getUnwrapMode();
    final CacheKey key = new CacheKey(
        descriptor.getAnnotation(),
        typeOfValidatedElement,
        constraintValidatorFactory,
        valueUnwrapMode
    );

    @SuppressWarnings("unchecked")
    ConstraintValidator<A, V> constraintValidator = (ConstraintValidator<A, V>) constraintValidatorCache.get( key );

    if ( constraintValidator == null ) {
      Class<? extends ConstraintValidator<?, ?>> validatorClass = findMatchingValidatorClass(
          valueContext,
          descriptor
      );
      constraintValidator = createAndInitializeValidator(
          constraintValidatorFactory,
          validatorClass,
          descriptor
      );
      putInitializedValidator(
          typeOfValidatedElement,
          descriptor.getAnnotation(),
          constraintValidatorFactory,
          constraintValidator,
          valueUnwrapMode

      );
    }
    else {
      log.tracef( "Constraint validator %s found in cache.", constraintValidator );
    }

    return constraintValidator;
  }


  private void putInitializedValidator(Type validatedValueType,
                     Annotation annotation,
                     ConstraintValidatorFactory constraintFactory,
                     ConstraintValidator<?, ?> constraintValidator,
                     UnwrapMode unwrapMode) {
    // we only cache constraint validator instance for the default and least recently used factory
    if ( constraintFactory != defaultConstraintValidatorFactory && constraintFactory != leastRecentlyUsedNonDefaultConstraintValidatorFactory ) {
      clearEntriesForFactory( leastRecentlyUsedNonDefaultConstraintValidatorFactory );
      leastRecentlyUsedNonDefaultConstraintValidatorFactory = constraintFactory;
    }

    final CacheKey key = new CacheKey(
        annotation,
        validatedValueType,
        constraintFactory,
        unwrapMode
    );

    constraintValidatorCache.putIfAbsent( key, constraintValidator );
  }

  private <V, A extends Annotation> ConstraintValidator<A, V> createAndInitializeValidator(
      ConstraintValidatorFactory constraintFactory,
      Class<? extends ConstraintValidator<?, ?>> validatorClass,
      ConstraintDescriptor<A> descriptor) {
    @SuppressWarnings("unchecked")
    ConstraintValidator<A, V> constraintValidator = (ConstraintValidator<A, V>) constraintFactory.getInstance(
        validatorClass
    );
    if ( constraintValidator == null ) {
      throw log.getConstraintFactoryMustNotReturnNullException( validatorClass.getName() );
    }
    initializeConstraint( descriptor, constraintValidator );
    return constraintValidator;
  }

  private void clearEntriesForFactory(ConstraintValidatorFactory constraintFactory) {
    List<CacheKey> entriesToRemove = new ArrayList<CacheKey>();
    for ( Map.Entry<CacheKey, ConstraintValidator<?, ?>> entry : constraintValidatorCache.entrySet() ) {
      if ( entry.getKey().getConstraintFactory() == constraintFactory ) {
        entriesToRemove.add( entry.getKey() );
      }
    }
    for ( CacheKey key : entriesToRemove ) {
      constraintValidatorCache.remove( key );
    }
  }

  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 valueContext The current value context
   * @param descriptor The constraint descriptor for the constraint to be applied
   *
   * @return The class of a matching validator.
   */
  private <A extends Annotation> Class<? extends ConstraintValidator<A, ?>>
  findMatchingValidatorClass(ValueContext<?, ?> valueContext, ConstraintDescriptorImpl<A> descriptor) {
    Map<Type, Class<? extends ConstraintValidator<A, ?>>> availableValidatorTypes = descriptor.getAvailableValidatorTypes();
    Type typeOfValidatedElement = valueContext.getDeclaredTypeOfValidatedElement();

    // find constraint validator classes which can directly validate the value to validate
    List<Type> suitableTypesForValidatedValue = findSuitableValidatorTypes(
        typeOfValidatedElement,
        availableValidatorTypes
    );
    resolveAssignableTypes( suitableTypesForValidatedValue );

    // if we also have a suitable value unwrapper we resolve for the type provided by the unwrapper as well
    List<Type> suitableTypesForWrappedValue;
    ValidatedValueUnwrapper validatedValueHandler = valueContext.getValidatedValueHandler();
    if ( validatedValueHandler != null) {
      Type unwrappedType = validatedValueHandler.getValidatedValueType( typeOfValidatedElement );
      suitableTypesForWrappedValue = findSuitableValidatorTypes( unwrappedType, availableValidatorTypes );
      resolveAssignableTypes( suitableTypesForWrappedValue );
    }
    else {
      suitableTypesForWrappedValue = Collections.emptyList();
    }

    Type suitableType = verifyResolveWasUnique(
        valueContext,
        descriptor,
        suitableTypesForValidatedValue,
        suitableTypesForWrappedValue
    );

    return availableValidatorTypes.get( suitableType );
  }

  private Type verifyResolveWasUnique(ValueContext<?, ?> valueContext,
                    ConstraintDescriptorImpl<?> descriptor,
                    List<Type> constraintValidatorTypesForValidatedValue,
                    List<Type> constraintValidatorTypesForWrappedValue) {
    TypeResolutionResult typeResolutionResult = typeResolutionOutcome(
        constraintValidatorTypesForValidatedValue,
        constraintValidatorTypesForWrappedValue,
        valueContext
    );
    switch ( typeResolutionResult ) {
      case VALIDATORS_FOR_VALIDATED_VALUE_AND_WRAPPED_VALUE: {
        throw log.getConstraintValidatorExistsForWrapperAndWrappedValueException(
            valueContext.getPropertyPath().toString(),
            descriptor.getAnnotationType().getName(),
            valueContext.getValidatedValueHandler().getClass().getName()
        );
      }
      case SINGLE_VALIDATOR_FOR_VALIDATED_VALUE: {
        return constraintValidatorTypesForValidatedValue.get( 0 );
      }
      case MULTIPLE_VALIDATORS_FOR_VALIDATED_VALUE: {
        throw log.getMoreThanOneValidatorFoundForTypeException(
            getValidatedValueTypeForErrorReporting( valueContext ),
            StringHelper.join( constraintValidatorTypesForValidatedValue, "," )
        );
      }
      case SINGLE_VALIDATOR_FOR_WRAPPED_VALUE: {
        return constraintValidatorTypesForWrappedValue.get( 0 );
      }
      case NO_VALIDATORS: {
        if ( descriptor.getConstraintType() == ConstraintType.CROSS_PARAMETER ) {
          throw log.getValidatorForCrossParameterConstraintMustEitherValidateObjectOrObjectArrayException(
              descriptor.getAnnotationType().getName()
          );
        }
        else {
          Type typeOfValidatedElement = getValidatedValueTypeForErrorReporting( valueContext );
          String validatedValueClassName = typeOfValidatedElement.toString();
          if ( typeOfValidatedElement instanceof Class ) {
            Class<?> clazz = (Class<?>) typeOfValidatedElement;
            if ( clazz.isArray() ) {
              validatedValueClassName = clazz.getComponentType().toString() + "[]";
            }
            else {
              validatedValueClassName = clazz.getName();
            }
          }
          throw log.getNoValidatorFoundForTypeException(
              descriptor.getAnnotationType().getName(),
              validatedValueClassName,
              valueContext.getPropertyPath().toString()
          );
        }
      }
      case MULTIPLE_VALIDATORS_FOR_WRAPPED_VALUE: {
        // can currently not happen, because if there are multiple suitable unwrappers the first matching one
        // is chosen. See ValidationContext#getValidatedValueHandler (HF)
        break;
      }
    }
    return null;
  }

  private Type getValidatedValueTypeForErrorReporting(ValueContext<?, ?> valueContext) {
    Type typeOfValidatedElement = valueContext.getDeclaredTypeOfValidatedElement();

    // for the sake of better reporting we unwrap if there is a unwrapper in the context
    if ( valueContext.getValidatedValueHandler() != null ) {
      typeOfValidatedElement = valueContext.getCurrentValidatedValue().getClass();
    }
    return typeOfValidatedElement;
  }

  private TypeResolutionResult typeResolutionOutcome(List<Type> constraintValidatorTypesForValidatedValue,
                             List<Type> constraintValidatorTypesForWrappedValue,
                             ValueContext<?, ?> valueContext) {
    if ( UnwrapMode.UNWRAP.equals( valueContext.getUnwrapMode() )
        && constraintValidatorTypesForWrappedValue.size() == 0 ) {
      throw log.getNoUnwrapperFoundForTypeException( valueContext.getDeclaredTypeOfValidatedElement().toString() );
    }

    if ( constraintValidatorTypesForValidatedValue.size() > 0 && constraintValidatorTypesForWrappedValue.size() > 0 ) {
      switch ( valueContext.getUnwrapMode() ) {
        case AUTOMATIC: {
          return TypeResolutionResult.VALIDATORS_FOR_VALIDATED_VALUE_AND_WRAPPED_VALUE;
        }
        case UNWRAP: {
          return getTypeResolutionResultForWrappedValue( constraintValidatorTypesForWrappedValue );
        }
        case SKIP_UNWRAP: {
          // explicitly set the value handler to null. Is there a better place to do this? (HF)
          valueContext.setValidatedValueHandler( null );
          return getTypeResolutionResultForValidatedValue( valueContext, constraintValidatorTypesForValidatedValue );
        }
      }

    }

    if ( constraintValidatorTypesForWrappedValue.size() == 0 ) {
      return getTypeResolutionResultForValidatedValue( valueContext, constraintValidatorTypesForValidatedValue );
    }

    if ( constraintValidatorTypesForValidatedValue.size() == 0 ) {
      return getTypeResolutionResultForWrappedValue( constraintValidatorTypesForWrappedValue );
    }

    return null;
  }

  private ConstraintValidatorManager.TypeResolutionResult getTypeResolutionResultForWrappedValue(List<Type> constraintValidatorTypesForWrappedValue) {
    if ( constraintValidatorTypesForWrappedValue.size() == 1 ) {
      return ConstraintValidatorManager.TypeResolutionResult.SINGLE_VALIDATOR_FOR_WRAPPED_VALUE;
    }
    else {
      return ConstraintValidatorManager.TypeResolutionResult.MULTIPLE_VALIDATORS_FOR_WRAPPED_VALUE;
    }
  }

  private ConstraintValidatorManager.TypeResolutionResult getTypeResolutionResultForValidatedValue(ValueContext valueContext,
      List<Type> constraintValidatorTypesForValidatedValue) {
    if ( constraintValidatorTypesForValidatedValue.size() == 0 ) {
      return ConstraintValidatorManager.TypeResolutionResult.NO_VALIDATORS;
    }
    else if ( constraintValidatorTypesForValidatedValue.size() == 1 ) {
      // explicitly set the value handler to null. Is there a better place to do this? (HF)
      valueContext.setValidatedValueHandler( null );
      return ConstraintValidatorManager.TypeResolutionResult.SINGLE_VALIDATOR_FOR_VALIDATED_VALUE;
    }
    else {
      return ConstraintValidatorManager.TypeResolutionResult.MULTIPLE_VALIDATORS_FOR_VALIDATED_VALUE;
    }
  }

  private <A extends Annotation> List<Type> findSuitableValidatorTypes(Type type,
      Map<Type, Class<? extends ConstraintValidator<A, ?>>> availableValidatorTypes) {
    List<Type> determinedSuitableTypes = newArrayList();
    for ( Type validatorType : availableValidatorTypes.keySet() ) {
      if ( TypeHelper.isAssignable( validatorType, type )
          && !determinedSuitableTypes.contains( validatorType ) ) {
        determinedSuitableTypes.add( validatorType );
      }
    }
    return determinedSuitableTypes;
  }

  private <A extends Annotation> void initializeConstraint(ConstraintDescriptor<A> descriptor, ConstraintValidator<A, ?> constraintValidator) {
    try {
      constraintValidator.initialize( descriptor.getAnnotation() );
    }
    catch ( RuntimeException e ) {
      throw log.getUnableToInitializeConstraintValidatorException( constraintValidator.getClass().getName(), 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<Type>();
    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 UnwrapMode unwrapMode;
    private final int hashCode;

    private CacheKey(Annotation annotation,
             Type validatorType,
             ConstraintValidatorFactory constraintFactory,
             UnwrapMode unwrapMode) {
      this.annotation = annotation;
      this.validatedType = validatorType;
      this.constraintFactory = constraintFactory;
      this.unwrapMode = unwrapMode;
      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 ( hashCode != cacheKey.hashCode ) {
        return false;
      }
      if ( !annotation.equals( cacheKey.annotation ) ) {
        return false;
      }
      if ( !constraintFactory.equals( cacheKey.constraintFactory ) ) {
        return false;
      }
      if ( unwrapMode != cacheKey.unwrapMode ) {
        return false;
      }
      if ( !validatedType.equals( cacheKey.validatedType ) ) {
        return false;
      }

      return true;
    }

    @Override
    public int hashCode() {
      return hashCode;
    }

    private int createHashCode() {
      int result = annotation.hashCode();
      result = 31 * result + validatedType.hashCode();
      result = 31 * result + constraintFactory.hashCode();
      result = 31 * result + unwrapMode.hashCode();
      result = 31 * result + hashCode;
      return result;
    }
  }

  private static enum TypeResolutionResult {
    SINGLE_VALIDATOR_FOR_VALIDATED_VALUE,
    MULTIPLE_VALIDATORS_FOR_VALIDATED_VALUE,
    SINGLE_VALIDATOR_FOR_WRAPPED_VALUE,
    MULTIPLE_VALIDATORS_FOR_WRAPPED_VALUE,
    VALIDATORS_FOR_VALIDATED_VALUE_AND_WRAPPED_VALUE,
    NO_VALIDATORS
  }
}
TOP

Related Classes of org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager$CacheKey

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.