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;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
10 import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
11
12 import java.io.BufferedInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.lang.invoke.MethodHandles;
16 import java.security.AccessController;
17 import java.security.PrivilegedAction;
18 import java.time.Duration;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.stream.Collectors;
26
27 import javax.validation.BootstrapConfiguration;
28 import javax.validation.ClockProvider;
29 import javax.validation.ConstraintValidatorFactory;
30 import javax.validation.MessageInterpolator;
31 import javax.validation.ParameterNameProvider;
32 import javax.validation.TraversableResolver;
33 import javax.validation.ValidationProviderResolver;
34 import javax.validation.ValidatorFactory;
35 import javax.validation.spi.BootstrapState;
36 import javax.validation.spi.ConfigurationState;
37 import javax.validation.spi.ValidationProvider;
38 import javax.validation.valueextraction.ValueExtractor;
39
40 import org.hibernate.validator.BaseHibernateValidatorConfiguration;
41 import org.hibernate.validator.cfg.ConstraintMapping;
42 import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
43 import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl;
44 import org.hibernate.validator.internal.engine.resolver.TraversableResolvers;
45 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
46 import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
47 import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy;
48 import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper;
49 import org.hibernate.validator.internal.util.CollectionHelper;
50 import org.hibernate.validator.internal.util.Contracts;
51 import org.hibernate.validator.internal.util.Version;
52 import org.hibernate.validator.internal.util.logging.Log;
53 import org.hibernate.validator.internal.util.logging.LoggerFactory;
54 import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader;
55 import org.hibernate.validator.internal.util.privilegedactions.GetInstancesFromServiceLoader;
56 import org.hibernate.validator.internal.util.privilegedactions.SetContextClassLoader;
57 import org.hibernate.validator.internal.util.stereotypes.Lazy;
58 import org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters;
59 import org.hibernate.validator.internal.xml.config.ValidationXmlParser;
60 import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
61 import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;
62 import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
63 import org.hibernate.validator.spi.messageinterpolation.LocaleResolver;
64 import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider;
65 import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy;
66 import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
67 import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;
68
69 /**
70  * Hibernate specific {@code Configuration} implementation.
71  *
72  * @author Emmanuel Bernard
73  * @author Hardy Ferentschik
74  * @author Gunnar Morling
75  * @author Kevin Pollet &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
76  * @author Chris Beckey &lt;cbeckey@paypal.com&gt;
77  * @author Guillaume Smet
78  */

