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.Method; 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Properties; 027 028import javax.validation.Configuration; 029import javax.validation.ConstraintValidatorFactory; 030import javax.validation.MessageInterpolator; 031import javax.validation.ParameterNameProvider; 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.lang.Nullable; 053import org.springframework.util.Assert; 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 5.0, this class requires Bean Validation 1.1+, with special support 071 * for Hibernate Validator 5.x</b> (see {@link #setValidationMessageSource}). 072 * This class is also runtime-compatible with Bean Validation 2.0 and Hibernate Validator 6.0, 073 * with one special note: If you'd like to call BV 2.0's {@code getClockProvider()} method, 074 * obtain the native {@code ValidatorFactory} through {@code #unwrap(ValidatorFactory.class)} 075 * and call the {@code getClockProvider()} method on the returned native reference there. 076 * 077 * <p>This class is also being used by Spring's MVC configuration namespace, in case of the 078 * {@code javax.validation} API being present but no explicit Validator having been configured. 079 * 080 * @author Juergen Hoeller 081 * @since 3.0 082 * @see javax.validation.ValidatorFactory 083 * @see javax.validation.Validator 084 * @see javax.validation.Validation#buildDefaultValidatorFactory() 085 * @see javax.validation.ValidatorFactory#getValidator() 086 */ 087public class LocalValidatorFactoryBean extends SpringValidatorAdapter 088 implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean { 089 090 @SuppressWarnings("rawtypes") 091 @Nullable 092 private Class providerClass; 093 094 @Nullable 095 private ValidationProviderResolver validationProviderResolver; 096 097 @Nullable 098 private MessageInterpolator messageInterpolator; 099 100 @Nullable 101 private TraversableResolver traversableResolver; 102 103 @Nullable 104 private ConstraintValidatorFactory constraintValidatorFactory; 105 106 @Nullable 107 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 108 109 @Nullable 110 private Resource[] mappingLocations; 111 112 private final Map<String, String> validationPropertyMap = new HashMap<>(); 113 114 @Nullable 115 private ApplicationContext applicationContext; 116 117 @Nullable 118 private ValidatorFactory validatorFactory; 119 120 121 /** 122 * Specify the desired provider class, if any. 123 * <p>If not specified, JSR-303's default search mechanism will be used. 124 * @see javax.validation.Validation#byProvider(Class) 125 * @see javax.validation.Validation#byDefaultProvider() 126 */ 127 @SuppressWarnings("rawtypes") 128 public void setProviderClass(Class providerClass) { 129 this.providerClass = providerClass; 130 } 131 132 /** 133 * Specify a JSR-303 {@link ValidationProviderResolver} for bootstrapping the 134 * provider of choice, as an alternative to {@code META-INF} driven resolution. 135 * @since 4.3 136 */ 137 public void setValidationProviderResolver(ValidationProviderResolver validationProviderResolver) { 138 this.validationProviderResolver = validationProviderResolver; 139 } 140 141 /** 142 * Specify a custom MessageInterpolator to use for this ValidatorFactory 143 * and its exposed default Validator. 144 */ 145 public void setMessageInterpolator(MessageInterpolator messageInterpolator) { 146 this.messageInterpolator = messageInterpolator; 147 } 148 149 /** 150 * Specify a custom Spring MessageSource for resolving validation messages, 151 * instead of relying on JSR-303's default "ValidationMessages.properties" bundle 152 * in the classpath. This may refer to a Spring context's shared "messageSource" bean, 153 * or to some special MessageSource setup for validation purposes only. 154 * <p><b>NOTE:</b> This feature requires Hibernate Validator 4.3 or higher on the classpath. 155 * You may nevertheless use a different validation provider but Hibernate Validator's 156 * {@link ResourceBundleMessageInterpolator} class must be accessible during configuration. 157 * <p>Specify either this property or {@link #setMessageInterpolator "messageInterpolator"}, 158 * not both. If you would like to build a custom MessageInterpolator, consider deriving from 159 * Hibernate Validator's {@link ResourceBundleMessageInterpolator} and passing in a 160 * Spring-based {@code ResourceBundleLocator} when constructing your interpolator. 161 * <p>In order for Hibernate's default validation messages to be resolved still, your 162 * {@link MessageSource} must be configured for optional resolution (usually the default). 163 * In particular, the {@code MessageSource} instance specified here should not apply 164 * {@link org.springframework.context.support.AbstractMessageSource#setUseCodeAsDefaultMessage 165 * "useCodeAsDefaultMessage"} behavior. Please double-check your setup accordingly. 166 * @see ResourceBundleMessageInterpolator 167 */ 168 public void setValidationMessageSource(MessageSource messageSource) { 169 this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource); 170 } 171 172 /** 173 * Specify a custom TraversableResolver to use for this ValidatorFactory 174 * and its exposed default Validator. 175 */ 176 public void setTraversableResolver(TraversableResolver traversableResolver) { 177 this.traversableResolver = traversableResolver; 178 } 179 180 /** 181 * Specify a custom ConstraintValidatorFactory to use for this ValidatorFactory. 182 * <p>Default is a {@link SpringConstraintValidatorFactory}, delegating to the 183 * containing ApplicationContext for creating autowired ConstraintValidator instances. 184 */ 185 public void setConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) { 186 this.constraintValidatorFactory = constraintValidatorFactory; 187 } 188 189 /** 190 * Set the ParameterNameDiscoverer to use for resolving method and constructor 191 * parameter names if needed for message interpolation. 192 * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}. 193 */ 194 public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { 195 this.parameterNameDiscoverer = parameterNameDiscoverer; 196 } 197 198 /** 199 * Specify resource locations to load XML constraint mapping files from, if any. 200 */ 201 public void setMappingLocations(Resource... mappingLocations) { 202 this.mappingLocations = mappingLocations; 203 } 204 205 /** 206 * Specify bean validation properties to be passed to the validation provider. 207 * <p>Can be populated with a String "value" (parsed via PropertiesEditor) 208 * or a "props" element in XML bean definitions. 209 * @see javax.validation.Configuration#addProperty(String, String) 210 */ 211 public void setValidationProperties(Properties jpaProperties) { 212 CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.validationPropertyMap); 213 } 214 215 /** 216 * Specify bean validation properties to be passed to the validation provider as a Map. 217 * <p>Can be populated with a "map" or "props" element in XML bean definitions. 218 * @see javax.validation.Configuration#addProperty(String, String) 219 */ 220 public void setValidationPropertyMap(@Nullable Map<String, String> validationProperties) { 221 if (validationProperties != null) { 222 this.validationPropertyMap.putAll(validationProperties); 223 } 224 } 225 226 /** 227 * Allow Map access to the bean validation properties to be passed to the validation provider, 228 * with the option to add or override specific entries. 229 * <p>Useful for specifying entries directly, for example via "validationPropertyMap[myKey]". 230 */ 231 public Map<String, String> getValidationPropertyMap() { 232 return this.validationPropertyMap; 233 } 234 235 @Override 236 public void setApplicationContext(ApplicationContext applicationContext) { 237 this.applicationContext = applicationContext; 238 } 239 240 241 @Override 242 @SuppressWarnings({"rawtypes", "unchecked"}) 243 public void afterPropertiesSet() { 244 Configuration<?> configuration; 245 if (this.providerClass != null) { 246 ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass); 247 if (this.validationProviderResolver != null) { 248 bootstrap = bootstrap.providerResolver(this.validationProviderResolver); 249 } 250 configuration = bootstrap.configure(); 251 } 252 else { 253 GenericBootstrap bootstrap = Validation.byDefaultProvider(); 254 if (this.validationProviderResolver != null) { 255 bootstrap = bootstrap.providerResolver(this.validationProviderResolver); 256 } 257 configuration = bootstrap.configure(); 258 } 259 260 // Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method 261 if (this.applicationContext != null) { 262 try { 263 Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class); 264 ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader()); 265 } 266 catch (NoSuchMethodException ex) { 267 // Ignore - no Hibernate Validator 5.2+ or similar provider 268 } 269 } 270 271 MessageInterpolator targetInterpolator = this.messageInterpolator; 272 if (targetInterpolator == null) { 273 targetInterpolator = configuration.getDefaultMessageInterpolator(); 274 } 275 configuration.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator)); 276 277 if (this.traversableResolver != null) { 278 configuration.traversableResolver(this.traversableResolver); 279 } 280 281 ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory; 282 if (targetConstraintValidatorFactory == null && this.applicationContext != null) { 283 targetConstraintValidatorFactory = 284 new SpringConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory()); 285 } 286 if (targetConstraintValidatorFactory != null) { 287 configuration.constraintValidatorFactory(targetConstraintValidatorFactory); 288 } 289 290 if (this.parameterNameDiscoverer != null) { 291 configureParameterNameProvider(this.parameterNameDiscoverer, configuration); 292 } 293 294 if (this.mappingLocations != null) { 295 for (Resource location : this.mappingLocations) { 296 try { 297 configuration.addMapping(location.getInputStream()); 298 } 299 catch (IOException ex) { 300 throw new IllegalStateException("Cannot read mapping resource: " + location); 301 } 302 } 303 } 304 305 this.validationPropertyMap.forEach(configuration::addProperty); 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 configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) { 315 final ParameterNameProvider defaultProvider = configuration.getDefaultParameterNameProvider(); 316 configuration.parameterNameProvider(new ParameterNameProvider() { 317 @Override 318 public List<String> getParameterNames(Constructor<?> constructor) { 319 String[] paramNames = discoverer.getParameterNames(constructor); 320 return (paramNames != null ? Arrays.asList(paramNames) : 321 defaultProvider.getParameterNames(constructor)); 322 } 323 @Override 324 public List<String> getParameterNames(Method method) { 325 String[] paramNames = discoverer.getParameterNames(method); 326 return (paramNames != null ? Arrays.asList(paramNames) : 327 defaultProvider.getParameterNames(method)); 328 } 329 }); 330 } 331 332 /** 333 * Post-process the given Bean Validation configuration, 334 * adding to or overriding any of its settings. 335 * <p>Invoked right before building the {@link ValidatorFactory}. 336 * @param configuration the Configuration object, pre-populated with 337 * settings driven by LocalValidatorFactoryBean's properties 338 */ 339 protected void postProcessConfiguration(Configuration<?> configuration) { 340 } 341 342 343 @Override 344 public Validator getValidator() { 345 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 346 return this.validatorFactory.getValidator(); 347 } 348 349 @Override 350 public ValidatorContext usingContext() { 351 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 352 return this.validatorFactory.usingContext(); 353 } 354 355 @Override 356 public MessageInterpolator getMessageInterpolator() { 357 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 358 return this.validatorFactory.getMessageInterpolator(); 359 } 360 361 @Override 362 public TraversableResolver getTraversableResolver() { 363 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 364 return this.validatorFactory.getTraversableResolver(); 365 } 366 367 @Override 368 public ConstraintValidatorFactory getConstraintValidatorFactory() { 369 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 370 return this.validatorFactory.getConstraintValidatorFactory(); 371 } 372 373 @Override 374 public ParameterNameProvider getParameterNameProvider() { 375 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 376 return this.validatorFactory.getParameterNameProvider(); 377 } 378 379 // Bean Validation 2.0: currently not implemented here since it would imply 380 // a hard dependency on the new javax.validation.ClockProvider interface. 381 // To be resolved once Spring Framework requires Bean Validation 2.0+. 382 // Obtain the native ValidatorFactory through unwrap(ValidatorFactory.class) 383 // instead which will fully support a getClockProvider() call as well. 384 /* 385 @Override 386 public javax.validation.ClockProvider getClockProvider() { 387 Assert.notNull(this.validatorFactory, "No target ValidatorFactory set"); 388 return this.validatorFactory.getClockProvider(); 389 } 390 */ 391 392 @Override 393 @SuppressWarnings("unchecked") 394 public <T> T unwrap(@Nullable Class<T> type) { 395 if (type == null || !ValidatorFactory.class.isAssignableFrom(type)) { 396 try { 397 return super.unwrap(type); 398 } 399 catch (ValidationException ex) { 400 // ignore - we'll try ValidatorFactory unwrapping next 401 } 402 } 403 if (this.validatorFactory != null) { 404 try { 405 return this.validatorFactory.unwrap(type); 406 } 407 catch (ValidationException ex) { 408 // ignore if just being asked for ValidatorFactory 409 if (ValidatorFactory.class == type) { 410 return (T) this.validatorFactory; 411 } 412 throw ex; 413 } 414 } 415 throw new ValidationException("Cannot unwrap to " + type); 416 } 417 418 @Override 419 public void close() { 420 if (this.validatorFactory != null) { 421 this.validatorFactory.close(); 422 } 423 } 424 425 @Override 426 public void destroy() { 427 close(); 428 } 429 430 431 /** 432 * Inner class to avoid a hard-coded Hibernate Validator dependency. 433 */ 434 private static class HibernateValidatorDelegate { 435 436 public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) { 437 return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource)); 438 } 439 } 440 441}