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.reflect.Type;
11 import java.lang.reflect.TypeVariable;
12 import java.util.List;
13 import java.util.NoSuchElementException;
14 import java.util.Set;
15
16 import javax.validation.valueextraction.ValueExtractor;
17
18 import org.hibernate.validator.internal.engine.valuecontext.ValueContext;
19 import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree;
20 import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
21 import org.hibernate.validator.internal.engine.validationcontext.ValidationContext;
22 import org.hibernate.validator.internal.engine.valuecontext.BeanValueContext;
23 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
24 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper;
25 import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
26 import org.hibernate.validator.internal.metadata.location.ConstraintLocation;
27 import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind;
28 import org.hibernate.validator.internal.util.StringHelper;
29 import org.hibernate.validator.internal.util.stereotypes.Immutable;
30
31 /**
32  * Instances of this class abstract the constraint type  (class, method or field constraint) and give access to
33  * meta data about the constraint. This allows a unified handling of constraints in the validator implementation.
34  *
35  * @author Hardy Ferentschik
36  * @author Gunnar Morling
37  * @author Guillaume Smet
38  */

39 public class MetaConstraint<A extends Annotation> {
40
41     /**
42      * The constraint tree created from the constraint annotation.
43      */

44     private final ConstraintTree<A> constraintTree;
45
46     /**
47      * The location at which this constraint is defined.
48      */

49     private final ConstraintLocation location;
50
51     /**
52      * The path used to navigate from the outermost container to the innermost container and extract the value for
53      * validation.
54      */

55     @Immutable
56     private final ValueExtractionPathNode valueExtractionPath;
57
58     private final int hashCode;
59
60     /**
61      * Indicates if the constraint is defined for one group only: used to optimize already validated constraints
62      * tracking.
63      */

64     private final boolean isDefinedForOneGroupOnly;
65
66     /**
67      * @param constraintDescriptor The constraint descriptor for this constraint
68      * @param location meta data about constraint placement
69      * @param valueExtractionPath the potential {@link ValueExtractor}s used to extract the value to validate
70      * @param validatedValueType the type of the validated element
71      */

72     MetaConstraint(ConstraintValidatorManager constraintValidatorManager, ConstraintDescriptorImpl<A> constraintDescriptor,
73             ConstraintLocation location, List<ContainerClassTypeParameterAndExtractor> valueExtractionPath,
74             Type validatedValueType) {
75         this.constraintTree = ConstraintTree.of( constraintValidatorManager, constraintDescriptor, validatedValueType );
76         this.location = location;
77         this.valueExtractionPath = getValueExtractionPath( valueExtractionPath );
78         this.hashCode = buildHashCode( constraintDescriptor, location );
79         this.isDefinedForOneGroupOnly = constraintDescriptor.getGroups().size() <= 1;
80     }
81
82     private static ValueExtractionPathNode getValueExtractionPath(List<ContainerClassTypeParameterAndExtractor> valueExtractionPath) {
83         switch ( valueExtractionPath.size() ) {
84             case 0: return null;
85             case 1: return new SingleValueExtractionPathNode( valueExtractionPath.iterator().next() );
86             defaultreturn new LinkedValueExtractionPathNode( null, valueExtractionPath );
87         }
88     }
89
90     /**
91      * @return Returns the list of groups this constraint is part of. This might include the default group even when
92      *         it is not explicitly specified, but part of the redefined default group list of the hosting bean.
93      */

94     public final Set<Class<?>> getGroupList() {
95         return constraintTree.getDescriptor().getGroups();
96     }
97
98     public final boolean isDefinedForOneGroupOnly() {
99         return isDefinedForOneGroupOnly;
100     }
101
102     public final ConstraintDescriptorImpl<A> getDescriptor() {
103         return constraintTree.getDescriptor();
104     }
105
106     public final ConstraintLocationKind getConstraintLocationKind() {
107         return constraintTree.getDescriptor().getConstraintLocationKind();
108     }
109
110     public boolean validateConstraint(ValidationContext<?> validationContext, ValueContext<?, Object> valueContext) {
111         boolean success = true;
112         // constraint requiring value extraction to get the value to validate
113         if ( valueExtractionPath != null ) {
114             Object valueToValidate = valueContext.getCurrentValidatedValue();
115             if ( valueToValidate != null ) {
116                 TypeParameterValueReceiver receiver = new TypeParameterValueReceiver( validationContext, valueContext, valueExtractionPath );
117                 ValueExtractorHelper.extractValues( valueExtractionPath.getValueExtractorDescriptor(), valueToValidate, receiver );
118                 success = receiver.isSuccess();
119             }
120         }
121         // regular constraint
122         else {
123             success = doValidateConstraint( validationContext, valueContext );
124         }
125         return success;
126     }
127
128     private boolean doValidateConstraint(ValidationContext<?> executionContext, ValueContext<?, ?> valueContext) {
129         valueContext.setConstraintLocationKind( getConstraintLocationKind() );
130         boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext );
131
132         return validationResult;
133     }
134
135     public ConstraintLocation getLocation() {
136         return location;
137     }
138
139     @Override
140     public boolean equals(Object o) {
141         if ( this == o ) {
142             return true;
143         }
144         if ( o == null || getClass() != o.getClass() ) {
145             return false;
146         }
147
148         MetaConstraint<?> that = (MetaConstraint<?>) o;
149
150         if ( !constraintTree.getDescriptor().equals( that.constraintTree.getDescriptor() ) ) {
151             return false;
152         }
153
154         if ( !location.equals( that.location ) ) {
155             return false;
156         }
157
158         return true;
159     }
160
161     private static int buildHashCode(ConstraintDescriptorImpl<?> constraintDescriptor, ConstraintLocation location) {
162         final int prime = 31;
163         int result = 1;
164         result = prime * result + constraintDescriptor.hashCode();
165         result = prime * result + location.hashCode();
166         return result;
167     }
168
169     @Override
170     public int hashCode() {
171         return hashCode;
172     }
173
174     @Override
175     public String toString() {
176         final StringBuilder sb = new StringBuilder();
177         sb.append( "MetaConstraint" );
178         sb.append( "{constraintType=" ).append( StringHelper.toShortString( constraintTree.getDescriptor().getAnnotation().annotationType() ) );
179         sb.append( ", location=" ).append( location );
180         sb.append( ", valueExtractionPath=" ).append( valueExtractionPath );
181         sb.append( "}" );
182         return sb.toString();
183     }
184
185     private final class TypeParameterValueReceiver implements ValueExtractor.ValueReceiver {
186
187         private final ValidationContext<?> validationContext;
188         private final ValueContext<?, Object> valueContext;
189         private boolean success = true;
190         private ValueExtractionPathNode currentValueExtractionPathNode;
191
192         public TypeParameterValueReceiver(ValidationContext<?> validationContext, ValueContext<?, Object> valueContext, ValueExtractionPathNode currentValueExtractionPathNode) {
193             this.validationContext = validationContext;
194             this.valueContext = valueContext;
195             this.currentValueExtractionPathNode = currentValueExtractionPathNode;
196         }
197
198         @Override
199         public void value(String nodeName, Object object) {
200             doValidate( object, nodeName );
201         }
202
203         @Override
204         public void iterableValue(String nodeName, Object value) {
205             valueContext.markCurrentPropertyAsIterable();
206             doValidate( value, nodeName );
207         }
208
209         @Override
210         public void indexedValue(String nodeName, int index, Object value) {
211             valueContext.markCurrentPropertyAsIterableAndSetIndex( index );
212             doValidate( value, nodeName );
213         }
214
215         @Override
216         public void keyedValue(String nodeName, Object key, Object value) {
217             valueContext.markCurrentPropertyAsIterableAndSetKey( key );
218             doValidate( value, nodeName );
219         }
220
221         private void doValidate(Object value, String nodeName) {
222             BeanValueContext.ValueState<Object> originalValueState = valueContext.getCurrentValueState();
223
224             Class<?> containerClass = currentValueExtractionPathNode.getContainerClass();
225             if ( containerClass != null ) {
226                 valueContext.setTypeParameter( containerClass, currentValueExtractionPathNode.getTypeParameterIndex() );
227             }
228
229             if ( nodeName != null ) {
230                 valueContext.appendTypeParameterNode( nodeName );
231             }
232
233             valueContext.setCurrentValidatedValue( value );
234
235             if ( currentValueExtractionPathNode.hasNext() ) {
236                 if ( value != null ) {
237                     currentValueExtractionPathNode = currentValueExtractionPathNode.getNext();
238
239                     ValueExtractorDescriptor valueExtractorDescriptor = currentValueExtractionPathNode.getValueExtractorDescriptor();
240                     ValueExtractorHelper.extractValues( valueExtractorDescriptor, value, this );
241
242                     currentValueExtractionPathNode = currentValueExtractionPathNode.getPrevious();
243                 }
244             }
245             else {
246                 success &= doValidateConstraint( validationContext, valueContext );
247             }
248
249             // reset the value context to the state before this call
250             valueContext.resetValueState( originalValueState );
251         }
252
253         public boolean isSuccess() {
254             return success;
255         }
256     }
257
258     static final class ContainerClassTypeParameterAndExtractor {
259
260         private final Class<?> containerClass;
261         private final TypeVariable<?> typeParameter;
262         private final Integer typeParameterIndex;
263         private final ValueExtractorDescriptor valueExtractorDescriptor;
264
265         ContainerClassTypeParameterAndExtractor(Class<?> containerClass, TypeVariable<?> typeParameter, Integer typeParameterIndex, ValueExtractorDescriptor valueExtractorDescriptor) {
266             this.containerClass = containerClass;
267             this.typeParameter = typeParameter;
268             this.typeParameterIndex = typeParameterIndex;
269             this.valueExtractorDescriptor = valueExtractorDescriptor;
270         }
271
272         @Override
273         public String toString() {
274             return "ContainerClassTypeParameterAndExtractor [containerClass=" + containerClass +
275                     ", typeParameter=" + typeParameter +
276                     ", typeParameterIndex=" + typeParameterIndex +
277                     ", valueExtractorDescriptor=" + valueExtractorDescriptor + "]";
278         }
279     }
280
281     private interface ValueExtractionPathNode {
282         boolean hasNext();
283         ValueExtractionPathNode getPrevious();
284         ValueExtractionPathNode getNext();
285         Class<?> getContainerClass();
286         TypeVariable<?> getTypeParameter();
287         Integer getTypeParameterIndex();
288         ValueExtractorDescriptor getValueExtractorDescriptor();
289     }
290
291     private static final class SingleValueExtractionPathNode implements ValueExtractionPathNode {
292
293         private final Class<?> containerClass;
294         private final TypeVariable<?> typeParameter;
295         private final Integer typeParameterIndex;
296         private final ValueExtractorDescriptor valueExtractorDescriptor;
297
298         public SingleValueExtractionPathNode(ContainerClassTypeParameterAndExtractor typeParameterAndExtractor) {
299             this.containerClass = typeParameterAndExtractor.containerClass;
300             this.typeParameter = typeParameterAndExtractor.typeParameter;
301             this.typeParameterIndex = typeParameterAndExtractor.typeParameterIndex;
302             this.valueExtractorDescriptor = typeParameterAndExtractor.valueExtractorDescriptor;
303         }
304
305         @Override
306         public boolean hasNext() {
307             return false;
308         }
309
310         @Override
311         public ValueExtractionPathNode getPrevious() {
312             throw new NoSuchElementException();
313         }
314
315         @Override
316         public ValueExtractionPathNode getNext() {
317             throw new NoSuchElementException();
318         }
319
320         @Override
321         public Class<?> getContainerClass() {
322             return containerClass;
323         }
324
325         @Override
326         public TypeVariable<?> getTypeParameter() {
327             return typeParameter;
328         }
329
330         @Override
331         public Integer getTypeParameterIndex() {
332             return typeParameterIndex;
333         }
334
335         @Override
336         public ValueExtractorDescriptor getValueExtractorDescriptor() {
337             return valueExtractorDescriptor;
338         }
339
340         @Override
341         public String toString() {
342             return "SingleValueExtractionPathNode [containerClass=" + containerClass +
343                     ", typeParameter=" + typeParameter +
344                     ", valueExtractorDescriptor=" + valueExtractorDescriptor + "]";
345         }
346     }
347
348     private static final class LinkedValueExtractionPathNode implements ValueExtractionPathNode {
349
350         private final ValueExtractionPathNode previous;
351         private final ValueExtractionPathNode next;
352         private final Class<?> containerClass;
353         private final TypeVariable<?> typeParameter;
354         private final Integer typeParameterIndex;
355         private final ValueExtractorDescriptor valueExtractorDescriptor;
356
357         private LinkedValueExtractionPathNode( ValueExtractionPathNode previous, List<ContainerClassTypeParameterAndExtractor> elements) {
358             ContainerClassTypeParameterAndExtractor first = elements.get( 0 );
359             this.containerClass = first.containerClass;
360             this.typeParameter = first.typeParameter;
361             this.typeParameterIndex = first.typeParameterIndex;
362             this.valueExtractorDescriptor = first.valueExtractorDescriptor;
363             this.previous = previous;
364
365             if ( elements.size() == 1 ) {
366                 this.next = null;
367             }
368             else {
369                 this.next = new LinkedValueExtractionPathNode( this, elements.subList( 1, elements.size() ) );
370             }
371         }
372
373         @Override
374         public boolean hasNext() {
375             return next != null;
376         }
377
378         @Override
379         public ValueExtractionPathNode getPrevious() {
380             return previous;
381         }
382
383         @Override
384         public ValueExtractionPathNode getNext() {
385             return next;
386         }
387
388         @Override
389         public Class<?> getContainerClass() {
390             return containerClass;
391         }
392
393         @Override
394         public TypeVariable<?> getTypeParameter() {
395             return typeParameter;
396         }
397
398         @Override
399         public Integer getTypeParameterIndex() {
400             return typeParameterIndex;
401         }
402
403         @Override
404         public ValueExtractorDescriptor getValueExtractorDescriptor() {
405             return valueExtractorDescriptor;
406         }
407
408         @Override
409         public String toString() {
410             return "LinkedValueExtractionPathNode [containerClass=" + containerClass +
411                     ", typeParameter=" + typeParameter +
412                     ", valueExtractorDescriptor=" + valueExtractorDescriptor + "]";
413         }
414     }
415 }
416