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}