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.descriptor;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
10 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
11
12 import java.io.Serializable;
13 import java.lang.annotation.Annotation;
14 import java.lang.annotation.Documented;
15 import java.lang.annotation.Repeatable;
16 import java.lang.annotation.Retention;
17 import java.lang.annotation.Target;
18 import java.lang.invoke.MethodHandles;
19 import java.lang.reflect.Method;
20 import java.security.AccessController;
21 import java.security.PrivilegedAction;
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28
29 import javax.validation.Constraint;
30 import javax.validation.ConstraintTarget;
31 import javax.validation.ConstraintValidator;
32 import javax.validation.OverridesAttribute;
33 import javax.validation.Payload;
34 import javax.validation.ReportAsSingleViolation;
35 import javax.validation.constraintvalidation.SupportedValidationTarget;
36 import javax.validation.constraintvalidation.ValidationTarget;
37 import javax.validation.groups.Default;
38 import javax.validation.metadata.ConstraintDescriptor;
39 import javax.validation.metadata.ValidateUnwrappedValue;
40 import javax.validation.valueextraction.Unwrapping;
41
42 import org.hibernate.validator.constraints.CompositionType;
43 import org.hibernate.validator.constraints.ConstraintComposition;
44 import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorDescriptor;
45 import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
46 import org.hibernate.validator.internal.metadata.core.ConstraintOrigin;
47 import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind;
48 import org.hibernate.validator.internal.properties.Callable;
49 import org.hibernate.validator.internal.properties.Constrainable;
50 import org.hibernate.validator.internal.properties.Property;
51 import org.hibernate.validator.internal.util.CollectionHelper;
52 import org.hibernate.validator.internal.util.StringHelper;
53 import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor;
54 import org.hibernate.validator.internal.util.logging.Log;
55 import org.hibernate.validator.internal.util.logging.LoggerFactory;
56 import org.hibernate.validator.internal.util.privilegedactions.GetAnnotationAttributes;
57 import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethods;
58 import org.hibernate.validator.internal.util.privilegedactions.GetMethod;
59 import org.hibernate.validator.internal.util.stereotypes.Immutable;
60
61 /**
62  * Describes a single constraint (including its composing constraints).
63  *
64  * @author Emmanuel Bernard
65  * @author Hardy Ferentschik
66  * @author Federico Mancini
67  * @author Dag Hovland
68  * @author Guillaume Smet
69  */

