1 /*
2  * Hibernate Validator, declare and validate application constraints
3  *
4  * License: Apache License, Version 2.0
5  * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6  */

7 package org.hibernate.validator.internal.engine.valueextraction;
8
9 import java.lang.invoke.MethodHandles;
10 import java.lang.reflect.Type;
11 import java.lang.reflect.TypeVariable;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.concurrent.ConcurrentHashMap;
20 import java.util.stream.Collectors;
21
22 import javax.validation.ConstraintDeclarationException;
23 import javax.validation.valueextraction.ValueExtractor;
24
25 import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder;
26 import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData;
27 import org.hibernate.validator.internal.metadata.aggregated.PotentiallyContainerCascadingMetaData;
28 import org.hibernate.validator.internal.util.CollectionHelper;
29 import org.hibernate.validator.internal.util.Contracts;
30 import org.hibernate.validator.internal.util.ReflectionHelper;
31 import org.hibernate.validator.internal.util.TypeHelper;
32 import org.hibernate.validator.internal.util.TypeVariableBindings;
33 import org.hibernate.validator.internal.util.TypeVariables;
34 import org.hibernate.validator.internal.util.logging.Log;
35 import org.hibernate.validator.internal.util.logging.LoggerFactory;
36 import org.hibernate.validator.internal.util.stereotypes.Immutable;
37
38 /**
39  * Contains resolving algorithms for {@link ValueExtractor}s, and caches for these
40  * extractors based on container types.
41  *
42  * @author Gunnar Morling
43  * @author Guillaume Smet
44  * @author Marko Bekhta
45  */

46 public class ValueExtractorResolver {
47
48     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
49
50     @Immutable
51     private final Set<ValueExtractorDescriptor> registeredValueExtractors;
52
53     private final ConcurrentHashMap<ValueExtractorCacheKey, Set<ValueExtractorDescriptor>> possibleValueExtractorsByRuntimeTypeAndTypeParameter = new ConcurrentHashMap<>();
54
55     private final ConcurrentHashMap<Class<?>, Set<ValueExtractorDescriptor>> possibleValueExtractorsByRuntimeType = new ConcurrentHashMap<>();
56
57     private final Set<Class<?>> nonContainerTypes = Collections.newSetFromMap( new ConcurrentHashMap<>() );
58
59     ValueExtractorResolver(Set<ValueExtractorDescriptor> valueExtractors) {
60         this.registeredValueExtractors = CollectionHelper.toImmutableSet( valueExtractors );
61     }
62
63     /**
64      * Used to find all the maximally specific value extractors based on a declared type in the case of value unwrapping.
65      * <p>
66      * There might be several of them as there might be several type parameters.
67      * <p>
68      * Used for container element constraints.
69      */

70     public Set<ValueExtractorDescriptor> getMaximallySpecificValueExtractors(Class<?> declaredType) {
71         return getRuntimeCompliantValueExtractors( declaredType, registeredValueExtractors );
72     }
73
74     /**
75      * Used to find the maximally specific and container element compliant value extractor based on the declared type
76      * and the type parameter.
77      * <p>
78      * Used for container element constraints.
79      *
80      * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found
81      */

82     public ValueExtractorDescriptor getMaximallySpecificAndContainerElementCompliantValueExtractor(Class<?> declaredType, TypeVariable<?> typeParameter) {
83         return getUniqueValueExtractorOrThrowException(
84                 declaredType,
85                 getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter, declaredType, registeredValueExtractors )
86         );
87     }
88
89     /**
90      * Used to find the maximally specific and container element compliant value extractor based on the runtime type.
91      * <p>
92      * The maximally specific one is chosen among the candidates passed to this method.
93      * <p>
94      * Used for cascading validation.
95      *
96      * @see ValueExtractorResolver#getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type,
97      * TypeVariable, Class, Collection)
98      * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found
99      */

