001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.validation.beanvalidation;
018
019import java.io.IOException;
020import java.lang.reflect.Constructor;
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Proxy;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.Properties;
029import javax.validation.Configuration;
030import javax.validation.ConstraintValidatorFactory;
031import javax.validation.MessageInterpolator;
032import javax.validation.TraversableResolver;
033import javax.validation.Validation;
034import javax.validation.ValidationException;
035import javax.validation.ValidationProviderResolver;
036import javax.validation.Validator;
037import javax.validation.ValidatorContext;
038import javax.validation.ValidatorFactory;
039import javax.validation.bootstrap.GenericBootstrap;
040import javax.validation.bootstrap.ProviderSpecificBootstrap;
041
042import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
043
044import org.springframework.beans.factory.DisposableBean;
045import org.springframework.beans.factory.InitializingBean;
046import org.springframework.context.ApplicationContext;
047import org.springframework.context.ApplicationContextAware;
048import org.springframework.context.MessageSource;
049import org.springframework.core.DefaultParameterNameDiscoverer;
050import org.springframework.core.ParameterNameDiscoverer;
051import org.springframework.core.io.Resource;
052import org.springframework.util.Assert;
053import org.springframework.util.ClassUtils;
054import org.springframework.util.CollectionUtils;
055import org.springframework.util.ReflectionUtils;
056
057/**
058 * This is the central class for {@code javax.validation} (JSR-303) setup in a Spring
059 * application context: It bootstraps a {@code javax.validation.ValidationFactory} and
060 * exposes it through the Spring {@link org.springframework.validation.Validator} interface
061 * as well as through the JSR-303 {@link javax.validation.Validator} interface and the
062 * {@link javax.validation.ValidatorFactory} interface itself.
063 *
064 * <p>When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces,
065 * you'll be talking to the default Validator of the underlying ValidatorFactory. This is very
066 * convenient in that you don't have to perform yet another call on the factory, assuming that
067 * you will almost always use the default Validator anyway. This can also be injected directly
068 * into any target dependency of type {@link org.springframework.validation.Validator}!
069 *
070 * <p><b>As of Spring 4.0, this class supports Bean Validation 1.0 and 1.1, with special support
071 * for Hibernate Validator 4.3 and 5.x</b> (see {@link #setValidationMessageSource}).
072 *
073 * <p>Note that Bean Validation 1.1's {@code #forExecutables} method isn't supported: We do not
074 * expect that method to be called by application code; consider {@link MethodValidationInterceptor}
075 * instead. If you really need programmatic {@code #forExecutables} access, inject this class as
076 * a {@link ValidatorFactory} and call {@link #getValidator()} on it, then {@code #forExecutables}
077 * on the returned native {@link Validator} reference instead of directly on this class.
078 * Alternatively, call {@code #unwrap(Validator.class)} which will also provide the native object.
079 *
080 * <p>This class is also being used by Spring's MVC configuration namespace, in case of the
081 * {@code javax.validation} API being present but no explicit Validator having been configured.
082 *
083 * @author Juergen Hoeller
084 * @since 3.0
085 * @see javax.validation.ValidatorFactory
086 * @see javax.validation.Validator
087 * @see javax.validation.Validation#buildDefaultValidatorFactory()
088 * @see javax.validation.ValidatorFactory#getValidator()
089 */
090public class LocalValidatorFactoryBean extends SpringValidatorAdapter
091                implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
092
093        // Bean Validation 1.1 close() method available?
094        private static final Method closeMethod = ClassUtils.getMethodIfAvailable(ValidatorFactory.class, "close");
095
096
097        @SuppressWarnings("rawtypes")
098        private Class providerClass;
099
100        private ValidationProviderResolver validationProviderResolver;
101
102        private MessageInterpolator messageInterpolator;
103
104        private TraversableResolver traversableResolver;
105
106        private ConstraintValidatorFactory constraintValidatorFactory;
107
108        private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
109
110        private Resource[] mappingLocations;
111
112        private final Map<String, String> validationPropertyMap = new HashMap<String, String>();
113
114        private ApplicationContext applicationContext;
115
116        private ValidatorFactory validatorFactory;
117
118
119        /**
120         * Specify the desired provider class, if any.
121         * <p>If not specified, JSR-303's default search mechanism will be used.
122         * @see javax.validation.Validation#byProvider(Class)
123         * @see javax.validation.Validation#byDefaultProvider()
124         */
125        @SuppressWarnings("rawtypes")
126        public void setProviderClass(Class providerClass) {
127                this.providerClass = providerClass;
128        }
129
130        /**
131         * Specify a JSR-303 {@link ValidationProviderResolver} for bootstrapping the
132         * provider of choice, as an alternative to {@code META-INF} driven resolution.
133         * @since 4.3
134         */
135        public void setValidationProviderResolver(ValidationProviderResolver validationProviderResolver) {
136                this.validationProviderResolver = validationProviderResolver;
137        }
138
139        /**
140         * Specify a custom MessageInterpolator to use for this ValidatorFactory
141         * and its exposed default Validator.
142         */
143        public void setMessageInterpolator(MessageInterpolator messageInterpolator) {
144                this.messageInterpolator = messageInterpolator;
145        }
146
147        /**
148         * Specify a custom Spring MessageSource for resolving validation messages,
149         * instead of relying on JSR-303's default "ValidationMessages.properties" bundle
150         * in the classpath. This may refer to a Spring context's shared "messageSource" bean,
151         * or to some special MessageSource setup for validation purposes only.
152         * <p><b>NOTE:</b> This feature requires Hibernate Validator 4.3 or higher on the classpath.
153         * You may nevertheless use a different validation provider but Hibernate Validator's
154         * {@link ResourceBundleMessageInterpolator} class must be accessible during configuration.
155         * <p>Specify either this property or {@link #setMessageInterpolator "messageInterpolator"},
156         * not both. If you would like to build a custom MessageInterpolator, consider deriving from
157         * Hibernate Validator's {@link ResourceBundleMessageInterpolator} and passing in a
158         * Spring-based {@code ResourceBundleLocator} when constructing your interpolator.
159         * <p>In order for Hibernate's default validation messages to be resolved still, your
160         * {@link MessageSource} must be configured for optional resolution (usually the default).
161         * In particular, the {@code MessageSource} instance specified here should not apply
162         * {@link org.springframework.context.support.AbstractMessageSource#setUseCodeAsDefaultMessage
163         * "useCodeAsDefaultMessage"} behavior. Please double-check your setup accordingly.
164         * @see ResourceBundleMessageInterpolator
165         */
166        public void setValidationMessageSource(MessageSource messageSource) {
167                this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource);
168        }
169
170        /**
171         * Specify a custom TraversableResolver to use for this ValidatorFactory
172         * and its exposed default Validator.
173         */
174        public void setTraversableResolver(TraversableResolver traversableResolver) {
175                this.traversableResolver = traversableResolver;
176        }
177
178        /**
179         * Specify a custom ConstraintValidatorFactory to use for this ValidatorFactory.
180         * <p>Default is a {@link SpringConstraintValidatorFactory}, delegating to the
181         * containing ApplicationContext for creating autowired ConstraintValidator instances.
182         */
183        public void setConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
184                this.constraintValidatorFactory = constraintValidatorFactory;
185        }
186
187        /**
188         * Set the ParameterNameDiscoverer to use for resolving method and constructor
189         * parameter names if needed for message interpolation.
190         * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
191         */
192        public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
193                this.parameterNameDiscoverer = parameterNameDiscoverer;
194        }
195
196        /**
197         * Specify resource locations to load XML constraint mapping files from, if any.
198         */
199        public void setMappingLocations(Resource... mappingLocations) {
200                this.mappingLocations = mappingLocations;
201        }
202
203        /**
204         * Specify bean validation properties to be passed to the validation provider.
205         * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
206         * or a "props" element in XML bean definitions.
207         * @see javax.validation.Configuration#addProperty(String, String)
208         */
209        public void setValidationProperties(Properties jpaProperties) {
210                CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.validationPropertyMap);
211        }
212
213        /**
214         * Specify bean validation properties to be passed to the validation provider as a Map.
215         * <p>Can be populated with a "map" or "props" element in XML bean definitions.
216         * @see javax.validation.Configuration#addProperty(String, String)
217         */
218        public void setValidationPropertyMap(Map<String, String> validationProperties) {
219                if (validationProperties != null) {
220                        this.validationPropertyMap.putAll(validationProperties);
221                }
222        }
223
224        /**
225         * Allow Map access to the bean validation properties to be passed to the validation provider,
226         * with the option to add or override specific entries.
227         * <p>Useful for specifying entries directly, for example via "validationPropertyMap[myKey]".
228         */
229        public Map<String, String> getValidationPropertyMap() {
230                return this.validationPropertyMap;
231        }
232
233        @Override
234        public void setApplicationContext(ApplicationContext applicationContext) {
235                this.applicationContext = applicationContext;
236        }
237
238
239        @Override
240        @SuppressWarnings({"rawtypes", "unchecked"})
241        public void afterPropertiesSet() {
242                Configuration<?> configuration;
243                if (this.providerClass != null) {
244                        ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass);
245                        if (this.validationProviderResolver != null) {
246                                bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
247                        }
248                        configuration = bootstrap.configure();
249                }
250                else {
251                        GenericBootstrap bootstrap = Validation.byDefaultProvider();
252                        if (this.validationProviderResolver != null) {
253                                bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
254                        }
255                        configuration = bootstrap.configure();
256                }
257
258                // Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
259                if (this.applicationContext != null) {
260                        try {
261                                Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class);
262                                ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
263                        }
264                        catch (NoSuchMethodException ex) {
265                                // Ignore - no Hibernate Validator 5.2+ or similar provider
266                        }
267                }
268
269                MessageInterpolator targetInterpolator = this.messageInterpolator;
270                if (targetInterpolator == null) {
271                        targetInterpolator = configuration.getDefaultMessageInterpolator();
272                }
273                configuration.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator));
274
275                if (this.traversableResolver != null) {
276                        configuration.traversableResolver(this.traversableResolver);
277                }
278
279                ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
280                if (targetConstraintValidatorFactory == null && this.applicationContext != null) {
281                        targetConstraintValidatorFactory =
282                                        new SpringConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory());
283                }
284                if (targetConstraintValidatorFactory != null) {
285                        configuration.constraintValidatorFactory(targetConstraintValidatorFactory);
286                }
287
288                if (this.parameterNameDiscoverer != null) {
289                        configureParameterNameProviderIfPossible(configuration);
290                }
291
292                if (this.mappingLocations != null) {
293                        for (Resource location : this.mappingLocations) {
294                                try {
295                                        configuration.addMapping(location.getInputStream());
296                                }
297                                catch (IOException ex) {
298                                        throw new IllegalStateException("Cannot read mapping resource: " + location);
299                                }
300                        }
301                }
302
303                for (Map.Entry<String, String> entry : this.validationPropertyMap.entrySet()) {
304                        configuration.addProperty(entry.getKey(), entry.getValue());
305                }
306
307                // Allow for custom post-processing before we actually build the ValidatorFactory.
308                postProcessConfiguration(configuration);
309
310                this.validatorFactory = configuration.buildValidatorFactory();
311                setTargetValidator(this.validatorFactory.getValidator());
312        }
313
314        private void configureParameterNameProviderIfPossible(Configuration<?> configuration) {
315                try {
316                        Class<?> parameterNameProviderClass =
317                                        ClassUtils.forName("javax.validation.ParameterNameProvider", getClass().getClassLoader());
318                        Method parameterNameProviderMethod =
319                                        Configuration.class.getMethod("parameterNameProvider", parameterNameProviderClass);
320                        final Object defaultProvider = ReflectionUtils.invokeMethod(
321                                        Configuration.class.getMethod("getDefaultParameterNameProvider"), configuration);
322                        final ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
323                        Object parameterNameProvider = Proxy.newProxyInstance(getClass().getClassLoader(),
324                                        new Class<?>[] {parameterNameProviderClass}, new InvocationHandler() {
325                                @Override
326                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
327                                        if (method.getName().equals("getParameterNames")) {
328                                                String[] result = null;
329                                                if (args[0] instanceof Constructor) {
330                                                        result = discoverer.getParameterNames((Constructor<?>) args[0]);
331                                                }
332                                                else if (args[0] instanceof Method) {
333                                                        result = discoverer.getParameterNames((Method) args[0]);
334                                                }
335                                                if (result != null) {
336                                                        return Arrays.asList(result);
337                                                }
338                                                else {
339                                                        try {
340                                                                return method.invoke(defaultProvider, args);
341                                                        }
342                                                        catch (InvocationTargetException ex) {
343                                                                throw ex.getTargetException();
344                                                        }
345                                                }
346                                        }
347                                        else {
348                                                // toString, equals, hashCode
349                                                try {
350                                                        return method.invoke(this, args);
351                                                }
352                                                catch (InvocationTargetException ex) {
353                                                        throw ex.getTargetException();
354                                                }
355                                        }
356                                }
357                        });
358                        ReflectionUtils.invokeMethod(parameterNameProviderMethod, configuration, parameterNameProvider);
359
360                }
361                catch (Throwable ex) {
362                        // Bean Validation 1.1 API not available - simply not applying the ParameterNameDiscoverer
363                }
364        }
365
366        /**
367         * Post-process the given Bean Validation configuration,
368         * adding to or overriding any of its settings.
369         * <p>Invoked right before building the {@link ValidatorFactory}.
370         * @param configuration the Configuration object, pre-populated with
371         * settings driven by LocalValidatorFactoryBean's properties
372         */
373        protected void postProcessConfiguration(Configuration<?> configuration) {
374        }
375
376
377        @Override
378        public Validator getValidator() {
379                Assert.notNull(this.validatorFactory, "No target ValidatorFactory set");
380                return this.validatorFactory.getValidator();
381        }
382
383        @Override
384        public ValidatorContext usingContext() {
385                Assert.notNull(this.validatorFactory, "No target ValidatorFactory set");
386                return this.validatorFactory.usingContext();
387        }
388
389        @Override
390        public MessageInterpolator getMessageInterpolator() {
391                Assert.notNull(this.validatorFactory, "No target ValidatorFactory set");
392                return this.validatorFactory.getMessageInterpolator();
393        }
394
395        @Override
396        public TraversableResolver getTraversableResolver() {
397                Assert.notNull(this.validatorFactory, "No target ValidatorFactory set");
398                return this.validatorFactory.getTraversableResolver();
399        }
400
401        @Override
402        public ConstraintValidatorFactory getConstraintValidatorFactory() {
403                Assert.notNull(this.validatorFactory, "No target ValidatorFactory set");
404                return this.validatorFactory.getConstraintValidatorFactory();
405        }
406
407        @Override
408        @SuppressWarnings("unchecked")
409        public <T> T unwrap(Class<T> type) {
410                if (type == null || !ValidatorFactory.class.isAssignableFrom(type)) {
411                        try {
412                                return super.unwrap(type);
413                        }
414                        catch (ValidationException ex) {
415                                // ignore - we'll try ValidatorFactory unwrapping next
416                        }
417                }
418                try {
419                        return this.validatorFactory.unwrap(type);
420                }
421                catch (ValidationException ex) {
422                        // ignore if just being asked for ValidatorFactory
423                        if (ValidatorFactory.class == type) {
424                                return (T) this.validatorFactory;
425                        }
426                        throw ex;
427                }
428        }
429
430
431        public void close() {
432                if (closeMethod != null && this.validatorFactory != null) {
433                        ReflectionUtils.invokeMethod(closeMethod, this.validatorFactory);
434                }
435        }
436
437        @Override
438        public void destroy() {
439                close();
440        }
441
442
443        /**
444         * Inner class to avoid a hard-coded Hibernate Validator dependency.
445         */
446        private static class HibernateValidatorDelegate {
447
448                public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) {
449                        return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));
450                }
451        }
452
453}