70 public class ConstraintDescriptorImpl<T extends Annotation> implements ConstraintDescriptor<T>, Serializable {
71
72     private static final long serialVersionUID = -2563102960314069246L;
73     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
74     private static final int OVERRIDES_PARAMETER_DEFAULT_INDEX = -1;
75
76     /**
77      * A list of annotations which can be ignored when investigating for composing constraints.
78      */

79     private static final List<String> NON_COMPOSING_CONSTRAINT_ANNOTATIONS = Arrays.asList(
80             Documented.class.getName(),
81             Retention.class.getName(),
82             Target.class.getName(),
83             Constraint.class.getName(),
84             ReportAsSingleViolation.class.getName(),
85             Repeatable.class.getName(),
86             Deprecated.class.getName()
87     );
88
89     /**
90      * The annotation descriptor - accessing the annotation information has a cost so we do it only once.
91      */

92     private final ConstraintAnnotationDescriptor<T> annotationDescriptor;
93
94     /**
95      * The set of classes implementing the validation for this constraint. See also
96      * {@code ConstraintValidator} resolution algorithm.
97      */

98     @Immutable
99     private final List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses;
100
101     /**
102      * This field is transient as the implementations of  {@link ConstraintValidatorDescriptor} might not be serializable.
103      * Typically {@code ClassBasedValidatorDescriptor} might contain a {@code ParameterizedTypeImpl}, which is not serializable.
104      */

105     @Immutable
106     private final transient List<ConstraintValidatorDescriptor<T>> matchingConstraintValidatorDescriptors;
107
108     /**
109      * The groups for which to apply this constraint.
110      */

111     @Immutable
112     private final Set<Class<?>> groups;
113
114     /**
115      * The specified payload of the constraint.
116      */

117     @Immutable
118     private final Set<Class<? extends Payload>> payloads;
119
120     /**
121      * The composing constraints for this constraint.
122      */

123     @Immutable
124     private final Set<ConstraintDescriptorImpl<?>> composingConstraints;
125
126     /**
127      * Flag indicating if in case of a composing constraint a single error or multiple errors should be raised.
128      */

129     private final boolean isReportAsSingleInvalidConstraint;
130
131     /**
132      * Describes on which level ({@code TYPE}, {@code METHOD}, {@code FIELD}...) the constraint was
133      * defined on.
134      */

135     private final ConstraintLocationKind constraintLocationKind;
136
137     /**
138      * The origin of the constraint. Defined on the actual root class or somewhere in the class hierarchy
139      */

140     private final ConstraintOrigin definedOn;
141
142     /**
143      * The type of this constraint.
144      */

145     private final ConstraintType constraintType;
146
147     /**
148      * The unwrapping behavior defined on the constraint.
149      */

150     private final ValidateUnwrappedValue valueUnwrapping;
151
152     /**
153      * The target of the constraint.
154      */

155     private final ConstraintTarget validationAppliesTo;
156
157     /**
158      * Type indicating how composing constraints should be combined. By default this is set to
159      * {@code ConstraintComposition.CompositionType.AND}.
160      */

161     private final CompositionType compositionType;
162
163     private final int hashCode;
164
165     public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,
166             Constrainable constrainable,
167             ConstraintAnnotationDescriptor<T> annotationDescriptor,
168             ConstraintLocationKind constraintLocationKind,
169             Class<?> implicitGroup,
170             ConstraintOrigin definedOn,
171             ConstraintType externalConstraintType) {
172         this.annotationDescriptor = annotationDescriptor;
173         this.constraintLocationKind = constraintLocationKind;
174         this.definedOn = definedOn;
175         this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(
176                 ReportAsSingleViolation.class
177         );
178
179         // HV-181 - To avoid any thread visibility issues we are building the different data structures in tmp variables and
180         // then assign them to the final variables
181         this.groups = buildGroupSet( annotationDescriptor, implicitGroup );
182         this.payloads = buildPayloadSet( annotationDescriptor );
183
184         this.valueUnwrapping = determineValueUnwrapping( this.payloads, constrainable, annotationDescriptor.getType() );
185
186         this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );
187
188         this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() )
189                 .stream()
190                 .map( ConstraintValidatorDescriptor::getValidatorClass )
191                 .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
192
193         List<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
194                 annotationDescriptor.getType(),
195                 ValidationTarget.PARAMETERS
196         ) );
197         List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
198                 annotationDescriptor.getType(),
199                 ValidationTarget.ANNOTATED_ELEMENT
200         ) );
201
202         if ( crossParameterValidatorDescriptors.size() > 1 ) {
203             throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );
204         }
205
206         this.constraintType = determineConstraintType(
207                 annotationDescriptor.getType(),
208                 constrainable,
209                 !genericValidatorDescriptors.isEmpty(),
210                 !crossParameterValidatorDescriptors.isEmpty(),
211                 externalConstraintType
212         );
213         this.composingConstraints = parseComposingConstraints( constraintHelper, constrainable, constraintType );
214         this.compositionType = parseCompositionType( constraintHelper );
215         validateComposingConstraintTypes();
216
217         if ( constraintType == ConstraintType.GENERIC ) {
218             this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( genericValidatorDescriptors );
219         }
220         else {
221             this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( crossParameterValidatorDescriptors );
222         }
223
224         this.hashCode = annotationDescriptor.hashCode();
225     }
226
227     public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,
228             Constrainable constrainable,
229             ConstraintAnnotationDescriptor<T> annotationDescriptor,
230             ConstraintLocationKind constraintLocationKind) {
231         this( constraintHelper, constrainable, annotationDescriptor, constraintLocationKind, null, ConstraintOrigin.DEFINED_LOCALLY, null );
232     }
233
234     public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,
235             Constrainable constrainable,
236             ConstraintAnnotationDescriptor<T> annotationDescriptor,
237             ConstraintLocationKind constraintLocationKind,
238             ConstraintType constraintType) {
239         this( constraintHelper, constrainable, annotationDescriptor, constraintLocationKind, null, ConstraintOrigin.DEFINED_LOCALLY, constraintType );
240     }
241
242     public ConstraintAnnotationDescriptor<T> getAnnotationDescriptor() {
243         return annotationDescriptor;
244     }
245
246     @Override
247     public T getAnnotation() {
248         return annotationDescriptor.getAnnotation();
249     }
250
251     public Class<T> getAnnotationType() {
252         return annotationDescriptor.getType();
253     }
254
255     @Override
256     public String getMessageTemplate() {
257         return annotationDescriptor.getMessage();
258     }
259
260     @Override
261     public Set<Class<?>> getGroups() {
262         return groups;
263     }
264
265     @Override
266     public Set<Class<? extends Payload>> getPayload() {
267         return payloads;
268     }
269
270     @Override
271     public ConstraintTarget getValidationAppliesTo() {
272         return validationAppliesTo;
273     }
274
275     @Override
276     public ValidateUnwrappedValue getValueUnwrapping() {
277         return valueUnwrapping;
278     }
279
280     @Override
281     public List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses() {
282         return constraintValidatorClasses;
283     }
284
285     /**
286      * Return all constraint validator descriptors (either generic or cross-parameter) which are registered for the
287      * constraint of this descriptor.
288      *
289      * @return The constraint validator descriptors applying to type of this constraint.
290      */

