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.engine.valueextraction;
8
9 import java.lang.reflect.Type;
10 import java.lang.reflect.TypeVariable;
11 import java.security.AccessController;
12 import java.security.PrivilegedAction;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.LinkedHashMap;
17 import java.util.LinkedHashSet;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.stream.Collectors;
21
22 import javax.validation.ConstraintDeclarationException;
23 import javax.validation.ValidationException;
24 import javax.validation.valueextraction.ValueExtractor;
25
26 import org.hibernate.validator.internal.util.privilegedactions.LoadClass;
27 import org.hibernate.validator.internal.util.stereotypes.Immutable;
28
29 /**
30  * @author Gunnar Morling
31  * @author Guillaume Smet
32  * @author Marko Bekhta
33  */

34 public class ValueExtractorManager {
35
36     @Immutable
37     public static final Set<ValueExtractorDescriptor> SPEC_DEFINED_EXTRACTORS;
38
39     /**
40      * Set this environment variable to true to ensure the JavaFX integrations are disabled.
41      * Normally the JavaFX extensions are enabled if and only if JavaFX is found on classpath.
42      */

43     private static final String HIBERNATE_VALIDATOR_FORCE_DISABLE_JAVAFX_INTEGRATION = "org.hibernate.validator.force-disable-javafx-integration";
44
45     static {
46         LinkedHashSet<ValueExtractorDescriptor> specDefinedExtractors = new LinkedHashSet<>();
47
48         if ( isJavaFxExtensionsEnabled() ) {
49             specDefinedExtractors.add( ObservableValueValueExtractor.DESCRIPTOR );
50             specDefinedExtractors.add( ListPropertyValueExtractor.DESCRIPTOR );
51             specDefinedExtractors.add( ReadOnlyListPropertyValueExtractor.DESCRIPTOR );
52             specDefinedExtractors.add( MapPropertyValueExtractor.DESCRIPTOR );
53             specDefinedExtractors.add( ReadOnlyMapPropertyValueExtractor.DESCRIPTOR );
54             specDefinedExtractors.add( MapPropertyKeyExtractor.DESCRIPTOR );
55             specDefinedExtractors.add( ReadOnlyMapPropertyKeyExtractor.DESCRIPTOR );
56             specDefinedExtractors.add( SetPropertyValueExtractor.DESCRIPTOR );
57             specDefinedExtractors.add( ReadOnlySetPropertyValueExtractor.DESCRIPTOR );
58         }
59
60         specDefinedExtractors.add( ByteArrayValueExtractor.DESCRIPTOR );
61         specDefinedExtractors.add( ShortArrayValueExtractor.DESCRIPTOR );
62         specDefinedExtractors.add( IntArrayValueExtractor.DESCRIPTOR );
63         specDefinedExtractors.add( LongArrayValueExtractor.DESCRIPTOR );
64         specDefinedExtractors.add( FloatArrayValueExtractor.DESCRIPTOR );
65         specDefinedExtractors.add( DoubleArrayValueExtractor.DESCRIPTOR );
66         specDefinedExtractors.add( CharArrayValueExtractor.DESCRIPTOR );
67         specDefinedExtractors.add( BooleanArrayValueExtractor.DESCRIPTOR );
68         specDefinedExtractors.add( ObjectArrayValueExtractor.DESCRIPTOR );
69
70         specDefinedExtractors.add( ListValueExtractor.DESCRIPTOR );
71
72         specDefinedExtractors.add( MapValueExtractor.DESCRIPTOR );
73         specDefinedExtractors.add( MapKeyExtractor.DESCRIPTOR );
74
75         specDefinedExtractors.add( IterableValueExtractor.DESCRIPTOR );
76
77         specDefinedExtractors.add( OptionalValueExtractor.DESCRIPTOR );
78         specDefinedExtractors.add( OptionalIntValueExtractor.DESCRIPTOR );
79         specDefinedExtractors.add( OptionalDoubleValueExtractor.DESCRIPTOR );
80         specDefinedExtractors.add( OptionalLongValueExtractor.DESCRIPTOR );
81
82         SPEC_DEFINED_EXTRACTORS = Collections.unmodifiableSet( specDefinedExtractors );
83     }
84
85     @Immutable
86     private final Map<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> registeredValueExtractors;
87
88     private final ValueExtractorResolver valueExtractorResolver;
89
90     public ValueExtractorManager(Set<ValueExtractor<?>> externalExtractors) {
91         LinkedHashMap<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> tmpValueExtractors = new LinkedHashMap<>();
92
93         // first all built-in extractors
94         for ( ValueExtractorDescriptor descriptor : SPEC_DEFINED_EXTRACTORS ) {
95             tmpValueExtractors.put( descriptor.getKey(), descriptor );
96         }
97
98         // then any custom ones, overriding the built-in ones
99         for ( ValueExtractor<?> valueExtractor : externalExtractors ) {
100             ValueExtractorDescriptor descriptor = new ValueExtractorDescriptor( valueExtractor );
101             tmpValueExtractors.put( descriptor.getKey(), descriptor );
102         }
103
104         registeredValueExtractors = Collections.unmodifiableMap( tmpValueExtractors );
105         valueExtractorResolver = new ValueExtractorResolver( new HashSet<>( registeredValueExtractors.values() ) );
106     }
107
108     public ValueExtractorManager(ValueExtractorManager template,
109             Map<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> externalValueExtractorDescriptors) {
110         LinkedHashMap<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> tmpValueExtractors = new LinkedHashMap<>( template.registeredValueExtractors );
111         tmpValueExtractors.putAll( externalValueExtractorDescriptors );
112
113         registeredValueExtractors = Collections.unmodifiableMap( tmpValueExtractors );
114         valueExtractorResolver = new ValueExtractorResolver( new HashSet<>( registeredValueExtractors.values() ) );
115     }
116
117     public static Set<ValueExtractor<?>> getDefaultValueExtractors() {
118         return SPEC_DEFINED_EXTRACTORS.stream()
119                 .map( d -> d.getValueExtractor() )
120                 .collect( Collectors.collectingAndThen( Collectors.toSet(), Collections::unmodifiableSet ) );
121     }
122
123     /**
124      * Used to find the maximally specific and container element compliant value extractor based on the runtime type.
125      * <p>
126      * The maximally specific one is chosen among the candidates passed to this method.
127      * <p>
128      * Used for cascading validation.
129      *
130      * @see ValueExtractorResolver#getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type,
131      * TypeVariable, Class, Collection)
132      * @throws ConstraintDeclarationException if more than 2 maximally specific container-element-compliant value extractors are found
133      */

134     public ValueExtractorDescriptor getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(Type declaredType, TypeVariable<?> typeParameter,
135             Class<?> runtimeType, Collection<ValueExtractorDescriptor> valueExtractorCandidates) {
136
137         if ( valueExtractorCandidates.size() == 1 ) {
138             return valueExtractorCandidates.iterator().next();
139         }
140         else if ( !valueExtractorCandidates.isEmpty() ) {
141             return valueExtractorResolver.getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(
142                     declaredType,
143                     typeParameter,
144                     runtimeType,
145                     valueExtractorCandidates
146             );
147         }
148         else {
149             return valueExtractorResolver.getMaximallySpecificAndRuntimeContainerElementCompliantValueExtractor(
150                     declaredType,
151                     typeParameter,
152                     runtimeType,
153                     registeredValueExtractors.values()
154             );
155         }
156     }
157
158     public ValueExtractorResolver getResolver() {
159         return valueExtractorResolver;
160     }
161
162     @Override
163     public int hashCode() {
164         final int prime = 31;
165         int result = 1;
166         result = prime * result + ( ( registeredValueExtractors == null ) ? 0 : registeredValueExtractors.hashCode() );
167         return result;
168     }
169
170     @Override
171     public boolean equals(Object obj) {
172         if ( this == obj ) {
173             return true;
174         }
175         if ( obj == null ) {
176             return false;
177         }
178         if ( getClass() != obj.getClass() ) {
179             return false;
180         }
181         ValueExtractorManager other = (ValueExtractorManager) obj;
182
183         return registeredValueExtractors.equals( other.registeredValueExtractors );
184     }
185
186     private static boolean isJavaFxExtensionsEnabled() {
187         if ( isJavaFxForcefullyDisabled() ) {
188             return false;
189         }
190         else {
191             return isJavaFxInClasspath();
192         }
193     }
194
195     private static boolean isJavaFxForcefullyDisabled() {
196         return run( new PrivilegedAction<Boolean>() {
197             @Override
198             public Boolean run() {
199                  return Boolean.valueOf( Boolean.getBoolean( HIBERNATE_VALIDATOR_FORCE_DISABLE_JAVAFX_INTEGRATION ) );
200             }
201         } );
202     }
203
204     private static boolean isJavaFxInClasspath() {
205         return isClassPresent( "javafx.beans.value.ObservableValue"false );
206     }
207
208     private static boolean isClassPresent(String className, boolean fallbackOnTCCL) {
209         try {
210             run( LoadClass.action( className, ValueExtractorManager.class.getClassLoader(), fallbackOnTCCL ) );
211             return true;
212         }
213         catch (ValidationException e) {
214             return false;
215         }
216     }
217
218     public void clear() {
219         valueExtractorResolver.clear();
220     }
221
222     /**
223      * Runs the given privileged action, using a privileged block if required.
224      * <p>
225      * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
226      * privileged actions within HV's protection domain.
227      */

228     private static <T> T run(PrivilegedAction<T> action) {
229         return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
230     }
231 }
232