All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.validator.internal.engine.valueextraction.ValueExtractorResolver Maven / Gradle / Ivy

Go to download

JSR 380's RI, Hibernate Validator version ${hibernate-validator.version} and its dependencies repackaged as OSGi bundle

There is a newer version: 5.1.0
Show newest version
/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or .
 */
package org.hibernate.validator.internal.engine.valueextraction;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import javax.validation.ConstraintDeclarationException;
import javax.validation.valueextraction.ValueExtractor;

import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder;
import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData;
import org.hibernate.validator.internal.metadata.aggregated.PotentiallyContainerCascadingMetaData;
import org.hibernate.validator.internal.util.CollectionHelper;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.TypeHelper;
import org.hibernate.validator.internal.util.TypeVariableBindings;
import org.hibernate.validator.internal.util.TypeVariables;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.stereotypes.Immutable;

/**
 * Contains resolving algorithms for {@link ValueExtractor}s, and caches for these
 * extractors based on container types.
 *
 * @author Gunnar Morling
 * @author Guillaume Smet
 * @author Marko Bekhta
 */
public class ValueExtractorResolver {

	private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

	private static final Object NON_CONTAINER_VALUE = new Object();

	@Immutable
	private final Set registeredValueExtractors;

	private final ConcurrentHashMap> possibleValueExtractorsByRuntimeTypeAndTypeParameter = new ConcurrentHashMap<>();

	private final ConcurrentHashMap, Set> possibleValueExtractorsByRuntimeType = new ConcurrentHashMap<>();

	private final ConcurrentHashMap, Object> nonContainerTypes = new ConcurrentHashMap<>();

	ValueExtractorResolver(Set valueExtractors) {
		this.registeredValueExtractors = CollectionHelper.toImmutableSet( valueExtractors );
	}