291     public List<ConstraintValidatorDescriptor<T>> getMatchingConstraintValidatorDescriptors() {
292         return matchingConstraintValidatorDescriptors;
293     }
294
295     @Override
296     public Map<String, Object> getAttributes() {
297         return annotationDescriptor.getAttributes();
298     }
299
300     @SuppressWarnings("unchecked")
301     @Override
302     public Set<ConstraintDescriptor<?>> getComposingConstraints() {
303         return (Set<ConstraintDescriptor<?>>) (Object) composingConstraints;
304     }
305
306     public Set<ConstraintDescriptorImpl<?>> getComposingConstraintImpls() {
307         return composingConstraints;
308     }
309
310     @Override
311     public boolean isReportAsSingleViolation() {
312         return isReportAsSingleInvalidConstraint;
313     }
314
315     public ConstraintLocationKind getConstraintLocationKind() {
316         return constraintLocationKind;
317     }
318
319     public ConstraintOrigin getDefinedOn() {
320         return definedOn;
321     }
322
323     public ConstraintType getConstraintType() {
324         return constraintType;
325     }
326
327     @Override
328     public <U> U unwrap(Class<U> type) {
329         throw LOG.getUnwrappingOfConstraintDescriptorNotSupportedYetException();
330     }
331
332     @Override
333     public boolean equals(Object o) {
334         if ( this == o ) {
335             return true;
336         }
337         if ( o == null || getClass() != o.getClass() ) {
338             return false;
339         }
340
341         ConstraintDescriptorImpl<?> that = (ConstraintDescriptorImpl<?>) o;
342
343         if ( annotationDescriptor != null ? !annotationDescriptor.equals( that.annotationDescriptor ) : that.annotationDescriptor != null ) {
344             return false;
345         }
346
347         return true;
348     }
349
350     @Override
351     public int hashCode() {
352         return hashCode;
353     }
354
355     @Override
356     public String toString() {
357         final StringBuilder sb = new StringBuilder();
358         sb.append( "ConstraintDescriptorImpl" );
359         sb.append( "{annotation=" ).append( StringHelper.toShortString( annotationDescriptor.getType() ) );
360         sb.append( ", payloads=" ).append( payloads );
361         sb.append( ", hasComposingConstraints=" ).append( composingConstraints.isEmpty() );
362         sb.append( ", isReportAsSingleInvalidConstraint=" ).append( isReportAsSingleInvalidConstraint );
363         sb.append( ", constraintLocationKind=" ).append( constraintLocationKind );
364         sb.append( ", definedOn=" ).append( definedOn );
365         sb.append( ", groups=" ).append( groups );
366         sb.append( ", attributes=" ).append( annotationDescriptor.getAttributes() );
367         sb.append( ", constraintType=" ).append( constraintType );
368         sb.append( ", valueUnwrapping=" ).append( valueUnwrapping );
369         sb.append( '}' );
370         return sb.toString();
371     }
372
373     /**
374      * Determines the type of this constraint. The following rules apply in
375      * descending order:
376      * <ul>
377      * <li>If {@code validationAppliesTo()} is set to either
378      * {@link ConstraintTarget#RETURN_VALUE} or
379      * {@link ConstraintTarget#PARAMETERS}, this value will be considered.</li>
380      * <li>Otherwise, if the constraint is either purely generic or purely
381      * cross-parameter as per its validators, that value will be considered.</li>
382      * <li>Otherwise, if the constraint is not on an executable, it is
383      * considered generic.</li>
384      * <li>Otherwise, the type will be determined based on exclusive existence
385      * of parameters and return value.</li>
386      * <li>If that also is not possible, determination fails (i.e. the user must
387      * specify the target explicitly).</li>
388      * </ul>
389      *
390      * @param constrainable The annotated member
391      * @param hasGenericValidators Whether the constraint has at least one generic validator or
392      * not
393      * @param hasCrossParameterValidator Whether the constraint has a cross-parameter validator
394      * @param externalConstraintType constraint type as derived from external context, e.g. for
395      * constraints declared in XML via {@code &lt;return-value/gt;}
396      *
397      * @return The type of this constraint
398      */

399     private ConstraintType determineConstraintType(Class<? extends Annotation> constraintAnnotationType,
400             Constrainable constrainable,
401             boolean hasGenericValidators,
402             boolean hasCrossParameterValidator,
403             ConstraintType externalConstraintType) {
404         ConstraintTarget constraintTarget = validationAppliesTo;
405         ConstraintType constraintType = null;
406         boolean isExecutable = constraintLocationKind.isExecutable();
407
408         //target explicitly set to RETURN_VALUE
409         if ( constraintTarget == ConstraintTarget.RETURN_VALUE ) {
410             if ( !isExecutable ) {
411                 throw LOG.getParametersOrReturnValueConstraintTargetGivenAtNonExecutableException(
412                         annotationDescriptor.getType(),
413                         ConstraintTarget.RETURN_VALUE
414                 );
415             }
416             constraintType = ConstraintType.GENERIC;
417         }
418         //target explicitly set to PARAMETERS
419         else if ( constraintTarget == ConstraintTarget.PARAMETERS ) {
420             if ( !isExecutable ) {
421                 throw LOG.getParametersOrReturnValueConstraintTargetGivenAtNonExecutableException(
422                         annotationDescriptor.getType(),
423                         ConstraintTarget.PARAMETERS
424                 );
425             }
426             constraintType = ConstraintType.CROSS_PARAMETER;
427         }
428         //target set by external context (e.g. <return-value> element in XML or returnValue() method in prog. API)
429         else if ( externalConstraintType != null ) {
430             constraintType = externalConstraintType;
431         }
432         //target set to IMPLICIT or not set at all
433         else {
434             //try to derive the type from the existing validators
435             if ( hasGenericValidators && !hasCrossParameterValidator ) {
436                 constraintType = ConstraintType.GENERIC;
437             }
438             else if ( !hasGenericValidators && hasCrossParameterValidator ) {
439                 constraintType = ConstraintType.CROSS_PARAMETER;
440             }
441             else if ( !isExecutable ) {
442                 constraintType = ConstraintType.GENERIC;
443             }
444             else if ( constraintAnnotationType.isAnnotationPresent( SupportedValidationTarget.class ) ) {
445                 SupportedValidationTarget supportedValidationTarget = constraintAnnotationType.getAnnotation( SupportedValidationTarget.class );
446                 if ( supportedValidationTarget.value().length == 1 ) {
447                     constraintType = supportedValidationTarget.value()[0] == ValidationTarget.ANNOTATED_ELEMENT ? ConstraintType.GENERIC : ConstraintType.CROSS_PARAMETER;
448                 }
449             }
450
451             //try to derive from existence of parameters/return value
452             //hence look only if it is a callable
453             else if ( constrainable instanceof Callable ) {
454                 boolean hasParameters = constrainable.as( Callable.class ).hasParameters();
455                 boolean hasReturnValue = constrainable.as( Callable.class ).hasReturnValue();
456
457                 if ( !hasParameters && hasReturnValue ) {
458                     constraintType = ConstraintType.GENERIC;
459                 }
460                 else if ( hasParameters && !hasReturnValue ) {
461                     constraintType = ConstraintType.CROSS_PARAMETER;
462                 }
463             }
464         }
465
466         // Now we are out of luck
467         if ( constraintType == null ) {
468             throw LOG.getImplicitConstraintTargetInAmbiguousConfigurationException( annotationDescriptor.getType() );
469         }
470
471         if ( constraintType == ConstraintType.CROSS_PARAMETER ) {
472             validateCrossParameterConstraintType( constrainable, hasCrossParameterValidator );
473         }
474
475         return constraintType;
476     }
477
478     private static ValidateUnwrappedValue determineValueUnwrapping(Set<Class<? extends Payload>> payloads, Constrainable constrainable, Class<? extends Annotation> annotationType) {
479         if ( payloads.contains( Unwrapping.Unwrap.class ) ) {
480             if ( payloads.contains( Unwrapping.Skip.class ) ) {
481                 throw LOG.getInvalidUnwrappingConfigurationForConstraintException( constrainable, annotationType );
482             }
483
484             return ValidateUnwrappedValue.UNWRAP;
485         }
486
487         if ( payloads.contains( Unwrapping.Skip.class ) ) {
488             return ValidateUnwrappedValue.SKIP;
489         }
490
491         return ValidateUnwrappedValue.DEFAULT;
492     }
493
494     private static ConstraintTarget determineValidationAppliesTo(ConstraintAnnotationDescriptor<?> annotationDescriptor) {
495         return annotationDescriptor.getValidationAppliesTo();
496     }
497
498     private void validateCrossParameterConstraintType(Constrainable constrainable, boolean hasCrossParameterValidator) {
499         if ( !hasCrossParameterValidator ) {
500             throw LOG.getCrossParameterConstraintHasNoValidatorException( annotationDescriptor.getType() );
501         }
502         else if ( constrainable == null ) {
503             throw LOG.getCrossParameterConstraintOnClassException( annotationDescriptor.getType() );
504         }
505         else if ( constrainable instanceof Property ) {
506             throw LOG.getCrossParameterConstraintOnFieldException( annotationDescriptor.getType(), constrainable );
507         }
508         else if ( !constrainable.as( Callable.class ).hasParameters() ) {
509             throw LOG.getCrossParameterConstraintOnMethodWithoutParametersException(
510                     annotationDescriptor.getType(),
511                     constrainable
512             );
513         }
514     }
515
516     /**
517      * Asserts that this constraint and all its composing constraints share the
518      * same constraint type (generic or cross-parameter).
519      */

520     private void validateComposingConstraintTypes() {
521         for ( ConstraintDescriptorImpl<?> composingConstraint : getComposingConstraintImpls() ) {
522             if ( composingConstraint.constraintType != constraintType ) {
523                 throw LOG.getComposedAndComposingConstraintsHaveDifferentTypesException(
524                         annotationDescriptor.getType(),
525                         composingConstraint.annotationDescriptor.getType(),
526                         constraintType,
527                         composingConstraint.constraintType
528                 );
529             }
530         }
531     }
532
533     @SuppressWarnings("unchecked")
534     private static Set<Class<? extends Payload>> buildPayloadSet(ConstraintAnnotationDescriptor<?> annotationDescriptor) {
535         Set<Class<? extends Payload>> payloadSet = newHashSet();
536
537         Class<? extends Payload>[] payloadFromAnnotation = annotationDescriptor.getPayload();
538
539         if ( payloadFromAnnotation != null ) {
540             payloadSet.addAll( Arrays.asList( payloadFromAnnotation ) );
541         }
542         return CollectionHelper.toImmutableSet( payloadSet );
543     }
544
545     private static Set<Class<?>> buildGroupSet(ConstraintAnnotationDescriptor<?> annotationDescriptor, Class<?> implicitGroup) {
546         Set<Class<?>> groupSet = newHashSet();
547         final Class<?>[] groupsFromAnnotation = annotationDescriptor.getGroups();
548         if ( groupsFromAnnotation.length == 0 ) {
549             groupSet.add( Default.class );
550         }
551         else {
552             groupSet.addAll( Arrays.asList( groupsFromAnnotation ) );
553         }
554
555         // if the constraint is part of the Default group it is automatically part of the implicit group as well
556         if ( implicitGroup != null && groupSet.contains( Default.class ) ) {
557             groupSet.add( implicitGroup );
558         }
559         return CollectionHelper.toImmutableSet( groupSet );
560     }
561
562     private Map<ClassIndexWrapper, Map<String, Object>> parseOverrideParameters() {
563         Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = newHashMap();
564         final Method[] methods = run( GetDeclaredMethods.action( annotationDescriptor.getType() ) );
565         for ( Method m : methods ) {
566             if ( m.getAnnotation( OverridesAttribute.class ) != null ) {
567                 addOverrideAttributes(
568                         overrideParameters, m, m.getAnnotation( OverridesAttribute.class )
569                 );
570             }
571             else if ( m.getAnnotation( OverridesAttribute.List.class ) != null ) {
572                 addOverrideAttributes(
573                         overrideParameters,
574                         m,
575                         m.getAnnotation( OverridesAttribute.List.class ).value()
576                 );
577             }
578         }
579         return overrideParameters;
580     }
581
582     private void addOverrideAttributes(Map<ClassIndexWrapper, Map<String, Object>> overrideParameters, Method m, OverridesAttribute... attributes) {
583         Object value = annotationDescriptor.getAttribute( m.getName() );
584         for ( OverridesAttribute overridesAttribute : attributes ) {
585             String overridesAttributeName = overridesAttribute.name().length() > 0 ? overridesAttribute.name() : m.getName();
586
587             ensureAttributeIsOverridable( m, overridesAttribute, overridesAttributeName );
588
589             ClassIndexWrapper wrapper = new ClassIndexWrapper(
590                     overridesAttribute.constraint(), overridesAttribute.constraintIndex()
591             );
592             Map<String, Object> map = overrideParameters.get( wrapper );
593             if ( map == null ) {
594                 map = newHashMap();
595                 overrideParameters.put( wrapper, map );
596             }
597             map.put( overridesAttributeName, value );
598         }
599     }
600
601     private void ensureAttributeIsOverridable(Method m, OverridesAttribute overridesAttribute, String overridesAttributeName) {
602         final Method method = run( GetMethod.action( overridesAttribute.constraint(), overridesAttributeName ) );
603         if ( method == null ) {
604             throw LOG.getOverriddenConstraintAttributeNotFoundException( overridesAttributeName );
605         }
606         Class<?> returnTypeOfOverriddenConstraint = method.getReturnType();
607         if ( !returnTypeOfOverriddenConstraint.equals( m.getReturnType() ) ) {
608             throw LOG.getWrongAttributeTypeForOverriddenConstraintException(
609                     returnTypeOfOverriddenConstraint,
610                     m.getReturnType()
611             );
612         }
613     }
614
615     private Set<ConstraintDescriptorImpl<?>> parseComposingConstraints(ConstraintHelper constraintHelper, Constrainable constrainable,
616             ConstraintType constraintType) {
617         Set<ConstraintDescriptorImpl<?>> composingConstraintsSet = newHashSet();
618         Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = parseOverrideParameters();
619         Map<Class<? extends Annotation>, ComposingConstraintAnnotationLocation> composingConstraintLocations = new HashMap<>();
620
621         for ( Annotation declaredAnnotation : annotationDescriptor.getType().getDeclaredAnnotations() ) {
622             Class<? extends Annotation> declaredAnnotationType = declaredAnnotation.annotationType();
623             if ( NON_COMPOSING_CONSTRAINT_ANNOTATIONS.contains( declaredAnnotationType.getName() ) ) {
624                 // ignore the usual suspects which will be in almost any constraint, but are no composing constraint
625                 continue;
626             }
627
628             if ( constraintHelper.isConstraintAnnotation( declaredAnnotationType ) ) {
629                 if ( composingConstraintLocations.containsKey( declaredAnnotationType )
630                         && !ComposingConstraintAnnotationLocation.DIRECT.equals( composingConstraintLocations.get( declaredAnnotationType ) ) ) {
631                     throw LOG.getCannotMixDirectAnnotationAndListContainerOnComposedConstraintException( annotationDescriptor.getType(), declaredAnnotationType );
632                 }
633
634                 ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
635                         constraintHelper,
636                         constrainable,
637                         overrideParameters,
638                         OVERRIDES_PARAMETER_DEFAULT_INDEX,
639                         declaredAnnotation,
640                         constraintType
641                 );
642                 composingConstraintsSet.add( descriptor );
643                 composingConstraintLocations.put( declaredAnnotationType, ComposingConstraintAnnotationLocation.DIRECT );
644                 LOG.debugf( "Adding composing constraint: %s.", descriptor );
645             }
646             else if ( constraintHelper.isMultiValueConstraint( declaredAnnotationType ) ) {
647                 List<Annotation> multiValueConstraints = constraintHelper.getConstraintsFromMultiValueConstraint( declaredAnnotation );
648                 int index = 0;
649                 for ( Annotation constraintAnnotation : multiValueConstraints ) {
650                     if ( composingConstraintLocations.containsKey( constraintAnnotation.annotationType() )
651                             && !ComposingConstraintAnnotationLocation.IN_CONTAINER.equals( composingConstraintLocations.get( constraintAnnotation.annotationType() ) ) ) {
652                         throw LOG.getCannotMixDirectAnnotationAndListContainerOnComposedConstraintException( annotationDescriptor.getType(),
653                                 constraintAnnotation.annotationType() );
654                     }
655
656                     ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
657                             constraintHelper,
658                             constrainable,
659                             overrideParameters,
660                             index,
661                             constraintAnnotation,
662                             constraintType
663                     );
664                     composingConstraintsSet.add( descriptor );
665                     composingConstraintLocations.put( constraintAnnotation.annotationType(), ComposingConstraintAnnotationLocation.IN_CONTAINER );
666                     LOG.debugf( "Adding composing constraint: %s.", descriptor );
667                     index++;
668                 }
669             }
670         }
671         return CollectionHelper.toImmutableSet( composingConstraintsSet );
672     }
673
674     private CompositionType parseCompositionType(ConstraintHelper constraintHelper) {
675         for ( Annotation declaredAnnotation : annotationDescriptor.getType().getDeclaredAnnotations() ) {
676             Class<? extends Annotation> declaredAnnotationType = declaredAnnotation.annotationType();
677             if ( NON_COMPOSING_CONSTRAINT_ANNOTATIONS.contains( declaredAnnotationType.getName() ) ) {
678                 // ignore the usual suspects which will be in almost any constraint, but are no composing constraint
679                 continue;
680             }
681
682             if ( constraintHelper.isConstraintComposition( declaredAnnotationType ) ) {
683                 if ( LOG.isDebugEnabled() ) {
684                     LOG.debugf( "Adding Bool %s.", declaredAnnotationType.getName() );
685                 }
686                 return ( (ConstraintComposition) declaredAnnotation ).value();
687             }
688         }
689         return CompositionType.AND;
690     }
691
692     private <U extends Annotation> ConstraintDescriptorImpl<U> createComposingConstraintDescriptor(
693             ConstraintHelper constraintHelper,
694             Constrainable constrainable,
695             Map<ClassIndexWrapper, Map<String, Object>> overrideParameters,
696             int index,
697             U constraintAnnotation,
698             ConstraintType constraintType) {
699
700         @SuppressWarnings("unchecked")
701         final Class<U> annotationType = (Class<U>) constraintAnnotation.annotationType();
702
703         // use a annotation proxy
704         ConstraintAnnotationDescriptor.Builder<U> annotationDescriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>(
705                 annotationType, run( GetAnnotationAttributes.action( constraintAnnotation ) )
706         );
707
708         // get the right override parameters
709         Map<String, Object> overrides = overrideParameters.get(
710                 new ClassIndexWrapper(
711                         annotationType, index
712                 )
713         );
714         if ( overrides != null ) {
715             for ( Map.Entry<String, Object> entry : overrides.entrySet() ) {
716                 annotationDescriptorBuilder.setAttribute( entry.getKey(), entry.getValue() );
717             }
718         }
719
720         //propagate inherited attributes to composing constraints
721         annotationDescriptorBuilder.setGroups( groups.toArray( new Class<?>[groups.size()] ) );
722         annotationDescriptorBuilder.setPayload( payloads.toArray( new Class<?>[payloads.size()] ) );
723         if ( annotationDescriptorBuilder.hasAttribute( ConstraintHelper.VALIDATION_APPLIES_TO ) ) {
724             ConstraintTarget validationAppliesTo = getValidationAppliesTo();
725
726             // composed constraint does not declare validationAppliesTo() itself
727             if ( validationAppliesTo == null ) {
728                 if ( constraintType == ConstraintType.CROSS_PARAMETER ) {
729                     validationAppliesTo = ConstraintTarget.PARAMETERS;
730                 }
731                 else {
732                      validationAppliesTo = ConstraintTarget.IMPLICIT;
733                 }
734             }
735
736             annotationDescriptorBuilder.setAttribute( ConstraintHelper.VALIDATION_APPLIES_TO, validationAppliesTo );
737         }
738
739         return new ConstraintDescriptorImpl<>(
740                 constraintHelper, constrainable, annotationDescriptorBuilder.build(), constraintLocationKind, null, definedOn, constraintType
741         );
742     }
743
744     /**
745      * Runs the given privileged action, using a privileged block if required.
746      * <p>
747      * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
748      * privileged actions within HV's protection domain.
749      */

