Upgrade to JPA 2.1+ and Bean Validation 1.1+; remove native support for Hibernate 3.6 and 4.x

Issue: SPR-13481
Issue: SPR-13827
This commit is contained in:
Juergen Hoeller
2016-07-04 23:37:23 +02:00
parent 69ec437fbc
commit 54004e0d78
128 changed files with 299 additions and 26731 deletions

View File

@@ -24,11 +24,13 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.validation.Configuration;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.ParameterNameProvider;
import javax.validation.TraversableResolver;
import javax.validation.Validation;
import javax.validation.ValidationProviderResolver;
@@ -66,14 +68,8 @@ import org.springframework.util.ReflectionUtils;
* you will almost always use the default Validator anyway. This can also be injected directly
* into any target dependency of type {@link org.springframework.validation.Validator}!
*
* <p><b>As of Spring 4.0, this class supports Bean Validation 1.0 and 1.1, with special support
* for Hibernate Validator 4.3 and 5.x</b> (see {@link #setValidationMessageSource}).
*
* <p>Note that Bean Validation 1.1's {@code #forExecutables} method isn't supported: We do not
* expect that method to be called by application code; consider {@link MethodValidationInterceptor}
* instead. If you really need programmatic {@code #forExecutables} access, inject this class as
* a {@link ValidatorFactory} and call {@link #getValidator()} on it, then {@code #forExecutables}
* on the returned native {@link Validator} reference instead of directly on this class.
* <p><b>As of Spring 5.0, this class requires Bean Validation 1.1, with special support
* for Hibernate Validator 5.x</b> (see {@link #setValidationMessageSource}).
*
* <p>This class is also being used by Spring's MVC configuration namespace, in case of the
* {@code javax.validation} API being present but no explicit Validator having been configured.
@@ -88,10 +84,6 @@ import org.springframework.util.ReflectionUtils;
public class LocalValidatorFactoryBean extends SpringValidatorAdapter
implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
// Bean Validation 1.1 close() method available?
private static final Method closeMethod = ClassUtils.getMethodIfAvailable(ValidatorFactory.class, "close");
@SuppressWarnings("rawtypes")
private Class providerClass;
@@ -305,55 +297,23 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
}
private void configureParameterNameProviderIfPossible(Configuration<?> configuration) {
try {
Class<?> parameterNameProviderClass =
ClassUtils.forName("javax.validation.ParameterNameProvider", getClass().getClassLoader());
Method parameterNameProviderMethod =
Configuration.class.getMethod("parameterNameProvider", parameterNameProviderClass);
final Object defaultProvider = ReflectionUtils.invokeMethod(
Configuration.class.getMethod("getDefaultParameterNameProvider"), configuration);
final ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
Object parameterNameProvider = Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {parameterNameProviderClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getParameterNames")) {
String[] result = null;
if (args[0] instanceof Constructor) {
result = discoverer.getParameterNames((Constructor<?>) args[0]);
}
else if (args[0] instanceof Method) {
result = discoverer.getParameterNames((Method) args[0]);
}
if (result != null) {
return Arrays.asList(result);
}
else {
try {
return method.invoke(defaultProvider, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
else {
// toString, equals, hashCode
try {
return method.invoke(this, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
});
ReflectionUtils.invokeMethod(parameterNameProviderMethod, configuration, parameterNameProvider);
}
catch (Exception ex) {
// Bean Validation 1.1 API not available - simply not applying the ParameterNameDiscoverer
}
// TODO: inner class
final ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
final ParameterNameProvider defaultProvider = configuration.getDefaultParameterNameProvider();
configuration.parameterNameProvider(new ParameterNameProvider() {
@Override
public List<String> getParameterNames(Constructor<?> constructor) {
String[] paramNames = discoverer.getParameterNames(constructor);
return (paramNames != null ? Arrays.asList(paramNames) :
defaultProvider.getParameterNames(constructor));
}
@Override
public List<String> getParameterNames(Method method) {
String[] paramNames = discoverer.getParameterNames(method);
return (paramNames != null ? Arrays.asList(paramNames) :
defaultProvider.getParameterNames(method));
}
});
}
/**
@@ -397,9 +357,14 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return this.validatorFactory.getConstraintValidatorFactory();
}
@Override
public ParameterNameProvider getParameterNameProvider() {
return this.validatorFactory.getParameterNameProvider();
}
public void close() {
if (closeMethod != null && this.validatorFactory != null) {
ReflectionUtils.invokeMethod(closeMethod, this.validatorFactory);
if (this.validatorFactory != null) {
this.validatorFactory.close();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import javax.validation.ValidatorFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.validator.HibernateValidator;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
@@ -48,9 +47,8 @@ import org.springframework.validation.annotation.Validated;
* at the type level of the containing target class, applying to all public service methods
* of that class. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 4.0, this functionality requires either a Bean Validation 1.1 provider
* (such as Hibernate Validator 5.x) or the Bean Validation 1.0 API with Hibernate Validator
* 4.3. The actual provider will be autodetected and automatically adapted.
* <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1 provider
* (such as Hibernate Validator 5.x).
*
* @author Juergen Hoeller
* @since 3.1
@@ -88,8 +86,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
this(forExecutablesMethod != null ? Validation.buildDefaultValidatorFactory() :
HibernateValidatorDelegate.buildValidatorFactory());
this(Validation.buildDefaultValidatorFactory());
}
/**
@@ -114,43 +111,36 @@ public class MethodValidationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?>[] groups = determineValidationGroups(invocation);
if (forExecutablesMethod != null) {
// Standard Bean Validation 1.1 API
Object execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator);
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<?>> result;
// Standard Bean Validation 1.1 API
Object execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator);
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<?>> result;
try {
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateReturnValueMethod,
execVal, invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
try {
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod,
execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
else {
// Hibernate Validator 4.3's native API
return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups);
Object returnValue = invocation.proceed();
result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateReturnValueMethod,
execVal, invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
/**
@@ -168,36 +158,4 @@ public class MethodValidationInterceptor implements MethodInterceptor {
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
/**
* Inner class to avoid a hard-coded Hibernate Validator 4.3 dependency.
*/
private static class HibernateValidatorDelegate {
public static ValidatorFactory buildValidatorFactory() {
return Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory();
}
@SuppressWarnings("deprecation")
public static Object invokeWithinValidation(MethodInvocation invocation, Validator validator, Class<?>[] groups)
throws Throwable {
org.hibernate.validator.method.MethodValidator methodValidator =
validator.unwrap(org.hibernate.validator.method.MethodValidator.class);
Set<org.hibernate.validator.method.MethodConstraintViolation<Object>> result =
methodValidator.validateAllParameters(
invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups);
if (!result.isEmpty()) {
throw new org.hibernate.validator.method.MethodConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
result = methodValidator.validateReturnValue(
invocation.getThis(), invocation.getMethod(), returnValue, groups);
if (!result.isEmpty()) {
throw new org.hibernate.validator.method.MethodConstraintViolationException(result);
}
return returnValue;
}
}
}

View File

@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.validation.ConstraintViolation;
import javax.validation.executable.ExecutableValidator;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
@@ -299,6 +300,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
}
@Override
public ExecutableValidator forExecutables() {
return this.targetValidator.forExecutables();
}
/**
* Wrapper for a String attribute which can be resolved via a {@code MessageSource},

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,10 +31,9 @@ import org.springframework.tests.sample.beans.TestBean;
import static org.junit.Assert.*;
/**
* Tested against Hibernate Validator 4.3, as of Spring 4.0.
* Tested against Hibernate Validator 5.x.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class BeanValidationPostProcessorTests {
@@ -52,6 +51,7 @@ public class BeanValidationPostProcessorTests {
assertTrue(ex.getRootCause().getMessage().contains("testBean"));
assertTrue(ex.getRootCause().getMessage().contains("invalid"));
}
ac.close();
}
@Test
@@ -63,6 +63,7 @@ public class BeanValidationPostProcessorTests {
bd.getPropertyValues().add("testBean", new TestBean());
ac.registerBeanDefinition("bean", bd);
ac.refresh();
ac.close();
}
@Test
@@ -74,6 +75,7 @@ public class BeanValidationPostProcessorTests {
ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class));
ac.registerBeanDefinition("bean", new RootBeanDefinition(AfterInitConstraintBean.class));
ac.refresh();
ac.close();
}
@Test
@@ -92,6 +94,7 @@ public class BeanValidationPostProcessorTests {
assertTrue(ex.getRootCause().getMessage().contains("stringValue"));
assertTrue(ex.getRootCause().getMessage().contains("invalid"));
}
ac.close();
}
@Test
@@ -103,6 +106,7 @@ public class BeanValidationPostProcessorTests {
bd.getPropertyValues().add("stringValue", "ss");
ac.registerBeanDefinition("bean", bd);
ac.refresh();
ac.close();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,11 +35,11 @@ import org.springframework.validation.annotation.Validated;
import static org.junit.Assert.*;
/**
* Tested against Hibernate Validator 4.3, as of Spring 4.0.
* Tests against Hibernate Validator 5.x.
*
* @author Juergen Hoeller
* @since 3.1
*/
@SuppressWarnings("rawtypes")
public class MethodValidationTests {
@Test
@@ -65,6 +65,7 @@ public class MethodValidationTests {
}
@SuppressWarnings("unchecked")
private void doTestProxyValidation(MyValidInterface proxy) {
assertNotNull(proxy.myValidMethod("value", 5));
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,10 @@ import javax.validation.constraints.NotNull;
import org.hibernate.validator.HibernateValidator;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
@@ -47,10 +51,9 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Tested against Hibernate Validator 4.3, as of Spring 4.0.
* Tests against Hibernate Validator 5.x.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class ValidatorFactoryTests {
@@ -58,6 +61,7 @@ public class ValidatorFactoryTests {
public void testSimpleValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
assertEquals(2, result.size());
@@ -78,6 +82,7 @@ public class ValidatorFactoryTests {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
assertEquals(2, result.size());
@@ -112,6 +117,7 @@ public class ValidatorFactoryTests {
public void testSpringValidationFieldType() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.setName("Phil");
person.getAddress().setStreet("Phil's Street");
@@ -126,6 +132,7 @@ public class ValidatorFactoryTests {
public void testSpringValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
@@ -153,6 +160,7 @@ public class ValidatorFactoryTests {
public void testSpringValidationWithClassLevel() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.setName("Juergen");
person.getAddress().setStreet("Juergen's Street");
@@ -166,10 +174,32 @@ public class ValidatorFactoryTests {
assertTrue(errorCodes.contains("NameAddressValid"));
}
@Test
public void testSpringValidationWithAutowiredValidator() throws Exception {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
LocalValidatorFactoryBean.class);
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
ValidPerson person = new ValidPerson();
person.expectsAutowiredValidator = true;
person.setName("Juergen");
person.getAddress().setStreet("Juergen's Street");
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(1, result.getErrorCount());
ObjectError globalError = result.getGlobalError();
List<String> errorCodes = Arrays.asList(globalError.getCodes());
assertEquals(2, errorCodes.size());
assertTrue(errorCodes.contains("NameAddressValid.person"));
assertTrue(errorCodes.contains("NameAddressValid"));
ctx.close();
}
@Test
public void testSpringValidationWithErrorInListElement() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.getAddressList().add(new ValidAddress());
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
@@ -187,6 +217,7 @@ public class ValidatorFactoryTests {
public void testSpringValidationWithErrorInSetElement() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.getAddressSet().add(new ValidAddress());
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
@@ -240,6 +271,8 @@ public class ValidatorFactoryTests {
@Valid
private Set<ValidAddress> addressSet = new LinkedHashSet<ValidAddress>();
public boolean expectsAutowiredValidator = false;
public String getName() {
return name;
}
@@ -304,16 +337,22 @@ public class ValidatorFactoryTests {
public static class NameAddressValidator implements ConstraintValidator<NameAddressValid, ValidPerson> {
@Autowired
private Environment environment;
@Override
public void initialize(NameAddressValid constraintAnnotation) {
}
@Override
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
if (value.expectsAutowiredValidator) {
assertNotNull(this.environment);
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
context.buildConstraintViolationWithTemplate(
context.getDefaultConstraintMessageTemplate()).addNode("address").addConstraintViolation().disableDefaultConstraintViolation();
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
@@ -378,7 +417,7 @@ public class ValidatorFactoryTests {
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
context.buildConstraintViolationWithTemplate("NULL"). addNode("value").addConstraintViolation();
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
return false;
}
return true;