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.provider;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
10
11 import java.lang.invoke.MethodHandles;
12 import java.util.HashMap;
13 import java.util.Map;
14 import java.util.Set;
15
16 import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
17 import org.hibernate.validator.internal.engine.ConstraintCreationContext;
18 import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions;
19 import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl;
20 import org.hibernate.validator.internal.metadata.raw.BeanConfiguration;
21 import org.hibernate.validator.internal.util.CollectionHelper;
22 import org.hibernate.validator.internal.util.Contracts;
23 import org.hibernate.validator.internal.util.logging.Log;
24 import org.hibernate.validator.internal.util.logging.LoggerFactory;
25 import org.hibernate.validator.internal.util.stereotypes.Immutable;
26
27 /**
28  * A {@link MetaDataProvider} based on the programmatic constraint API.
29  *
30  * @author Gunnar Morling
31  */

32 public class ProgrammaticMetaDataProvider implements MetaDataProvider {
33
34     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
35
36     // cached against the fqcn of a class. not a class instance itself (HV-479)
37     @Immutable
38     private final Map<String, BeanConfiguration<?>> configuredBeans;
39     private final AnnotationProcessingOptions annotationProcessingOptions;
40
41     public ProgrammaticMetaDataProvider(ConstraintCreationContext constraintCreationContext,
42                                         Set<DefaultConstraintMapping> constraintMappings) {
43         Contracts.assertNotNull( constraintMappings );
44
45         configuredBeans = CollectionHelper.toImmutableMap(
46                 createBeanConfigurations( constraintMappings, constraintCreationContext )
47         );
48
49         assertUniquenessOfConfiguredTypes( constraintMappings );
50         annotationProcessingOptions = mergeAnnotationProcessingOptions( constraintMappings );
51     }
52
53     private static void assertUniquenessOfConfiguredTypes(Set<DefaultConstraintMapping> mappings) {
54         Set<Class<?>> allConfiguredTypes = newHashSet();
55
56         for ( DefaultConstraintMapping constraintMapping : mappings ) {
57             for ( Class<?> configuredType : constraintMapping.getConfiguredTypes() ) {
58                 if ( allConfiguredTypes.contains( configuredType ) ) {
59                     throw LOG.getBeanClassHasAlreadyBeConfiguredViaProgrammaticApiException( configuredType );
60                 }
61             }
62
63             allConfiguredTypes.addAll( constraintMapping.getConfiguredTypes() );
64         }
65     }
66
67     private static Map<String, BeanConfiguration<?>> createBeanConfigurations(Set<DefaultConstraintMapping> mappings,
68             ConstraintCreationContext constraintCreationContext) {
69         final Map<String, BeanConfiguration<?>> configuredBeans = new HashMap<>();
70         for ( DefaultConstraintMapping mapping : mappings ) {
71             Set<BeanConfiguration<?>> beanConfigurations = mapping.getBeanConfigurations( constraintCreationContext );
72
73             for ( BeanConfiguration<?> beanConfiguration : beanConfigurations ) {
74                 configuredBeans.put( beanConfiguration.getBeanClass().getName(), beanConfiguration );
75             }
76         }
77         return configuredBeans;
78     }
79
80     /**
81      * Creates a single merged {@code AnnotationProcessingOptions} in case multiple programmatic mappings are provided.
82      * <p>
83      * Note that it is made sure at this point that no element (type, property, method etc.) is configured more than once within
84      * all the given contexts. So the "merge" pulls together the information for all configured elements, but it will never
85      * merge several configurations for one given element.
86      *
87      * @param mappings set of mapping contexts providing annotation processing options to be merged
88      *
89      * @return a single annotation processing options object
90      */

91     private static AnnotationProcessingOptions mergeAnnotationProcessingOptions(Set<DefaultConstraintMapping> mappings) {
92         // if we only have one mapping we can return the context of just this mapping
93         if ( mappings.size() == 1 ) {
94             return mappings.iterator().next().getAnnotationProcessingOptions();
95         }
96
97         AnnotationProcessingOptions options = new AnnotationProcessingOptionsImpl();
98
99         for ( DefaultConstraintMapping mapping : mappings ) {
100             options.merge( mapping.getAnnotationProcessingOptions() );
101         }
102
103         return options;
104     }
105
106     @Override
107     @SuppressWarnings("unchecked")
108     public <T> BeanConfiguration<T> getBeanConfiguration(Class<T> beanClass) {
109         return (BeanConfiguration<T>) configuredBeans.get( beanClass.getName() );
110     }
111
112     @Override
113     public AnnotationProcessingOptions getAnnotationProcessingOptions() {
114         return annotationProcessingOptions;
115     }
116 }
117