	/**
	 * Used to find all the maximally specific value extractors based on a declared type in the case of value unwrapping.
	 * 

* There might be several of them as there might be several type parameters. *

* Used for container element constraints. */ public Set getMaximallySpecificValueExtractors(Class declaredType) { return getRuntimeCompliantValueExtractors( declaredType, registeredValueExtractors ); } /** * Used to find the maximally specific and container element compliant value extractor based on the declared type * and the type parameter. *

* Used for container element constraints. * * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found */ public ValueExtractorDescriptor getMaximallySpecificAndContainerElementCompliantValueExtractor(Class declaredType, TypeVariable typeParameter) { return getUniqueValueExtractorOrThrowException( declaredType, getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter, declaredType, registeredValueExtractors ) ); } /** * Used to find the maximally specific and container element compliant value extractor based on the runtime type. *

* The maximally specific one is chosen among the candidates passed to this method. *

* Used for cascading validation. * * @see ValueExtractorResolver#getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type, * TypeVariable, Class, Collection) * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found */ public ValueExtractorDescriptor getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type declaredType, TypeVariable typeParameter, Class runtimeType, Collection valueExtractorCandidates) { Contracts.assertNotEmpty( valueExtractorCandidates, "Value extractor candidates cannot be empty" ); if ( valueExtractorCandidates.size() == 1 ) { return valueExtractorCandidates.iterator().next(); } else { return getUniqueValueExtractorOrThrowException( runtimeType, getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter, runtimeType, valueExtractorCandidates ) ); } } /** * Used to determine if the passed runtime type is a container and if so return a corresponding maximally specific * value extractor. *

* Obviously, it only works if there's only one value extractor corresponding to the runtime type as we don't * precise any type parameter. *

* There is a special case: when the passed type is assignable to a {@link Map}, the {@link MapValueExtractor} will * be returned. This is required by the Bean Validation specification. *

* Used for cascading validation when the {@code @Valid} annotation is placed on the whole container. * * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found */ public ValueExtractorDescriptor getMaximallySpecificValueExtractorForAllContainerElements(Class runtimeType, Set potentialValueExtractorDescriptors) { // if it's a Map assignable type, it gets a special treatment to conform to the Bean Validation specification if ( TypeHelper.isAssignable( Map.class, runtimeType ) ) { return MapValueExtractor.DESCRIPTOR; } return getUniqueValueExtractorOrThrowException( runtimeType, getRuntimeCompliantValueExtractors( runtimeType, potentialValueExtractorDescriptors ) ); } /** * Used to determine the value extractor candidates valid for a declared type and type variable. *

* The effective value extractor will be narrowed from these candidates using the runtime type. *

* Used to optimize the choice of the value extractor in the case of cascading validation. */ public Set getValueExtractorCandidatesForCascadedValidation(Type declaredType, TypeVariable typeParameter) { Set valueExtractorDescriptors = new HashSet<>(); valueExtractorDescriptors.addAll( getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter, TypeHelper.getErasedReferenceType( declaredType ), registeredValueExtractors ) ); valueExtractorDescriptors.addAll( getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors( declaredType, typeParameter ) ); return CollectionHelper.toImmutableSet( valueExtractorDescriptors ); } /** * Used to determine the possible value extractors that can be applied to a declared type. *

* Used when building cascading metadata in {@link CascadingMetaDataBuilder} to decide if it should be promoted to * {@link ContainerCascadingMetaData} with cascaded constrained type arguments. *

* An example could be when we need to upgrade BV 1.1 style {@code @Valid private List list;} * to {@code private List<@Valid SomeBean> list;} *

* Searches only for maximally specific value extractors based on a type. *

* Types that are assignable to {@link Map} are handled as a special case - key value extractor is ignored for them. */ public Set getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation(Type enclosingType) { // if it's a Map assignable type, it gets a special treatment to conform to the Bean Validation specification boolean mapAssignable = TypeHelper.isAssignable( Map.class, enclosingType ); Class enclosingClass = ReflectionHelper.getClassFromType( enclosingType ); return getRuntimeCompliantValueExtractors( enclosingClass, registeredValueExtractors ) .stream() .filter( ved -> !mapAssignable || !ved.equals( MapKeyExtractor.DESCRIPTOR ) ) .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) ); } /** * Used to determine the value extractors which potentially could be applied to the runtime type of a given declared type. *

* An example could be when there's a declaration like {@code private PotentiallyContainerAtRuntime<@Valid Bean>;} and there's * no value extractor present for {@code PotentiallyContainerAtRuntime} but there's one available for * {@code Container extends PotentiallyContainerAtRuntime}. *

* Returned set of extractors is used to determine if at runtime a value extractor can be applied to a runtime type, * and if {@link PotentiallyContainerCascadingMetaData} should be promoted to {@link ContainerCascadingMetaData}. * * @return a set of {@link ValueExtractorDescriptor}s that possibly might be applied to a {@code declaredType} * at a runtime. */ public Set getPotentialValueExtractorCandidatesForCascadedValidation(Type declaredType) { return registeredValueExtractors .stream() .filter( e -> TypeHelper.isAssignable( declaredType, e.getContainerType() ) ) .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) ); } public void clear() { nonContainerTypes.clear(); possibleValueExtractorsByRuntimeType.clear(); possibleValueExtractorsByRuntimeTypeAndTypeParameter.clear(); } /** * Returns the set of potentially type-compliant and container-element-compliant value extractors or an empty set if none was found. *

* A value extractor is potentially runtime type compliant if it might be compliant for any runtime type that matches the declared type. */ private Set getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors(Type declaredType, TypeVariable typeParameter) { boolean isInternal = TypeVariables.isInternal( typeParameter ); Type erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType ); Set typeCompatibleExtractors = registeredValueExtractors .stream() .filter( e -> TypeHelper.isAssignable( erasedDeclaredType, e.getContainerType() ) ) .collect( Collectors.toSet() ); Set containerElementCompliantExtractors = new HashSet<>(); for ( ValueExtractorDescriptor extractorDescriptor : typeCompatibleExtractors ) { TypeVariable typeParameterBoundToExtractorType; if ( !isInternal ) { Map, Map, TypeVariable>> allBindings = TypeVariableBindings.getTypeVariableBindings( extractorDescriptor.getContainerType() ); Map, TypeVariable> bindingsForExtractorType = allBindings.get( erasedDeclaredType ); typeParameterBoundToExtractorType = bind( extractorDescriptor.getExtractedTypeParameter(), bindingsForExtractorType ); } else { typeParameterBoundToExtractorType = typeParameter; } if ( Objects.equals( typeParameter, typeParameterBoundToExtractorType ) ) { containerElementCompliantExtractors.add( extractorDescriptor ); } } return containerElementCompliantExtractors; } private ValueExtractorDescriptor getUniqueValueExtractorOrThrowException(Class runtimeType, Set maximallySpecificContainerElementCompliantValueExtractors) { if ( maximallySpecificContainerElementCompliantValueExtractors.size() == 1 ) { return maximallySpecificContainerElementCompliantValueExtractors.iterator().next(); } else if ( maximallySpecificContainerElementCompliantValueExtractors.isEmpty() ) { return null; } else { throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException( runtimeType, ValueExtractorHelper.toValueExtractorClasses( maximallySpecificContainerElementCompliantValueExtractors ) ); } } private Set getMaximallySpecificValueExtractors(Set possibleValueExtractors) { Set valueExtractorDescriptors = CollectionHelper.newHashSet( possibleValueExtractors.size() ); for ( ValueExtractorDescriptor descriptor : possibleValueExtractors ) { if ( valueExtractorDescriptors.isEmpty() ) { valueExtractorDescriptors.add( descriptor ); continue; } Iterator candidatesIterator = valueExtractorDescriptors.iterator(); boolean isNewRoot = true; while ( candidatesIterator.hasNext() ) { ValueExtractorDescriptor candidate = candidatesIterator.next(); // we consider the strictly more specific value extractor so 2 value extractors for the same container // type should throw an error in the end if no other more specific value extractor is found. if ( candidate.getContainerType().equals( descriptor.getContainerType() ) ) { continue; } if ( TypeHelper.isAssignable( candidate.getContainerType(), descriptor.getContainerType() ) ) { candidatesIterator.remove(); } else if ( TypeHelper.isAssignable( descriptor.getContainerType(), candidate.getContainerType() ) ) { isNewRoot = false; } } if ( isNewRoot ) { valueExtractorDescriptors.add( descriptor ); } } return valueExtractorDescriptors; } /** * @return a set of runtime compliant value extractors based on a runtime type. If there are no available value extractors * an empty set will be returned which means the type is not a container. */ private Set getRuntimeCompliantValueExtractors(Class runtimeType, Set potentialValueExtractorDescriptors) { if ( nonContainerTypes.contains( runtimeType ) ) { return Collections.emptySet(); } Set valueExtractorDescriptors = possibleValueExtractorsByRuntimeType.get( runtimeType ); if ( valueExtractorDescriptors == null ) { //otherwise we just look for maximally specific extractors for the runtime type Set possibleValueExtractors = potentialValueExtractorDescriptors .stream() .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) ) .collect( Collectors.toSet() ); valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors ); } if ( valueExtractorDescriptors.isEmpty() ) { nonContainerTypes.put( runtimeType, NON_CONTAINER_VALUE ); } else { Set extractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors ); possibleValueExtractorsByRuntimeType.put( runtimeType, extractorDescriptorsToCache ); return extractorDescriptorsToCache; } return valueExtractorDescriptors; } private Set getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates(Type declaredType, TypeVariable typeParameter, Class runtimeType, Collection valueExtractorCandidates) { if ( nonContainerTypes.contains( runtimeType ) ) { return Collections.emptySet(); } ValueExtractorCacheKey cacheKey = new ValueExtractorCacheKey( runtimeType, typeParameter ); Set valueExtractorDescriptors = possibleValueExtractorsByRuntimeTypeAndTypeParameter.get( cacheKey ); if ( valueExtractorDescriptors == null ) { boolean isInternal = TypeVariables.isInternal( typeParameter ); Class erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType ); Set possibleValueExtractors = valueExtractorCandidates .stream() .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) ) .filter( extractorDescriptor -> checkValueExtractorTypeCompatibility( typeParameter, isInternal, erasedDeclaredType, extractorDescriptor ) ).collect( Collectors.toSet() ); valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors ); if ( valueExtractorDescriptors.isEmpty() ) { nonContainerTypes.put( runtimeType, NON_CONTAINER_VALUE ); } else { Set extractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors ); possibleValueExtractorsByRuntimeTypeAndTypeParameter.put( cacheKey, extractorDescriptorsToCache ); return extractorDescriptorsToCache; } } return valueExtractorDescriptors; } private boolean checkValueExtractorTypeCompatibility(TypeVariable typeParameter, boolean isInternal, Class erasedDeclaredType, ValueExtractorDescriptor extractorDescriptor) { return TypeHelper.isAssignable( extractorDescriptor.getContainerType(), erasedDeclaredType ) ? validateValueExtractorCompatibility( isInternal, erasedDeclaredType, extractorDescriptor.getContainerType(), typeParameter, extractorDescriptor.getExtractedTypeParameter() ) : validateValueExtractorCompatibility( isInternal, extractorDescriptor.getContainerType(), erasedDeclaredType, extractorDescriptor.getExtractedTypeParameter(), typeParameter ); } private boolean validateValueExtractorCompatibility(boolean isInternal, Class typeForBinding, Class typeToBind, TypeVariable typeParameterForBinding, TypeVariable typeParameterToCompare) { TypeVariable typeParameterBoundToExtractorType; if ( !isInternal ) { Map, Map, TypeVariable>> allBindings = TypeVariableBindings.getTypeVariableBindings( typeForBinding ); Map, TypeVariable> bindingsForExtractorType = allBindings.get( typeToBind ); typeParameterBoundToExtractorType = bind( typeParameterForBinding, bindingsForExtractorType ); } else { typeParameterBoundToExtractorType = typeParameterForBinding; } return Objects.equals( typeParameterToCompare, typeParameterBoundToExtractorType ); } private TypeVariable bind(TypeVariable typeParameter, Map, TypeVariable> bindings) { return bindings != null ? bindings.get( typeParameter ) : null; } private static class ValueExtractorCacheKey { // These properties are not final on purpose, it's faster when they are not private Class type; private TypeVariable typeParameter; private int hashCode; ValueExtractorCacheKey(Class type, TypeVariable typeParameter) { this.type = type; this.typeParameter = typeParameter; this.hashCode = buildHashCode(); } @Override public boolean equals(Object o) { if ( this == o ) { return true; } if ( o == null ) { return false; } // We don't check the class as an optimization, the keys of the map are ValueExtractorCacheKey anyway ValueExtractorCacheKey that = (ValueExtractorCacheKey) o; return Objects.equals( this.type, that.type ) && Objects.equals( this.typeParameter, that.typeParameter ); } @Override public int hashCode() { return hashCode; } private int buildHashCode() { int result = this.type.hashCode(); result = 31 * result + ( this.typeParameter != null ? this.typeParameter.hashCode() : 0 ); return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy