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.aggregated;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;
10 import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
11 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
12
13 import java.lang.reflect.Type;
14 import java.util.Arrays;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Set;
20
21 import javax.validation.ElementKind;
22 import javax.validation.metadata.ParameterDescriptor;
23
24 import org.hibernate.validator.internal.engine.ConstraintCreationContext;
25 import org.hibernate.validator.internal.engine.MethodValidationConfiguration;
26 import org.hibernate.validator.internal.metadata.aggregated.rule.MethodConfigurationRule;
27 import org.hibernate.validator.internal.metadata.core.MetaConstraint;
28 import org.hibernate.validator.internal.metadata.descriptor.ExecutableDescriptorImpl;
29 import org.hibernate.validator.internal.metadata.raw.ConstrainedElement;
30 import org.hibernate.validator.internal.metadata.raw.ConstrainedElement.ConstrainedElementKind;
31 import org.hibernate.validator.internal.metadata.raw.ConstrainedExecutable;
32 import org.hibernate.validator.internal.metadata.raw.ConstrainedParameter;
33 import org.hibernate.validator.internal.properties.Callable;
34 import org.hibernate.validator.internal.properties.Signature;
35 import org.hibernate.validator.internal.util.CollectionHelper;
36 import org.hibernate.validator.internal.util.ExecutableHelper;
37 import org.hibernate.validator.internal.util.ExecutableParameterNameProvider;
38 import org.hibernate.validator.internal.util.stereotypes.Immutable;
39
40 /**
41  * An aggregated view of the constraint related meta data for a given method or
42  * constructors and in (case of methods) all the methods in the inheritance
43  * hierarchy which it overrides or implements.
44  * <p>
45  * Instances are retrieved by creating a {@link Builder} and adding all required
46  * {@link ConstrainedExecutable} objects to it. Instances are read-only after
47  * creation.
48  * </p>
49  * <p>
50  * Identity is solely based on the method's name and parameter types, hence sets
51  * and similar collections of this type may only be created in the scope of one
52  * Java type.
53  * </p>
54  *
55  * @author Gunnar Morling
56  */