79 public abstract class AbstractConfigurationImpl<T extends BaseHibernateValidatorConfiguration<T>>
80         implements BaseHibernateValidatorConfiguration<T>, ConfigurationState {
81
82     static {
83         Version.touch();
84     }
85
86     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
87
88
89     /**
90      * Built lazily so RBMI and its dependency on EL is only initialized if actually needed
91      */

92     @Lazy
93     private ResourceBundleLocator defaultResourceBundleLocator;
94     @Lazy
95     private MessageInterpolator defaultMessageInterpolator;
96     @Lazy
97     private MessageInterpolator messageInterpolator;
98
99     /**
100      * Created lazily to avoid fishing in the classpath if one has been defined.
101      */

102     @Lazy
103     private TraversableResolver defaultTraversableResolver;
104
105     private final ConstraintValidatorFactory defaultConstraintValidatorFactory;
106     private final ParameterNameProvider defaultParameterNameProvider;
107     private final ClockProvider defaultClockProvider;
108     private final PropertyNodeNameProvider defaultPropertyNodeNameProvider;
109
110     private ValidationProviderResolver providerResolver;
111     private final ValidationBootstrapParameters validationBootstrapParameters;
112     private boolean ignoreXmlConfiguration = false;
113     private final Set<InputStream> configurationStreams = newHashSet();
114     private BootstrapConfiguration bootstrapConfiguration;
115
116     private final Map<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> valueExtractorDescriptors = new HashMap<>();
117
118     // HV-specific options
119     private final Set<DefaultConstraintMapping> programmaticMappings = newHashSet();
120     private boolean failFast;
121     private ClassLoader externalClassLoader;
122     private final MethodValidationConfiguration.Builder methodValidationConfigurationBuilder = new MethodValidationConfiguration.Builder();
123     private boolean traversableResolverResultCacheEnabled = true;
124     private ScriptEvaluatorFactory scriptEvaluatorFactory;
125     private Duration temporalValidationTolerance;
126     private Object constraintValidatorPayload;
127     private GetterPropertySelectionStrategy getterPropertySelectionStrategy;
128     private Set<Locale> locales = Collections.emptySet();
129     private Locale defaultLocale = Locale.getDefault();
130     private LocaleResolver localeResolver;
131     private BeanMetaDataClassNormalizer beanMetaDataClassNormalizer;
132
133     protected AbstractConfigurationImpl(BootstrapState state) {
134         this();
135         if ( state.getValidationProviderResolver() == null ) {
136             this.providerResolver = state.getDefaultValidationProviderResolver();
137         }
138         else {
139             this.providerResolver = state.getValidationProviderResolver();
140         }
141     }
142
143     protected AbstractConfigurationImpl(ValidationProvider<?> provider) {
144         this();
145         if ( provider == null ) {
146             throw LOG.getInconsistentConfigurationException();
147         }
148         this.providerResolver = null;
149         validationBootstrapParameters.setProvider( provider );
150     }
151
152     private AbstractConfigurationImpl() {
153         this.validationBootstrapParameters = new ValidationBootstrapParameters();
154
155         this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
156         this.defaultParameterNameProvider = new DefaultParameterNameProvider();
157         this.defaultClockProvider = DefaultClockProvider.INSTANCE;
158         this.defaultPropertyNodeNameProvider = new DefaultPropertyNodeNameProvider();
159     }
160
161     @Override
162     public final T ignoreXmlConfiguration() {
163         ignoreXmlConfiguration = true;
164         return thisAsT();
165     }
166
167     @Override
168     public final T messageInterpolator(MessageInterpolator interpolator) {
169         if ( LOG.isDebugEnabled() ) {
170             if ( interpolator != null ) {
171                 LOG.debug( "Setting custom MessageInterpolator of type " + interpolator.getClass().getName() );
172             }
173         }
174         this.validationBootstrapParameters.setMessageInterpolator( interpolator );
175         return thisAsT();
176     }
177
178     @Override
179     public final T traversableResolver(TraversableResolver resolver) {
180         if ( LOG.isDebugEnabled() ) {
181             if ( resolver != null ) {
182                 LOG.debug( "Setting custom TraversableResolver of type " + resolver.getClass().getName() );
183             }
184         }
185         this.validationBootstrapParameters.setTraversableResolver( resolver );
186         return thisAsT();
187     }
188
189     @Override
190     public final T enableTraversableResolverResultCache(boolean enabled) {
191         this.traversableResolverResultCacheEnabled = enabled;
192         return thisAsT();
193     }
194
195     public final boolean isTraversableResolverResultCacheEnabled() {
196         return traversableResolverResultCacheEnabled;
197     }
198
199     @Override
200     public final T constraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
201         if ( LOG.isDebugEnabled() ) {
202             if ( constraintValidatorFactory != null ) {
203                 LOG.debug(
204                         "Setting custom ConstraintValidatorFactory of type " + constraintValidatorFactory.getClass()
205                                 .getName()
206                 );
207             }
208         }
209         this.validationBootstrapParameters.setConstraintValidatorFactory( constraintValidatorFactory );
210         return thisAsT();
211     }
212
213     @Override
214     public T parameterNameProvider(ParameterNameProvider parameterNameProvider) {
215         if ( LOG.isDebugEnabled() ) {
216             if ( parameterNameProvider != null ) {
217                 LOG.debug(
218                         "Setting custom ParameterNameProvider of type " + parameterNameProvider.getClass()
219                                 .getName()
220                 );
221             }
222         }
223         this.validationBootstrapParameters.setParameterNameProvider( parameterNameProvider );
224         return thisAsT();
225     }
226
227     @Override
228     public T clockProvider(ClockProvider clockProvider) {
229         if ( LOG.isDebugEnabled() ) {
230             if ( clockProvider != null ) {
231                 LOG.debug( "Setting custom ClockProvider of type " + clockProvider.getClass().getName() );
232             }
233         }
234         this.validationBootstrapParameters.setClockProvider( clockProvider );
235         return thisAsT();
236     }
237
238     @Override
239     public T propertyNodeNameProvider(PropertyNodeNameProvider propertyNodeNameProvider) {
240         if ( LOG.isDebugEnabled() ) {
241             if ( propertyNodeNameProvider != null ) {
242                 LOG.debug( "Setting custom PropertyNodeNameProvider of type " + propertyNodeNameProvider.getClass()
243                         .getName() );
244             }
245         }
246         this.validationBootstrapParameters.setPropertyNodeNameProvider( propertyNodeNameProvider );
247
248         return thisAsT();
249     }
250
251     @Override
252     public T localeResolver(LocaleResolver localeResolver) {
253         if ( LOG.isDebugEnabled() ) {
254             if ( localeResolver != null ) {
255                 LOG.debug( "Setting custom LocaleResolver of type " + localeResolver.getClass()
256                         .getName() );
257             }
258         }
259         this.localeResolver = localeResolver;
260
261         return thisAsT();
262     }
263
264     @Override
265     public T addValueExtractor(ValueExtractor<?> extractor) {
266         Contracts.assertNotNull( extractor, MESSAGES.parameterMustNotBeNull( "extractor" ) );
267
268         ValueExtractorDescriptor descriptor = new ValueExtractorDescriptor( extractor );
269         ValueExtractorDescriptor previous = valueExtractorDescriptors.put( descriptor.getKey(), descriptor );
270
271         if ( previous != null ) {
272             throw LOG.getValueExtractorForTypeAndTypeUseAlreadyPresentException( extractor, previous.getValueExtractor() );
273         }
274
275         if ( LOG.isDebugEnabled() ) {
276             LOG.debug( "Adding value extractor " + extractor );
277         }
278
279         return thisAsT();
280     }
281
282     @Override
283     public final T addMapping(InputStream stream) {
284         Contracts.assertNotNull( stream, MESSAGES.inputStreamCannotBeNull() );
285
286         validationBootstrapParameters.addMapping( stream.markSupported() ? stream : new BufferedInputStream( stream ) );
287         return thisAsT();
288     }
289
290     @Override
291     public final T failFast(boolean failFast) {
292         this.failFast = failFast;
293         return thisAsT();
294     }
295
296     @Override
297     public T allowOverridingMethodAlterParameterConstraint(boolean allow) {
298         this.methodValidationConfigurationBuilder.allowOverridingMethodAlterParameterConstraint( allow );
299         return thisAsT();
300     }
301
302     public boolean isAllowOverridingMethodAlterParameterConstraint() {
303         return this.methodValidationConfigurationBuilder.isAllowOverridingMethodAlterParameterConstraint();
304     }
305
306     @Override
307     public T allowMultipleCascadedValidationOnReturnValues(boolean allow) {
308         this.methodValidationConfigurationBuilder.allowMultipleCascadedValidationOnReturnValues( allow );
309         return thisAsT();
310     }
311
312     public boolean isAllowMultipleCascadedValidationOnReturnValues() {
313         return this.methodValidationConfigurationBuilder.isAllowMultipleCascadedValidationOnReturnValues();
314     }
315
316     @Override
317     public T allowParallelMethodsDefineParameterConstraints(boolean allow) {
318         this.methodValidationConfigurationBuilder.allowParallelMethodsDefineParameterConstraints( allow );
319         return thisAsT();
320     }
321
322     @Override
323     public T scriptEvaluatorFactory(ScriptEvaluatorFactory scriptEvaluatorFactory) {
324         Contracts.assertNotNull( scriptEvaluatorFactory, MESSAGES.parameterMustNotBeNull( "scriptEvaluatorFactory" ) );
325
326         this.scriptEvaluatorFactory = scriptEvaluatorFactory;
327         return thisAsT();
328     }
329
330     @Override
331     public T temporalValidationTolerance(Duration temporalValidationTolerance) {
332         Contracts.assertNotNull( temporalValidationTolerance, MESSAGES.parameterMustNotBeNull( "temporalValidationTolerance" ) );
333
334         this.temporalValidationTolerance = temporalValidationTolerance.abs();
335         return thisAsT();
336     }
337
338     @Override
339     public T constraintValidatorPayload(Object constraintValidatorPayload) {
340         Contracts.assertNotNull( constraintValidatorPayload, MESSAGES.parameterMustNotBeNull( "constraintValidatorPayload" ) );
341
342         this.constraintValidatorPayload = constraintValidatorPayload;
343         return thisAsT();
344     }
345
346     @Override
347     public T getterPropertySelectionStrategy(GetterPropertySelectionStrategy getterPropertySelectionStrategy) {
348         Contracts.assertNotNull( getterPropertySelectionStrategy, MESSAGES.parameterMustNotBeNull( "getterPropertySelectionStrategy" ) );
349
350         this.getterPropertySelectionStrategy = getterPropertySelectionStrategy;
351         return thisAsT();
352     }
353
354     @Override
355     public T locales(Set<Locale> locales) {
356         Contracts.assertNotNull( defaultLocale, MESSAGES.parameterMustNotBeNull( "locales" ) );
357
358         this.locales = locales;
359         return thisAsT();
360     }
361
362     @Override
363     public T defaultLocale(Locale defaultLocale) {
364         Contracts.assertNotNull( defaultLocale, MESSAGES.parameterMustNotBeNull( "defaultLocale" ) );
365
366         this.defaultLocale = defaultLocale;
367         return thisAsT();
368     }
369
370     public boolean isAllowParallelMethodsDefineParameterConstraints() {
371         return this.methodValidationConfigurationBuilder.isAllowParallelMethodsDefineParameterConstraints();
372     }
373
374     public MethodValidationConfiguration getMethodValidationConfiguration() {
375         return this.methodValidationConfigurationBuilder.build();
376     }
377
378     @Override
379     public final DefaultConstraintMapping createConstraintMapping() {
380         GetterPropertySelectionStrategy getterPropertySelectionStrategyToUse = null;
381         if ( getterPropertySelectionStrategy == null ) {
382             getterPropertySelectionStrategyToUse = new DefaultGetterPropertySelectionStrategy();
383         }
384         else {
385             getterPropertySelectionStrategyToUse = getterPropertySelectionStrategy;
386         }
387
388         return new DefaultConstraintMapping( new JavaBeanHelper( getterPropertySelectionStrategyToUse, defaultPropertyNodeNameProvider ) );
389     }
390
391     @Override
392     public final T addMapping(ConstraintMapping mapping) {
393         Contracts.assertNotNull( mapping, MESSAGES.parameterMustNotBeNull( "mapping" ) );
394
395         this.programmaticMappings.add( (DefaultConstraintMapping) mapping );
396         return thisAsT();
397     }
398
399     @Override
400     public final T addProperty(String name, String value) {
401         if ( value != null ) {
402             validationBootstrapParameters.addConfigProperty( name, value );
403         }
404         return thisAsT();
405     }
406
407     @Override
408     public T externalClassLoader(ClassLoader externalClassLoader) {
409         Contracts.assertNotNull( externalClassLoader, MESSAGES.parameterMustNotBeNull( "externalClassLoader" ) );
410         this.externalClassLoader = externalClassLoader;
411
412         // we need to reset the messageInterpolator field as it might vary depending on the class loader
413         this.messageInterpolator = null;
414
415         return thisAsT();
416     }
417
418     @Override
419     public final ValidatorFactory buildValidatorFactory() {
420         loadValueExtractorsFromServiceLoader();
421         parseValidationXml();
422
423         for ( ValueExtractorDescriptor valueExtractorDescriptor : valueExtractorDescriptors.values() ) {
424             validationBootstrapParameters.addValueExtractorDescriptor( valueExtractorDescriptor );
425         }
426
427         ValidatorFactory factory = null;
428         try {
429             if ( isSpecificProvider() ) {
430                 factory = validationBootstrapParameters.getProvider().buildValidatorFactory( this );
431             }
432             else {
433                 final Class<? extends ValidationProvider<?>> providerClass = validationBootstrapParameters.getProviderClass();
434                 if ( providerClass != null ) {
435                     for ( ValidationProvider<?> provider : providerResolver.getValidationProviders() ) {
436                         if ( providerClass.isAssignableFrom( provider.getClass() ) ) {
437                             factory = provider.buildValidatorFactory( this );
438                             break;
439                         }
440                     }
441                     if ( factory == null ) {
442                         throw LOG.getUnableToFindProviderException( providerClass );
443                     }
444                 }
445                 else {
446                     List<ValidationProvider<?>> providers = providerResolver.getValidationProviders();
447                     assert providers.size() != 0; // I run therefore I am
448                     factory = providers.get( 0 ).buildValidatorFactory( this );
449                 }
450             }
451         }
452         finally {
453             // close all input streams opened by this configuration
454             for ( InputStream in : configurationStreams ) {
455                 try {
456                     in.close();
457                 }
458                 catch (IOException io) {
459                     LOG.unableToCloseInputStream();
460                 }
461             }
462         }
463
464         return factory;
465     }
466
467     @Override
468     public final boolean isIgnoreXmlConfiguration() {
469         return ignoreXmlConfiguration;
470     }
471
472     @Override
473     public final MessageInterpolator getMessageInterpolator() {
474         // apply explicitly given MI, otherwise use default one
475         MessageInterpolator selectedInterpolator = validationBootstrapParameters.getMessageInterpolator();
476         if ( selectedInterpolator != null ) {
477             return selectedInterpolator;
478         }
479         if ( messageInterpolator == null ) {
480             messageInterpolator = getDefaultMessageInterpolatorConfiguredWithClassLoader();
481         }
482
483         return messageInterpolator;
484     }
485
486     @Override
487     public final Set<InputStream> getMappingStreams() {
488         return validationBootstrapParameters.getMappings();
489     }
490
491     public final boolean getFailFast() {
492         return failFast;
493     }
494
495     @Override
496     public final ConstraintValidatorFactory getConstraintValidatorFactory() {
497         return validationBootstrapParameters.getConstraintValidatorFactory();
498     }
499
500     @Override
501     public final TraversableResolver getTraversableResolver() {
502         return validationBootstrapParameters.getTraversableResolver();
503     }
504
505     @Override
506     public BootstrapConfiguration getBootstrapConfiguration() {
507         if ( bootstrapConfiguration == null ) {
508             bootstrapConfiguration = new ValidationXmlParser( externalClassLoader ).parseValidationXml();
509         }
510         return bootstrapConfiguration;
511     }
512
513     @Override
514     public ParameterNameProvider getParameterNameProvider() {
515         return validationBootstrapParameters.getParameterNameProvider();
516     }
517
518     @Override
519     public ClockProvider getClockProvider() {
520         return validationBootstrapParameters.getClockProvider();
521     }
522
523     public PropertyNodeNameProvider getPropertyNodeNameProvider() {
524         return validationBootstrapParameters.getPropertyNodeNameProvider();
525     }
526
527     public LocaleResolver getLocaleResolver() {
528         return localeResolver;
529     }
530
531     public ScriptEvaluatorFactory getScriptEvaluatorFactory() {
532         return scriptEvaluatorFactory;
533     }
534
535     public Duration getTemporalValidationTolerance() {
536         return temporalValidationTolerance;
537     }
538
539     public Object getConstraintValidatorPayload() {
540         return constraintValidatorPayload;
541     }
542
543     public GetterPropertySelectionStrategy getGetterPropertySelectionStrategy() {
544         return getterPropertySelectionStrategy;
545     }
546
547     @Override
548     public Set<ValueExtractor<?>> getValueExtractors() {
549         return validationBootstrapParameters.getValueExtractorDescriptors()
550                 .values()
551                 .stream()
552                 .map( ValueExtractorDescriptor::getValueExtractor )
553                 .collect( Collectors.toSet() );
554     }
555
556     @Override
557     public final Map<String, String> getProperties() {
558         return validationBootstrapParameters.getConfigProperties();
559     }
560
561     public ClassLoader getExternalClassLoader() {
562         return externalClassLoader;
563     }
564
565     @Override
566     public final MessageInterpolator getDefaultMessageInterpolator() {
567         if ( defaultMessageInterpolator == null ) {
568             defaultMessageInterpolator = new ResourceBundleMessageInterpolator( getDefaultResourceBundleLocator(), getAllSupportedLocales(),
569                     defaultLocale, ValidatorFactoryConfigurationHelper.determineLocaleResolver( thisthis.getProperties(), externalClassLoader ),
570                     preloadResourceBundles() );
571         }
572
573         return defaultMessageInterpolator;
574     }
575
576     @Override
577     public final TraversableResolver getDefaultTraversableResolver() {
578         if ( defaultTraversableResolver == null ) {
579             defaultTraversableResolver = TraversableResolvers.getDefault();
580         }
581         return defaultTraversableResolver;
582     }
583
584     @Override
585     public final ConstraintValidatorFactory getDefaultConstraintValidatorFactory() {
586         return defaultConstraintValidatorFactory;
587     }
588
589     @Override
590     public final ResourceBundleLocator getDefaultResourceBundleLocator() {
591         if ( defaultResourceBundleLocator == null ) {
592             defaultResourceBundleLocator = new PlatformResourceBundleLocator(
593                     ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, preloadResourceBundles() ? getAllSupportedLocales() : Collections.emptySet() );
594         }
595
596         return defaultResourceBundleLocator;
597     }
598
599     @Override
600     public ParameterNameProvider getDefaultParameterNameProvider() {
601         return defaultParameterNameProvider;
602     }
603
604     @Override
605     public ClockProvider getDefaultClockProvider() {
606         return defaultClockProvider;
607     }
608
609     @Override
610     public Set<ValueExtractor<?>> getDefaultValueExtractors() {
611         return ValueExtractorManager.getDefaultValueExtractors();
612     }
613
614     @Override
615     public T beanMetaDataClassNormalizer(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer) {
616         if ( LOG.isDebugEnabled() ) {
617             if ( beanMetaDataClassNormalizer != null ) {
618                 LOG.debug( "Setting custom BeanMetaDataClassNormalizer of type " + beanMetaDataClassNormalizer.getClass()
619                         .getName() );
620             }
621         }
622         this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer;
623         return thisAsT();
624     }
625
626     public BeanMetaDataClassNormalizer getBeanMetaDataClassNormalizer() {
627         return beanMetaDataClassNormalizer;
628     }
629
630
631     public final Set<DefaultConstraintMapping> getProgrammaticMappings() {
632         return programmaticMappings;
633     }
634
635     private boolean isSpecificProvider() {
636         return validationBootstrapParameters.getProvider() != null;
637     }
638
639     /**
640      * Tries to check whether a validation.xml file exists and parses it
641      */

642     private void parseValidationXml() {
643         if ( ignoreXmlConfiguration ) {
644             LOG.ignoringXmlConfiguration();
645
646             if ( validationBootstrapParameters.getTraversableResolver() == null ) {
647                 validationBootstrapParameters.setTraversableResolver( getDefaultTraversableResolver() );
648             }
649             if ( validationBootstrapParameters.getConstraintValidatorFactory() == null ) {
650                 validationBootstrapParameters.setConstraintValidatorFactory( defaultConstraintValidatorFactory );
651             }
652             if ( validationBootstrapParameters.getParameterNameProvider() == null ) {
653                 validationBootstrapParameters.setParameterNameProvider( defaultParameterNameProvider );
654             }
655             if ( validationBootstrapParameters.getClockProvider() == null ) {
656                 validationBootstrapParameters.setClockProvider( defaultClockProvider );
657             }
658             if ( validationBootstrapParameters.getPropertyNodeNameProvider() == null ) {
659                 validationBootstrapParameters.setPropertyNodeNameProvider( defaultPropertyNodeNameProvider );
660             }
661         }
662         else {
663             ValidationBootstrapParameters xmlParameters = new ValidationBootstrapParameters(
664                     getBootstrapConfiguration(), externalClassLoader
665             );
666             applyXmlSettings( xmlParameters );
667         }
668     }
669
670     @SuppressWarnings("rawtypes")
671     private void loadValueExtractorsFromServiceLoader() {
672         List<ValueExtractor> valueExtractors = run( GetInstancesFromServiceLoader.action(
673                 externalClassLoader != null ? externalClassLoader : run( GetClassLoader.fromContext() ),
674                 ValueExtractor.class
675         ) );
676
677         for ( ValueExtractor<?> valueExtractor : valueExtractors ) {
678             validationBootstrapParameters.addValueExtractorDescriptor( new ValueExtractorDescriptor( valueExtractor ) );
679         }
680     }
681
682     private void applyXmlSettings(ValidationBootstrapParameters xmlParameters) {
683         validationBootstrapParameters.setProviderClass( xmlParameters.getProviderClass() );
684
685         if ( validationBootstrapParameters.getMessageInterpolator() == null ) {
686             if ( xmlParameters.getMessageInterpolator() != null ) {
687                 validationBootstrapParameters.setMessageInterpolator( xmlParameters.getMessageInterpolator() );
688             }
689         }
690
691         if ( validationBootstrapParameters.getTraversableResolver() == null ) {
692             if ( xmlParameters.getTraversableResolver() != null ) {
693                 validationBootstrapParameters.setTraversableResolver( xmlParameters.getTraversableResolver() );
694             }
695             else {
696                 validationBootstrapParameters.setTraversableResolver( getDefaultTraversableResolver() );
697             }
698         }
699
700         if ( validationBootstrapParameters.getConstraintValidatorFactory() == null ) {
701             if ( xmlParameters.getConstraintValidatorFactory() != null ) {
702                 validationBootstrapParameters.setConstraintValidatorFactory(
703                         xmlParameters.getConstraintValidatorFactory()
704                 );
705             }
706             else {
707                 validationBootstrapParameters.setConstraintValidatorFactory( defaultConstraintValidatorFactory );
708             }
709         }
710
711         if ( validationBootstrapParameters.getParameterNameProvider() == null ) {
712             if ( xmlParameters.getParameterNameProvider() != null ) {
713                 validationBootstrapParameters.setParameterNameProvider( xmlParameters.getParameterNameProvider() );
714             }
715             else {
716                 validationBootstrapParameters.setParameterNameProvider( defaultParameterNameProvider );
717             }
718         }
719
720         if ( validationBootstrapParameters.getClockProvider() == null ) {
721             if ( xmlParameters.getClockProvider() != null ) {
722                 validationBootstrapParameters.setClockProvider( xmlParameters.getClockProvider() );
723             }
724             else {
725                 validationBootstrapParameters.setClockProvider( defaultClockProvider );
726             }
727         }
728
729         if ( validationBootstrapParameters.getPropertyNodeNameProvider() == null ) {
730             if ( xmlParameters.getPropertyNodeNameProvider() != null ) {
731                 validationBootstrapParameters.setPropertyNodeNameProvider(
732                         xmlParameters.getPropertyNodeNameProvider() );
733             }
734             else {
735                 validationBootstrapParameters.setPropertyNodeNameProvider( defaultPropertyNodeNameProvider );
736             }
737         }
738
739         for ( ValueExtractorDescriptor valueExtractorDescriptor : xmlParameters.getValueExtractorDescriptors().values() ) {
740             validationBootstrapParameters.addValueExtractorDescriptor( valueExtractorDescriptor );
741         }
742
743         validationBootstrapParameters.addAllMappings( xmlParameters.getMappings() );
744         configurationStreams.addAll( xmlParameters.getMappings() );
745
746         for ( Map.Entry<String, String> entry : xmlParameters.getConfigProperties().entrySet() ) {
747             if ( validationBootstrapParameters.getConfigProperties().get( entry.getKey() ) == null ) {
748                 validationBootstrapParameters.addConfigProperty( entry.getKey(), entry.getValue() );
749             }
750         }
751     }
752
753     /**
754      * Returns the default message interpolator, configured with the given user class loader, if present.
755      */