100     public ValueExtractorDescriptor getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type declaredType, TypeVariable<?> typeParameter,
101             Class<?> runtimeType, Collection<ValueExtractorDescriptor> valueExtractorCandidates) {
102         Contracts.assertNotEmpty( valueExtractorCandidates, "Value extractor candidates cannot be empty" );
103         if ( valueExtractorCandidates.size() == 1 ) {
104             return valueExtractorCandidates.iterator().next();
105         }
106         else {
107             return getUniqueValueExtractorOrThrowException(
108                     runtimeType,
109                     getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates(
110                             declaredType, typeParameter, runtimeType, valueExtractorCandidates
111                     )
112             );
113         }
114     }
115
116     /**
117      * Used to determine if the passed runtime type is a container and if so return a corresponding maximally specific
118      * value extractor.
119      * <p>
120      * Obviously, it only works if there's only one value extractor corresponding to the runtime type as we don't
121      * precise any type parameter.
122      * <p>
123      * There is a special case: when the passed type is assignable to a {@link Map}, the {@link MapValueExtractor} will
124      * be returned. This is required by the Bean Validation specification.
125      * <p>
126      * Used for cascading validation when the {@code @Valid} annotation is placed on the whole container.
127      *
128      * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found
129      */

130     public ValueExtractorDescriptor getMaximallySpecificValueExtractorForAllContainerElements(Class<?> runtimeType, Set<ValueExtractorDescriptor> potentialValueExtractorDescriptors) {
131         // if it's a Map assignable type, it gets a special treatment to conform to the Bean Validation specification
132         if ( TypeHelper.isAssignable( Map.class, runtimeType ) ) {
133             return MapValueExtractor.DESCRIPTOR;
134         }
135
136         return getUniqueValueExtractorOrThrowException( runtimeType, getRuntimeCompliantValueExtractors( runtimeType, potentialValueExtractorDescriptors ) );
137     }
138
139     /**
140      * Used to determine the value extractor candidates valid for a declared type and type variable.
141      * <p>
142      * The effective value extractor will be narrowed from these candidates using the runtime type.
143      * <p>
144      * Used to optimize the choice of the value extractor in the case of cascading validation.
145      */

146     public Set<ValueExtractorDescriptor> getValueExtractorCandidatesForCascadedValidation(Type declaredType, TypeVariable<?> typeParameter) {
147         Set<ValueExtractorDescriptor> valueExtractorDescriptors = new HashSet<>();
148
149         valueExtractorDescriptors.addAll( getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates( declaredType, typeParameter,
150                 TypeHelper.getErasedReferenceType( declaredType ), registeredValueExtractors
151         ) );
152         valueExtractorDescriptors.addAll( getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors( declaredType, typeParameter ) );
153
154         return CollectionHelper.toImmutableSet( valueExtractorDescriptors );
155     }
156
157     /**
158      * Used to determine the possible value extractors that can be applied to a declared type.
159      * <p>
160      * Used when building cascading metadata in {@link CascadingMetaDataBuilder} to decide if it should be promoted to
161      * {@link ContainerCascadingMetaData} with cascaded constrained type arguments.
162      * <p>
163      * An example could be when we need to upgrade BV 1.1 style {@code @Valid private List<SomeBean> list;}
164      * to {@code private List<@Valid SomeBean> list;}
165      * <p>
166      * Searches only for maximally specific value extractors based on a type.
167      * <p>
168      * Types that are assignable to {@link Map} are handled as a special case - key value extractor is ignored for them.
169      */

170     public Set<ValueExtractorDescriptor> getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation(Type enclosingType) {
171         // if it's a Map assignable type, it gets a special treatment to conform to the Bean Validation specification
172         boolean mapAssignable = TypeHelper.isAssignable( Map.class, enclosingType );
173
174         Class<?> enclosingClass = ReflectionHelper.getClassFromType( enclosingType );
175         return getRuntimeCompliantValueExtractors( enclosingClass, registeredValueExtractors )
176                 .stream()
177                 .filter( ved -> !mapAssignable || !ved.equals( MapKeyExtractor.DESCRIPTOR ) )
178                 .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) );
179     }
180
181     /**
182      * Used to determine the value extractors which potentially could be applied to the runtime type of a given declared type.
183      * <p>
184      * An example could be when there's a declaration like {@code private PotentiallyContainerAtRuntime<@Valid Bean>;} and there's
185      * no value extractor present for {@code PotentiallyContainerAtRuntime} but there's one available for
186      * {@code Container extends PotentiallyContainerAtRuntime}.
187      * <p>
188      * Returned set of extractors is used to determine if at runtime a value extractor can be applied to a runtime type,
189      * and if {@link PotentiallyContainerCascadingMetaData} should be promoted to {@link ContainerCascadingMetaData}.
190      *
191      * @return a set of {@link ValueExtractorDescriptor}s that possibly might be applied to a {@code declaredType}
192      * at a runtime.
193      */