57 public class ExecutableMetaData extends AbstractConstraintMetaData {
58
59     private final Class<?>[] parameterTypes;
60
61     @Immutable
62     private final List<ParameterMetaData> parameterMetaDataList;
63
64     private final ValidatableParametersMetaData validatableParametersMetaData;
65
66     @Immutable
67     private final Set<MetaConstraint<?>> crossParameterConstraints;
68
69     private final boolean isGetter;
70
71     /**
72      * Set of signatures for storing this object in maps etc. Will only contain more than one entry in case this method
73      * overrides a super type method with generic parameters, in which case the signature of the super-type and the
74      * sub-type method will differ.
75      */

76     private final Set<Signature> signatures;
77
78     private final ReturnValueMetaData returnValueMetaData;
79     private final ElementKind kind;
80
81     private ExecutableMetaData(
82             String name,
83             Type returnType,
84             Class<?>[] parameterTypes,
85             ElementKind kind,
86             Set<Signature> signatures,
87             Set<MetaConstraint<?>> returnValueConstraints,
88             Set<MetaConstraint<?>> returnValueContainerElementConstraints,
89             List<ParameterMetaData> parameterMetaDataList,
90             Set<MetaConstraint<?>> crossParameterConstraints,
91             CascadingMetaData cascadingMetaData,
92             boolean isConstrained,
93             boolean isGetter) {
94         super(
95                 name,
96                 returnType,
97                 returnValueConstraints,
98                 returnValueContainerElementConstraints,
99                 cascadingMetaData.isMarkedForCascadingOnAnnotatedObjectOrContainerElements(),
100                 isConstrained
101         );
102
103         this.parameterTypes = parameterTypes;
104         this.parameterMetaDataList = CollectionHelper.toImmutableList( parameterMetaDataList );
105         this.validatableParametersMetaData = new ValidatableParametersMetaData( parameterMetaDataList );
106         this.crossParameterConstraints = CollectionHelper.toImmutableSet( crossParameterConstraints );
107         this.signatures = signatures;
108         this.returnValueMetaData = new ReturnValueMetaData(
109                 returnType,
110                 returnValueConstraints,
111                 returnValueContainerElementConstraints,
112                 cascadingMetaData
113         );
114         this.isGetter = isGetter;
115         this.kind = kind;
116     }
117
118     /**
119      * Returns meta data for the specified parameter of the represented executable.
120      *
121      * @param parameterIndex the index of the parameter
122      *
123      * @return Meta data for the specified parameter. Will never be {@code null}.
124      */

125     public ParameterMetaData getParameterMetaData(int parameterIndex) {
126         return parameterMetaDataList.get( parameterIndex );
127     }
128
129     public Class<?>[] getParameterTypes() {
130         return parameterTypes;
131     }
132
133     /**
134      * Returns the signature(s) of the method represented by this meta data object, based on the represented
135      * executable's name and its parameter types.
136      *
137      * @return The signatures of this meta data object. Will only contain more than one element in case the represented
138      * method represents a sub-type method overriding a super-type method using a generic type parameter in its
139      * parameters.
140      */

141     public Set<Signature> getSignatures() {
142         return signatures;
143     }
144
145     /**
146      * Returns the cross-parameter constraints declared for the represented
147      * method or constructor.
148      *
149      * @return the cross-parameter constraints declared for the represented
150      * method or constructor. May be empty but will never be
151      * {@code null}.
152      */

153     public Set<MetaConstraint<?>> getCrossParameterConstraints() {
154         return crossParameterConstraints;
155     }
156
157     public ValidatableParametersMetaData getValidatableParametersMetaData() {
158         return validatableParametersMetaData;
159     }
160
161     public ReturnValueMetaData getReturnValueMetaData() {
162         return returnValueMetaData;
163     }
164
165     @Override
166     public ExecutableDescriptorImpl asDescriptor(boolean defaultGroupSequenceRedefined, List<Class<?>> defaultGroupSequence) {
167         return new ExecutableDescriptorImpl(
168                 getType(),
169                 getName(),
170                 asDescriptors( getCrossParameterConstraints() ),
171                 returnValueMetaData.asDescriptor(
172                         defaultGroupSequenceRedefined,
173                         defaultGroupSequence
174                 ),
175                 parametersAsDescriptors( defaultGroupSequenceRedefined, defaultGroupSequence ),
176                 defaultGroupSequenceRedefined,
177                 isGetter,
178                 defaultGroupSequence
179         );
180     }
181
182     private List<ParameterDescriptor> parametersAsDescriptors(boolean defaultGroupSequenceRedefined, List<Class<?>> defaultGroupSequence) {
183         List<ParameterDescriptor> parameterDescriptorList = newArrayList();
184
185         for ( ParameterMetaData parameterMetaData : parameterMetaDataList ) {
186             parameterDescriptorList.add(
187                     parameterMetaData.asDescriptor(
188                             defaultGroupSequenceRedefined,
189                             defaultGroupSequence
190                     )
191             );
192         }
193
194         return parameterDescriptorList;
195     }
196
197     @Override
198     public ElementKind getKind() {
199         return kind;
200     }
201
202     @Override
203     public String toString() {
204         StringBuilder parameterBuilder = new StringBuilder();
205
206         for ( Class<?> oneParameterType : getParameterTypes() ) {
207             parameterBuilder.append( oneParameterType.getSimpleName() );
208             parameterBuilder.append( ", " );
209         }
210
211         String parameters =
212                 parameterBuilder.length() > 0 ?
213                         parameterBuilder.substring( 0, parameterBuilder.length() - 2 ) :
214                         parameterBuilder.toString();
215
216         return "ExecutableMetaData [executable=" + getType() + " " + getName() + "(" + parameters + "), isCascading=" + isCascading() + ", isConstrained="
217                 + isConstrained() + "]";
218     }
219
220     @Override
221     public int hashCode() {
222         final int prime = 31;
223         int result = super.hashCode();
224         result = prime * result + Arrays.hashCode( parameterTypes );
225         return result;
226     }
227
228     @Override
229     public boolean equals(Object obj) {
230         if ( this == obj ) {
231             return true;
232         }
233         if ( !super.equals( obj ) ) {
234             return false;
235         }
236         if ( getClass() != obj.getClass() ) {
237             return false;
238         }
239         ExecutableMetaData other = (ExecutableMetaData) obj;
240         if ( !Arrays.equals( parameterTypes, other.parameterTypes ) ) {
241             return false;
242         }
243         return true;
244     }
245
246     /**
247      * Creates new {@link ExecutableMetaData} instances.
248      *
249      * @author Gunnar Morling
250      * @author Kevin Pollet &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
251      */

252     public static class Builder extends MetaDataBuilder {
253         private final Set<Signature> signatures = newHashSet();
254
255         /**
256          * Either CONSTRUCTOR, METHOD or GETTER.
257          */

258         private final ConstrainedElementKind kind;
259         private final Set<ConstrainedExecutable> constrainedExecutables = newHashSet();
260         private Callable callable;
261         private final Set<MetaConstraint<?>> crossParameterConstraints = newHashSet();
262         private final Set<MethodConfigurationRule> rules;
263         private boolean isConstrained = false;
264         private CascadingMetaDataBuilder cascadingMetaDataBuilder;
265
266         /**
267          * Holds a merged representation of the configurations for one method
268          * from the hierarchy contributed by the different meta data providers.
269          * Used to check for violations of the Liskov substitution principle by
270          * e.g. adding parameter constraints in sub type methods.
271          */

272         private final Map<Class<?>, ConstrainedExecutable> executablesByDeclaringType = newHashMap();
273
274         private final ExecutableHelper executableHelper;
275
276         private final ExecutableParameterNameProvider parameterNameProvider;
277
278         public Builder(
279                 Class<?> beanClass,
280                 ConstrainedExecutable constrainedExecutable,
281                 ConstraintCreationContext constraintCreationContext,
282                 ExecutableHelper executableHelper,
283                 ExecutableParameterNameProvider parameterNameProvider,
284                 MethodValidationConfiguration methodValidationConfiguration) {
285             super( beanClass, constraintCreationContext );
286
287             this.executableHelper = executableHelper;
288             this.parameterNameProvider = parameterNameProvider;
289             this.kind = constrainedExecutable.getKind();
290             this.callable = constrainedExecutable.getCallable();
291             this.rules = methodValidationConfiguration.getConfiguredRuleSet();
292
293             add( constrainedExecutable );
294         }
295
296         @Override
297         public boolean accepts(ConstrainedElement constrainedElement) {
298             if ( kind != constrainedElement.getKind() ) {
299                 return false;
300             }
301
302             Callable candidate = ( (ConstrainedExecutable) constrainedElement ).getCallable();
303
304             //are the locations equal (created by different builders) or
305             //does one of the executables override the other one?
306             return isResolvedToSameMethodInHierarchy( callable, candidate );
307         }
308
309         private boolean isResolvedToSameMethodInHierarchy(Callable first, Callable other) {
310             if ( isConstructor( first ) || isConstructor( other ) ) {
311                 return first.equals( other );
312             }
313
314             return first.isResolvedToSameMethodInHierarchy( executableHelper, getBeanClass(), other );
315         }
316
317         private boolean overrides(Callable first, Callable other) {
318             if ( isConstructor( first ) || isConstructor( other ) ) {
319                 return false;
320             }
321
322             return executableHelper.overrides( first, other );
323         }
324
325         private boolean isConstructor(Callable callable) {
326             return callable.getConstrainedElementKind() == ConstrainedElementKind.CONSTRUCTOR;
327         }
328
329         @Override
330         public final void add(ConstrainedElement constrainedElement) {
331             super.add( constrainedElement );
332             ConstrainedExecutable constrainedExecutable = (ConstrainedExecutable) constrainedElement;
333
334             signatures.add( constrainedExecutable.getCallable().getSignature() );
335
336             constrainedExecutables.add( constrainedExecutable );
337             isConstrained = isConstrained || constrainedExecutable.isConstrained();
338             crossParameterConstraints.addAll( constrainedExecutable.getCrossParameterConstraints() );
339             if ( cascadingMetaDataBuilder == null ) {
340                 cascadingMetaDataBuilder = constrainedExecutable.getCascadingMetaDataBuilder();
341             }
342             else {
343                 cascadingMetaDataBuilder = cascadingMetaDataBuilder.merge( constrainedExecutable.getCascadingMetaDataBuilder() );
344             }
345
346             addToExecutablesByDeclaringType( constrainedExecutable );
347
348             // keep the "lowest" executable in hierarchy to make sure any type parameters declared on super-types (and
349             // used in overridden methods) are resolved for the specific sub-type we are interested in
350             if ( callable != null && overrides(
351                     constrainedExecutable.getCallable(),
352                     callable
353             ) ) {
354                 callable = constrainedExecutable.getCallable();
355             }
356         }
357
358         /**
359          * Merges the given executable with the metadata contributed by other
360          * providers for the same executable in the hierarchy.
361          *
362          * @param executable The executable to merge.
363          */

364         private void addToExecutablesByDeclaringType(ConstrainedExecutable executable) {
365             Class<?> beanClass = executable.getCallable().getDeclaringClass();
366             ConstrainedExecutable mergedExecutable = executablesByDeclaringType.get( beanClass );
367
368             if ( mergedExecutable != null ) {
369                 mergedExecutable = mergedExecutable.merge( executable );
370             }
371             else {
372                 mergedExecutable = executable;
373             }
374
375             executablesByDeclaringType.put( beanClass, mergedExecutable );
376         }
377
378         @Override
379         public ExecutableMetaData build() {
380             assertCorrectnessOfConfiguration();
381
382             return new ExecutableMetaData(
383                     callable.getName(),
384                     callable.getType(),
385                     callable.getParameterTypes(),
386                     kind == ConstrainedElementKind.CONSTRUCTOR ? ElementKind.CONSTRUCTOR : ElementKind.METHOD,
387                     kind == ConstrainedElementKind.CONSTRUCTOR ? Collections.singleton( callable.getSignature() ) :
388                             CollectionHelper.toImmutableSet( signatures ),
389                     adaptOriginsAndImplicitGroups( getDirectConstraints() ),
390                     adaptOriginsAndImplicitGroups( getContainerElementConstraints() ),
391                     findParameterMetaData(),
392                     adaptOriginsAndImplicitGroups( crossParameterConstraints ),
393                     cascadingMetaDataBuilder.build( constraintCreationContext.getValueExtractorManager(), callable ),
394                     isConstrained,
395                     kind == ConstrainedElementKind.GETTER
396             );
397         }
398
399         /**
400          * Finds the one executable from the underlying hierarchy with parameter
401          * constraints. If no executable in the hierarchy is parameter constrained,
402          * the parameter meta data from this builder's base executable is returned.
403          *
404          * @return The parameter meta data for this builder's executable.
405          */

406         private List<ParameterMetaData> findParameterMetaData() {
407             List<ParameterMetaData.Builder> parameterBuilders = null;
408
409             for ( ConstrainedExecutable oneExecutable : constrainedExecutables ) {
410                 if ( parameterBuilders == null ) {
411                     parameterBuilders = newArrayList();
412
413                     for ( ConstrainedParameter oneParameter : oneExecutable.getAllParameterMetaData() ) {
414                         parameterBuilders.add(
415                                 new ParameterMetaData.Builder(
416                                         callable.getDeclaringClass(),
417                                         oneParameter,
418                                         constraintCreationContext,
419                                         parameterNameProvider
420                                 )
421                         );
422                     }
423                 }
424                 else {
425                     int i = 0;
426                     for ( ConstrainedParameter oneParameter : oneExecutable.getAllParameterMetaData() ) {
427                         parameterBuilders.get( i ).add( oneParameter );
428                         i++;
429                     }
430                 }
431             }
432
433             List<ParameterMetaData> parameterMetaDatas = newArrayList();
434
435             for ( ParameterMetaData.Builder oneBuilder : parameterBuilders ) {
436                 parameterMetaDatas.add( oneBuilder.build() );
437             }
438
439             return parameterMetaDatas;
440         }
441
442         /**
443          * <p>
444          * Checks the configuration of this method for correctness as per the
445          * rules outlined in the Bean Validation specification, section 4.5.5
446          * ("Method constraints in inheritance hierarchies").
447          * </p>
448          * <p>
449          * In particular, overriding methods in sub-types may not add parameter
450          * constraints and the return value of an overriding method may not be
451          * marked as cascaded if the return value is marked as cascaded already
452          * on the overridden method.
453          * </p>
454          *
455          * @throws javax.validation.ConstraintDeclarationException In case any of the rules mandated by the
456          * specification are violated.
457          */

458         private void assertCorrectnessOfConfiguration() {
459             for ( Entry<Class<?>, ConstrainedExecutable> entry : executablesByDeclaringType.entrySet() ) {
460                 for ( Entry<Class<?>, ConstrainedExecutable> otherEntry : executablesByDeclaringType.entrySet() ) {
461                     for ( MethodConfigurationRule rule : rules ) {
462                         rule.apply( entry.getValue(), otherEntry.getValue() );
463                     }
464                 }
465             }
466         }
467     }
468 }
469