756     private MessageInterpolator getDefaultMessageInterpolatorConfiguredWithClassLoader() {
757         if ( externalClassLoader != null ) {
758             PlatformResourceBundleLocator userResourceBundleLocator = new PlatformResourceBundleLocator(
759                     ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES,
760                     preloadResourceBundles() ? getAllSupportedLocales() : Collections.emptySet(),
761                     externalClassLoader
762             );
763             PlatformResourceBundleLocator contributorResourceBundleLocator = new PlatformResourceBundleLocator(
764                     ResourceBundleMessageInterpolator.CONTRIBUTOR_VALIDATION_MESSAGES,
765                     preloadResourceBundles() ? getAllSupportedLocales() : Collections.emptySet(),
766                     externalClassLoader,
767                     true
768             );
769
770             // Within RBMI, the expression factory implementation is loaded from the TCCL; thus we set the TCCL to the
771             // given external class loader for this call
772             final ClassLoader originalContextClassLoader = run( GetClassLoader.fromContext() );
773
774             try {
775                 run( SetContextClassLoader.action( externalClassLoader ) );
776                 return new ResourceBundleMessageInterpolator(
777                         userResourceBundleLocator,
778                         contributorResourceBundleLocator,
779                         getAllSupportedLocales(),
780                         defaultLocale,
781                         ValidatorFactoryConfigurationHelper.determineLocaleResolver( thisthis.getProperties(), externalClassLoader ),
782                         preloadResourceBundles()
783                 );
784             }
785             finally {
786                 run( SetContextClassLoader.action( originalContextClassLoader ) );
787             }
788         }
789         else {
790             return getDefaultMessageInterpolator();
791         }
792     }
793
794     private Set<Locale> getAllSupportedLocales() {
795         if ( locales.isEmpty() ) {
796             return Collections.singleton( defaultLocale );
797         }
798         if ( locales.contains( defaultLocale ) ) {
799             return locales;
800         }
801
802         Set<Locale> allLocales = CollectionHelper.newHashSet( locales.size() + 1 );
803         allLocales.addAll( locales );
804         allLocales.add( defaultLocale );
805         return allLocales;
806     }
807
808     protected abstract boolean preloadResourceBundles();
809
810     @SuppressWarnings("unchecked")
811     protected T thisAsT() {
812         return (T) this;
813     }
814
815     /**
816      * Runs the given privileged action, using a privileged block if required.
817      * <p>
818      * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
819      * privileged actions within HV's protection domain.
820      */

821     private static <T> T run(PrivilegedAction<T> action) {
822         return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
823     }
824 }
825