194     public Set<ValueExtractorDescriptor> getPotentialValueExtractorCandidatesForCascadedValidation(Type declaredType) {
195         return registeredValueExtractors
196                 .stream()
197                 .filter( e -> TypeHelper.isAssignable( declaredType, e.getContainerType() ) )
198                 .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) );
199     }
200
201     public void clear() {
202         nonContainerTypes.clear();
203         possibleValueExtractorsByRuntimeType.clear();
204         possibleValueExtractorsByRuntimeTypeAndTypeParameter.clear();
205     }
206
207     /**
208      * Returns the set of potentially type-compliant and container-element-compliant value extractors or an empty set if none was found.
209      * <p>
210      * A value extractor is potentially runtime type compliant if it might be compliant for any runtime type that matches the declared type.
211      */

212     private Set<ValueExtractorDescriptor> getPotentiallyRuntimeTypeCompliantAndContainerElementCompliantValueExtractors(Type declaredType,
213             TypeVariable<?> typeParameter) {
214         boolean isInternal = TypeVariables.isInternal( typeParameter );
215         Type erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType );
216
217         Set<ValueExtractorDescriptor> typeCompatibleExtractors = registeredValueExtractors
218                 .stream()
219                 .filter( e -> TypeHelper.isAssignable( erasedDeclaredType, e.getContainerType() ) )
220                 .collect( Collectors.toSet() );
221
222         Set<ValueExtractorDescriptor> containerElementCompliantExtractors = new HashSet<>();
223
224         for ( ValueExtractorDescriptor extractorDescriptor : typeCompatibleExtractors ) {
225             TypeVariable<?> typeParameterBoundToExtractorType;
226
227             if ( !isInternal ) {
228                 Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings =
229                         TypeVariableBindings.getTypeVariableBindings( extractorDescriptor.getContainerType() );
230
231                 Map<TypeVariable<?>, TypeVariable<?>> bindingsForExtractorType = allBindings.get( erasedDeclaredType );
232                 typeParameterBoundToExtractorType = bind( extractorDescriptor.getExtractedTypeParameter(), bindingsForExtractorType );
233             }
234             else {
235                 typeParameterBoundToExtractorType = typeParameter;
236             }
237
238             if ( Objects.equals( typeParameter, typeParameterBoundToExtractorType ) ) {
239                 containerElementCompliantExtractors.add( extractorDescriptor );
240             }
241         }
242
243         return containerElementCompliantExtractors;
244     }
245
246     private ValueExtractorDescriptor getUniqueValueExtractorOrThrowException(Class<?> runtimeType,
247             Set<ValueExtractorDescriptor> maximallySpecificContainerElementCompliantValueExtractors) {
248         if ( maximallySpecificContainerElementCompliantValueExtractors.size() == 1 ) {
249             return maximallySpecificContainerElementCompliantValueExtractors.iterator().next();
250         }
251         else if ( maximallySpecificContainerElementCompliantValueExtractors.isEmpty() ) {
252             return null;
253         }
254         else {
255             throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException( runtimeType,
256                     ValueExtractorHelper.toValueExtractorClasses( maximallySpecificContainerElementCompliantValueExtractors )
257             );
258         }
259     }
260
261     private Set<ValueExtractorDescriptor> getMaximallySpecificValueExtractors(Set<ValueExtractorDescriptor> possibleValueExtractors) {
262         Set<ValueExtractorDescriptor> valueExtractorDescriptors = CollectionHelper.newHashSet( possibleValueExtractors.size() );
263
264         for ( ValueExtractorDescriptor descriptor : possibleValueExtractors ) {
265             if ( valueExtractorDescriptors.isEmpty() ) {
266                 valueExtractorDescriptors.add( descriptor );
267                 continue;
268             }
269             Iterator<ValueExtractorDescriptor> candidatesIterator = valueExtractorDescriptors.iterator();
270             boolean isNewRoot = true;
271             while ( candidatesIterator.hasNext() ) {
272                 ValueExtractorDescriptor candidate = candidatesIterator.next();
273
274                 // we consider the strictly more specific value extractor so 2 value extractors for the same container
275                 // type should throw an error in the end if no other more specific value extractor is found.
276                 if ( candidate.getContainerType().equals( descriptor.getContainerType() ) ) {
277                     continue;
278                 }
279
280                 if ( TypeHelper.isAssignable( candidate.getContainerType(), descriptor.getContainerType() ) ) {
281                     candidatesIterator.remove();
282                 }
283                 else if ( TypeHelper.isAssignable( descriptor.getContainerType(), candidate.getContainerType() ) ) {
284                     isNewRoot = false;
285                 }
286             }
287             if ( isNewRoot ) {
288                 valueExtractorDescriptors.add( descriptor );
289             }
290         }
291         return valueExtractorDescriptors;
292     }
293
294     /**
295      * @return a set of runtime compliant value extractors based on a runtime type. If there are no available value extractors
296      * an empty set will be returned which means the type is not a container.
297      */

