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.metadata.core;
8
9 import java.lang.annotation.Annotation;
10 import java.lang.invoke.MethodHandles;
11 import java.lang.reflect.Type;
12 import java.lang.reflect.TypeVariable;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.stream.Collectors;
20
21 import javax.validation.metadata.ValidateUnwrappedValue;
22
23 import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
24 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
25 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper;
26 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
27 import org.hibernate.validator.internal.metadata.core.MetaConstraint.ContainerClassTypeParameterAndExtractor;
28 import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
29 import org.hibernate.validator.internal.metadata.location.ConstraintLocation;
30 import org.hibernate.validator.internal.metadata.location.TypeArgumentConstraintLocation;
31 import org.hibernate.validator.internal.util.TypeHelper;
32 import org.hibernate.validator.internal.util.TypeResolutionHelper;
33 import org.hibernate.validator.internal.util.TypeVariableBindings;
34 import org.hibernate.validator.internal.util.TypeVariables;
35 import org.hibernate.validator.internal.util.logging.Log;
36 import org.hibernate.validator.internal.util.logging.LoggerFactory;
37
38 import com.fasterxml.classmate.ResolvedType;
39
40 /**
41  * Helper used to create {@link MetaConstraint}s.
42  *
43  * @author Guillaume Smet
44  */