750     private static <P> P run(PrivilegedAction<P> action) {
751         return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
752     }
753
754     /**
755      * @return the compositionType
756      */

757     public CompositionType getCompositionType() {
758         return compositionType;
759     }
760
761     /**
762      * A wrapper class to keep track for which composing constraints (class and index) a given attribute override applies to.
763      */

764     private static class ClassIndexWrapper {
765         final Class<?> clazz;
766         final int index;
767
768         ClassIndexWrapper(Class<?> clazz, int index) {
769             this.clazz = clazz;
770             this.index = index;
771         }
772
773         @Override
774         public boolean equals(Object o) {
775             if ( this == o ) {
776                 return true;
777             }
778             if ( o == null || getClass() != o.getClass() ) {
779                 return false;
780             }
781
782             @SuppressWarnings("unchecked"// safe due to the check above
783             ClassIndexWrapper that = (ClassIndexWrapper) o;
784
785             if ( index != that.index ) {
786                 return false;
787             }
788
789             return clazz.equals( that.clazz );
790         }
791
792         @Override
793         public int hashCode() {
794             int result = clazz != null ? clazz.hashCode() : 0;
795             result = 31 * result + index;
796             return result;
797         }
798
799         @Override
800         public String toString() {
801             return "ClassIndexWrapper [clazz=" + StringHelper.toShortString( clazz ) + ", index=" + index + "]";
802         }
803     }
804
805     /**
806      * The type of a constraint.
807      */

808     public enum ConstraintType {
809         /**
810          * A non cross parameter constraint.
811          */

812         GENERIC,
813
814         /**
815          * A cross parameter constraint.
816          */

817         CROSS_PARAMETER
818     }
819
820     /**
821      * The location of a composing constraint.
822      */

823     private enum ComposingConstraintAnnotationLocation {
824         /**
825          * The annotation is located directly on the class.
826          */

827         DIRECT,
828
829         /**
830          * The annotation is defined in a container, typically a List container.
831          */

832         IN_CONTAINER
833     }
834 }
835