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.constraintvalidation;
8
9 import java.lang.annotation.Annotation;
10 import java.lang.invoke.MethodHandles;
11 import java.lang.reflect.Type;
12 import java.util.Iterator;
13 import java.util.Map;
14 import java.util.Map.Entry;
15 import java.util.concurrent.ConcurrentHashMap;
16
17 import javax.validation.ConstraintValidator;
18 import javax.validation.ConstraintValidatorContext;
19 import javax.validation.ConstraintValidatorFactory;
20 import javax.validation.constraints.Null;
21
22 import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
23 import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
24 import org.hibernate.validator.internal.util.Contracts;
25 import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor;
26 import org.hibernate.validator.internal.util.logging.Log;
27 import org.hibernate.validator.internal.util.logging.LoggerFactory;
28
29 /**
30  * Default implementation of the {@link ConstraintValidatorManager}.
31  *
32  * @author Hardy Ferentschik
33  * @author Guillaume Smet
34  */

35 public class ConstraintValidatorManagerImpl extends AbstractConstraintValidatorManagerImpl {
36
37     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
38
39     /**
40      * Dummy {@code ConstraintValidator} used as placeholder for the case that for a given context there exists
41      * no matching constraint validator instance
42      */

43     private static final ConstraintValidator<?, ?> DUMMY_CONSTRAINT_VALIDATOR = new ConstraintValidator<Null, Object>() {
44
45         @Override
46         public boolean isValid(Object value, ConstraintValidatorContext context) {
47             return false;
48         }
49     };
50
51     /**
52      * The most recently used non default constraint validator factory.
53      */

54     private volatile ConstraintValidatorFactory mostRecentlyUsedNonDefaultConstraintValidatorFactory;
55
56     /**
57      * The most recently used non default constraint validator initialization context.
58      */

59     private volatile HibernateConstraintValidatorInitializationContext mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext;
60
61     /**
62      * Used for synchronizing access to {@link #mostRecentlyUsedNonDefaultConstraintValidatorFactory} (which can be
63      * null itself).
64      */

65     private final Object mostRecentlyUsedNonDefaultConstraintValidatorFactoryAndInitializationContextMutex = new Object();
66
67     /**
68      * Cache of initialized {@code ConstraintValidator} instances keyed against validated type, annotation,
69      * constraint validator factory and constraint validator initialization context ({@code CacheKey}).
70      */

71     private final ConcurrentHashMap<CacheKey, ConstraintValidator<?, ?>> constraintValidatorCache;
72
73     /**
74      * Creates a new {@code ConstraintValidatorManager}.
75      *
76      * @param defaultConstraintValidatorFactory the default validator factory
77      * @param defaultConstraintValidatorInitializationContext the default initialization context
78      */

79     public ConstraintValidatorManagerImpl(ConstraintValidatorFactory defaultConstraintValidatorFactory,
80             HibernateConstraintValidatorInitializationContext defaultConstraintValidatorInitializationContext) {
81         super( defaultConstraintValidatorFactory, defaultConstraintValidatorInitializationContext );
82         this.constraintValidatorCache = new ConcurrentHashMap<>();
83     }
84
85     @Override
86     public boolean isPredefinedScope() {
87         return false;
88     }
89
90     /**
91      * @param validatedValueType the type of the value to be validated. Cannot be {@code null}.
92      * @param descriptor the constraint descriptor for which to get an initialized constraint validator. Cannot be {@code null}
93      * @param constraintValidatorFactory constraint factory used to instantiate the constraint validator. Cannot be {@code null}.
94      * @param initializationContext context used on constraint validator initialization
95      * @param <A> the annotation type
96      *
97      * @return an initialized constraint validator for the given type and annotation of the value to be validated.
98      * {@code null} is returned if no matching constraint validator could be found.
99      */

100     @Override
101     public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(
102             Type validatedValueType,
103             ConstraintDescriptorImpl<A> descriptor,
104             ConstraintValidatorFactory constraintValidatorFactory,
105             HibernateConstraintValidatorInitializationContext initializationContext) {
106         Contracts.assertNotNull( validatedValueType );
107         Contracts.assertNotNull( descriptor );
108         Contracts.assertNotNull( constraintValidatorFactory );
109         Contracts.assertNotNull( initializationContext );
110
111         CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(), validatedValueType, constraintValidatorFactory, initializationContext );
112
113         @SuppressWarnings("unchecked")
114         ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key );
115
116         if ( constraintValidator == null ) {
117             constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
118             constraintValidator = cacheValidator( key, constraintValidator );
119         }
120         else {
121             LOG.tracef( "Constraint validator %s found in cache.", constraintValidator );
122         }
123
124         return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
125     }
126
127     private <A extends Annotation> ConstraintValidator<A, ?> cacheValidator(CacheKey key,
128             ConstraintValidator<A, ?> constraintValidator) {
129         // we only cache constraint validator instances for the default and most recently used factory
130         if ( ( key.getConstraintValidatorFactory() != getDefaultConstraintValidatorFactory()
131                 && key.getConstraintValidatorFactory() != mostRecentlyUsedNonDefaultConstraintValidatorFactory ) ||
132                 ( key.getConstraintValidatorInitializationContext() != getDefaultConstraintValidatorInitializationContext()
133                         && key.getConstraintValidatorInitializationContext() != mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext ) ) {
134
135             synchronized ( mostRecentlyUsedNonDefaultConstraintValidatorFactoryAndInitializationContextMutex ) {
136                 if ( key.constraintValidatorFactory != mostRecentlyUsedNonDefaultConstraintValidatorFactory ||
137                         key.constraintValidatorInitializationContext != mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext ) {
138                     clearEntries( mostRecentlyUsedNonDefaultConstraintValidatorFactory, mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext );
139                     mostRecentlyUsedNonDefaultConstraintValidatorFactory = key.getConstraintValidatorFactory();
140                     mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext = key.getConstraintValidatorInitializationContext();
141                 }
142             }
143         }
144
145         @SuppressWarnings("unchecked")
146         ConstraintValidator<A, ?> cached = (ConstraintValidator<A, ?>) constraintValidatorCache.putIfAbsent( key,
147                 constraintValidator != null ? constraintValidator : DUMMY_CONSTRAINT_VALIDATOR );
148
149         return cached != null ? cached : constraintValidator;
150     }
151
152     private void clearEntries(ConstraintValidatorFactory constraintValidatorFactory, HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext) {
153         Iterator<Entry<CacheKey, ConstraintValidator<?, ?>>> cacheEntries = constraintValidatorCache.entrySet().iterator();
154
155         while ( cacheEntries.hasNext() ) {
156             Entry<CacheKey, ConstraintValidator<?, ?>> cacheEntry = cacheEntries.next();
157             if ( cacheEntry.getKey().getConstraintValidatorFactory() == constraintValidatorFactory &&
158                     cacheEntry.getKey().getConstraintValidatorInitializationContext() == constraintValidatorInitializationContext ) {
159                 constraintValidatorFactory.releaseInstance( cacheEntry.getValue() );
160                 cacheEntries.remove();
161             }
162         }
163     }
164
165     @Override
166     public void clear() {
167         for ( Map.Entry<CacheKey, ConstraintValidator<?, ?>> entry : constraintValidatorCache.entrySet() ) {
168             entry.getKey().getConstraintValidatorFactory().releaseInstance( entry.getValue() );
169         }
170         constraintValidatorCache.clear();
171     }
172
173     public int numberOfCachedConstraintValidatorInstances() {
174         return constraintValidatorCache.size();
175     }
176
177     private static final class CacheKey {
178         // These members are not final for optimization purposes
179         private ConstraintAnnotationDescriptor<?> annotationDescriptor;
180         private Type validatedType;
181         private ConstraintValidatorFactory constraintValidatorFactory;
182         private HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext;
183         private int hashCode;
184
185         private CacheKey(ConstraintAnnotationDescriptor<?> annotationDescriptor, Type validatorType, ConstraintValidatorFactory constraintValidatorFactory,
186                 HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext) {
187             this.annotationDescriptor = annotationDescriptor;
188             this.validatedType = validatorType;
189             this.constraintValidatorFactory = constraintValidatorFactory;
190             this.constraintValidatorInitializationContext = constraintValidatorInitializationContext;
191             this.hashCode = createHashCode();
192         }
193
194         public ConstraintValidatorFactory getConstraintValidatorFactory() {
195             return constraintValidatorFactory;
196         }
197
198         public HibernateConstraintValidatorInitializationContext getConstraintValidatorInitializationContext() {
199             return constraintValidatorInitializationContext;
200         }
201
202         @Override
203         public boolean equals(Object o) {
204             if ( this == o ) {
205                 return true;
206             }
207             // no need to check for the type here considering it's only used in a typed map
208             if ( o == null ) {
209                 return false;
210             }
211
212             CacheKey other = (CacheKey) o;
213
214             if ( !annotationDescriptor.equals( other.annotationDescriptor ) ) {
215                 return false;
216             }
217             if ( !validatedType.equals( other.validatedType ) ) {
218                 return false;
219             }
220             if ( !constraintValidatorFactory.equals( other.constraintValidatorFactory ) ) {
221                 return false;
222             }
223             if ( !constraintValidatorInitializationContext.equals( other.constraintValidatorInitializationContext ) ) {
224                 return false;
225             }
226
227             return true;
228         }
229
230         @Override
231         public int hashCode() {
232             return hashCode;
233         }
234
235         private int createHashCode() {
236             int result = annotationDescriptor.hashCode();
237             result = 31 * result + validatedType.hashCode();
238             result = 31 * result + constraintValidatorFactory.hashCode();
239             result = 31 * result + constraintValidatorInitializationContext.hashCode();
240             return result;
241         }
242     }
243 }
244