45 public class MetaConstraints {
46
47     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
48
49     private MetaConstraints() {
50     }
51
52     public static <A extends Annotation> MetaConstraint<A> create(TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager,
53             ConstraintValidatorManager constraintValidatorManager,
54             ConstraintDescriptorImpl<A> constraintDescriptor, ConstraintLocation location) {
55         List<ContainerClassTypeParameterAndExtractor> valueExtractionPath = new ArrayList<>();
56
57         Type typeOfValidatedElement = addValueExtractorDescriptorForWrappedValue( typeResolutionHelper, valueExtractorManager, constraintDescriptor,
58                 valueExtractionPath, location );
59
60         ConstraintLocation current = location;
61         do {
62             if ( current instanceof TypeArgumentConstraintLocation ) {
63                 addValueExtractorDescriptorForTypeArgumentLocation( valueExtractorManager, valueExtractionPath, (TypeArgumentConstraintLocation) current );
64                 current = ( (TypeArgumentConstraintLocation) current ).getDelegate();
65             }
66             else {
67                 current = null;
68             }
69         }
70         while ( current != null );
71
72         Collections.reverse( valueExtractionPath );
73
74         return new MetaConstraint<>( constraintValidatorManager, constraintDescriptor, location, valueExtractionPath, typeOfValidatedElement );
75     }
76
77     private static <A extends Annotation> Type addValueExtractorDescriptorForWrappedValue(TypeResolutionHelper typeResolutionHelper,
78             ValueExtractorManager valueExtractorManager, ConstraintDescriptorImpl<A> constraintDescriptor,
79             List<ContainerClassTypeParameterAndExtractor> valueExtractionPath, ConstraintLocation location) {
80         if ( ValidateUnwrappedValue.SKIP.equals( constraintDescriptor.getValueUnwrapping() ) ) {
81             return location.getTypeForValidatorResolution();
82         }
83
84         Class<?> declaredType = TypeHelper.getErasedReferenceType( location.getTypeForValidatorResolution() );
85         Set<ValueExtractorDescriptor> valueExtractorDescriptorCandidates = valueExtractorManager.getResolver().getMaximallySpecificValueExtractors( declaredType );
86         ValueExtractorDescriptor selectedValueExtractorDescriptor;
87
88         // we want to force the unwrapping so we require one and only one maximally specific value extractors
89         if ( ValidateUnwrappedValue.UNWRAP.equals( constraintDescriptor.getValueUnwrapping() ) ) {
90             switch ( valueExtractorDescriptorCandidates.size() ) {
91                 case 0:
92                     throw LOG.getNoValueExtractorFoundForUnwrapException( declaredType );
93                 case 1:
94                     selectedValueExtractorDescriptor = valueExtractorDescriptorCandidates.iterator().next();
95                     break;
96                 default:
97                     throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException(
98                             declaredType,
99                             ValueExtractorHelper.toValueExtractorClasses( valueExtractorDescriptorCandidates ) );
100             }
101         }
102         // we are in the implicit (DEFAULT) case so:
103         // - if we don't have a maximally specific value extractor marked with @UnwrapByDefault, we don't unwrap
104         // - if we have one maximally specific value extractors that is marked with @UnwrapByDefault, we unwrap
105         // - otherwise, we throw an exception as we can't choose between the value extractors
106         else {
107             Set<ValueExtractorDescriptor> unwrapByDefaultValueExtractorDescriptorCandidates = valueExtractorDescriptorCandidates.stream()
108                     .filter( ved -> ved.isUnwrapByDefault() )
109                     .collect( Collectors.toSet() );
110
111             switch ( unwrapByDefaultValueExtractorDescriptorCandidates.size() ) {
112                 case 0:
113                     return location.getTypeForValidatorResolution();
114                 case 1:
115                     selectedValueExtractorDescriptor = unwrapByDefaultValueExtractorDescriptorCandidates.iterator().next();
116                     break;
117                 default:
118                     throw LOG.getImplicitUnwrappingNotAllowedWhenSeveralMaximallySpecificValueExtractorsMarkedWithUnwrapByDefaultDeclaredException(
119                             declaredType,
120                             ValueExtractorHelper.toValueExtractorClasses( unwrapByDefaultValueExtractorDescriptorCandidates ) );
121             }
122         }
123
124         if ( selectedValueExtractorDescriptor.getExtractedType().isPresent() ) {
125             valueExtractionPath.add( new ContainerClassTypeParameterAndExtractor( declaredType, nullnull, selectedValueExtractorDescriptor ) );
126             return selectedValueExtractorDescriptor.getExtractedType().get();
127         }
128         else {
129             Class<?> wrappedValueType = getWrappedValueType( typeResolutionHelper, location.getTypeForValidatorResolution(), selectedValueExtractorDescriptor );
130             TypeVariable<?> typeParameter = getContainerClassTypeParameter( declaredType, selectedValueExtractorDescriptor );
131
132             valueExtractionPath.add( new ContainerClassTypeParameterAndExtractor( declaredType, typeParameter, TypeVariables.getTypeParameterIndex( typeParameter ), selectedValueExtractorDescriptor ) );
133
134             return wrappedValueType;
135         }
136     }
137
138     private static void addValueExtractorDescriptorForTypeArgumentLocation( ValueExtractorManager valueExtractorManager,
139             List<ContainerClassTypeParameterAndExtractor> valueExtractionPath, TypeArgumentConstraintLocation typeArgumentConstraintLocation ) {
140         Class<?> declaredType = typeArgumentConstraintLocation.getContainerClass();
141         TypeVariable<?> typeParameter = typeArgumentConstraintLocation.getTypeParameter();
142
143         ValueExtractorDescriptor valueExtractorDescriptor = valueExtractorManager.getResolver()
144                 .getMaximallySpecificAndContainerElementCompliantValueExtractor( declaredType, typeParameter );
145
146         if ( valueExtractorDescriptor == null ) {
147             throw LOG.getNoValueExtractorFoundForTypeException( declaredType, typeParameter );
148         }
149
150         TypeVariable<?> actualTypeParameter = TypeVariables.getActualTypeParameter( typeParameter );
151         valueExtractionPath.add( new ContainerClassTypeParameterAndExtractor(
152                 TypeVariables.getContainerClass( typeParameter ),
153                 actualTypeParameter,
154                 TypeVariables.getTypeParameterIndex( actualTypeParameter ),
155                 valueExtractorDescriptor ) );
156     }
157
158     /**
159      * Returns the sub-types binding for the single type parameter of the super-type. E.g. for {@code IntegerProperty}
160      * and {@code Property<T>}, {@code Integer} would be returned.
161      */

162     private static Class<?> getWrappedValueType(TypeResolutionHelper typeResolutionHelper, Type declaredType, ValueExtractorDescriptor valueExtractorDescriptor) {
163         ResolvedType resolvedType = typeResolutionHelper.getTypeResolver().resolve( declaredType );
164
165         List<ResolvedType> resolvedTypeParameters = resolvedType.typeParametersFor( valueExtractorDescriptor.getContainerType() );
166
167         if ( resolvedTypeParameters == null || resolvedTypeParameters.isEmpty() ) {
168             throw LOG.getNoValueExtractorFoundForUnwrapException( declaredType );
169         }
170
171         return resolvedTypeParameters.get( TypeVariables.getTypeParameterIndex( valueExtractorDescriptor.getExtractedTypeParameter() ) ).getErasedType();
172     }
173
174     private static TypeVariable<?> getContainerClassTypeParameter(Class<?> declaredType, ValueExtractorDescriptor selectedValueExtractorDescriptor) {
175         if ( selectedValueExtractorDescriptor.getExtractedTypeParameter() == null ) {
176             return null;
177         }
178
179         Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings = TypeVariableBindings.getTypeVariableBindings( declaredType );
180         Map<TypeVariable<?>, TypeVariable<?>> extractorTypeBindings = allBindings.get( selectedValueExtractorDescriptor.getContainerType() );
181         if ( extractorTypeBindings == null ) {
182             return null;
183         }
184         return extractorTypeBindings.entrySet().stream()
185                 .filter( e -> Objects.equals( e.getKey().getGenericDeclaration(), declaredType ) )
186                 .collect( Collectors.toMap( Map.Entry::getValue, Map.Entry::getKey ) )
187                 .get( selectedValueExtractorDescriptor.getExtractedTypeParameter() );
188     }
189 }
190