298     private Set<ValueExtractorDescriptor> getRuntimeCompliantValueExtractors(Class<?> runtimeType, Set<ValueExtractorDescriptor> potentialValueExtractorDescriptors) {
299         if ( nonContainerTypes.contains( runtimeType ) ) {
300             return Collections.emptySet();
301         }
302
303         Set<ValueExtractorDescriptor> valueExtractorDescriptors = possibleValueExtractorsByRuntimeType.get( runtimeType );
304
305         if ( valueExtractorDescriptors != null ) {
306             return valueExtractorDescriptors;
307         }
308
309         Set<ValueExtractorDescriptor> possibleValueExtractors = potentialValueExtractorDescriptors
310                 .stream()
311                 .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) )
312                 .collect( Collectors.toSet() );
313
314         valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors );
315
316         if ( valueExtractorDescriptors.isEmpty() ) {
317             nonContainerTypes.add( runtimeType );
318             return Collections.emptySet();
319         }
320
321         Set<ValueExtractorDescriptor> valueExtractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors );
322         Set<ValueExtractorDescriptor> cachedValueExtractorDescriptors = possibleValueExtractorsByRuntimeType.putIfAbsent( runtimeType,
323                 valueExtractorDescriptorsToCache );
324         return cachedValueExtractorDescriptors != null ? cachedValueExtractorDescriptors : valueExtractorDescriptorsToCache;
325     }
326
327     private Set<ValueExtractorDescriptor> getRuntimeAndContainerElementCompliantValueExtractorsFromPossibleCandidates(Type declaredType,
328             TypeVariable<?> typeParameter, Class<?> runtimeType, Collection<ValueExtractorDescriptor> valueExtractorCandidates) {
329         if ( nonContainerTypes.contains( runtimeType ) ) {
330             return Collections.emptySet();
331         }
332
333         ValueExtractorCacheKey cacheKey = new ValueExtractorCacheKey( runtimeType, typeParameter );
334
335         Set<ValueExtractorDescriptor> valueExtractorDescriptors = possibleValueExtractorsByRuntimeTypeAndTypeParameter.get( cacheKey );
336
337         if ( valueExtractorDescriptors != null ) {
338             return valueExtractorDescriptors;
339         }
340
341         boolean isInternal = TypeVariables.isInternal( typeParameter );
342         Class<?> erasedDeclaredType = TypeHelper.getErasedReferenceType( declaredType );
343
344         Set<ValueExtractorDescriptor> possibleValueExtractors = valueExtractorCandidates
345                 .stream()
346                 .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) )
347                 .filter( extractorDescriptor ->
348                         checkValueExtractorTypeCompatibility(
349                                 typeParameter, isInternal, erasedDeclaredType, extractorDescriptor
350                         )
351                 ).collect( Collectors.toSet() );
352
353         valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors );
354
355         if ( valueExtractorDescriptors.isEmpty() ) {
356             nonContainerTypes.add( runtimeType );
357             return Collections.emptySet();
358         }
359
360         Set<ValueExtractorDescriptor> valueExtractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors );
361         Set<ValueExtractorDescriptor> cachedValueExtractorDescriptors = possibleValueExtractorsByRuntimeTypeAndTypeParameter.putIfAbsent( cacheKey,
362                 valueExtractorDescriptorsToCache );
363         return cachedValueExtractorDescriptors != null ? cachedValueExtractorDescriptors : valueExtractorDescriptorsToCache;
364     }
365
366     private boolean checkValueExtractorTypeCompatibility(TypeVariable<?> typeParameter, boolean isInternal, Class<?> erasedDeclaredType,
367             ValueExtractorDescriptor extractorDescriptor) {
368         return TypeHelper.isAssignable( extractorDescriptor.getContainerType(), erasedDeclaredType )
369                 ? validateValueExtractorCompatibility( isInternal, erasedDeclaredType, extractorDescriptor.getContainerType(), typeParameter,
370                 extractorDescriptor.getExtractedTypeParameter()
371         )
372                 : validateValueExtractorCompatibility( isInternal, extractorDescriptor.getContainerType(), erasedDeclaredType,
373                 extractorDescriptor.getExtractedTypeParameter(), typeParameter
374         );
375     }
376
377     private boolean validateValueExtractorCompatibility(boolean isInternal,
378             Class<?> typeForBinding,
379             Class<?> typeToBind,
380             TypeVariable<?> typeParameterForBinding,
381             TypeVariable<?> typeParameterToCompare) {
382         TypeVariable<?> typeParameterBoundToExtractorType;
383
384         if ( !isInternal ) {
385             Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings =
386                     TypeVariableBindings.getTypeVariableBindings( typeForBinding );
387
388             Map<TypeVariable<?>, TypeVariable<?>> bindingsForExtractorType = allBindings.get( typeToBind );
389             typeParameterBoundToExtractorType = bind( typeParameterForBinding, bindingsForExtractorType );
390         }
391         else {
392             typeParameterBoundToExtractorType = typeParameterForBinding;
393         }
394
395         return Objects.equals( typeParameterToCompare, typeParameterBoundToExtractorType );
396     }
397
398     private TypeVariable<?> bind(TypeVariable<?> typeParameter, Map<TypeVariable<?>, TypeVariable<?>> bindings) {
399         return bindings != null ? bindings.get( typeParameter ) : null;
400     }
401
402     private static class ValueExtractorCacheKey {
403
404         // These properties are not final on purpose, it's faster when they are not
405
406         private Class<?> type;
407         private TypeVariable<?> typeParameter;
408         private int hashCode;
409
410         ValueExtractorCacheKey(Class<?> type, TypeVariable<?> typeParameter) {
411             this.type = type;
412             this.typeParameter = typeParameter;
413             this.hashCode = buildHashCode();
414         }
415
416         @Override
417         public boolean equals(Object o) {
418             if ( this == o ) {
419                 return true;
420             }
421             if ( o == null ) {
422                 return false;
423             }
424             // We don't check the class as an optimization, the keys of the map are ValueExtractorCacheKey anyway
425             ValueExtractorCacheKey that = (ValueExtractorCacheKey) o;
426             return Objects.equals( this.type, that.type ) &&
427                     Objects.equals( this.typeParameter, that.typeParameter );
428         }
429
430         @Override
431         public int hashCode() {
432             return hashCode;
433         }
434
435         private int buildHashCode() {
436             int result = this.type.hashCode();
437             result = 31 * result + ( this.typeParameter != null ? this.typeParameter.hashCode() : 0 );
438             return result;
439         }
440     }
441 }
442