diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java index befc4b9f2d..697944e69a 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -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. @@ -28,7 +28,9 @@ 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; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.validation.annotation.Validated; @@ -111,24 +113,42 @@ public class MethodValidationInterceptor implements MethodInterceptor { @SuppressWarnings("unchecked") public Object invoke(MethodInvocation invocation) throws Throwable { Class[] groups = determineValidationGroups(invocation); + if (forExecutablesMethod != null) { - Object executableValidator = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator); - Set> result = (Set>) - ReflectionUtils.invokeMethod(validateParametersMethod, executableValidator, - invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups); + // Standard Bean Validation 1.1 API + Object execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator); + Method methodToValidate = invocation.getMethod(); + Set> result; + + try { + result = (Set>) 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>) ReflectionUtils.invokeMethod(validateParametersMethod, + execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups); + } if (!result.isEmpty()) { throw new ConstraintViolationException(result); } + Object returnValue = invocation.proceed(); - result = (Set>) - ReflectionUtils.invokeMethod(validateReturnValueMethod, executableValidator, - invocation.getThis(), invocation.getMethod(), returnValue, groups); + + result = (Set>) ReflectionUtils.invokeMethod(validateReturnValueMethod, + execVal, invocation.getThis(), methodToValidate, returnValue, groups); if (!result.isEmpty()) { throw new ConstraintViolationException(result); } + return returnValue; } + else { + // Hibernate Validator 4.3's native API return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups); } } @@ -179,4 +199,5 @@ public class MethodValidationInterceptor implements MethodInterceptor { return returnValue; } } + } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java index d13a6e99a3..812b1d9ae1 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java @@ -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. @@ -61,6 +61,7 @@ public class MethodValidationTests { ac.registerSingleton("bean", MyValidBean.class); ac.refresh(); doTestProxyValidation(ac.getBean("bean", MyValidInterface.class)); + ac.close(); } @@ -68,21 +69,21 @@ public class MethodValidationTests { assertNotNull(proxy.myValidMethod("value", 5)); try { assertNotNull(proxy.myValidMethod("value", 15)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { assertNotNull(proxy.myValidMethod(null, 5)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { assertNotNull(proxy.myValidMethod("value", 0)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected @@ -91,14 +92,23 @@ public class MethodValidationTests { proxy.myValidAsyncMethod("value", 5); try { proxy.myValidAsyncMethod("value", 15); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { proxy.myValidAsyncMethod(null, 5); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + + assertEquals("myValue", proxy.myGenericMethod("myValue")); + try { + proxy.myGenericMethod(null); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected @@ -107,7 +117,7 @@ public class MethodValidationTests { @MyStereotype - public static class MyValidBean implements MyValidInterface { + public static class MyValidBean implements MyValidInterface { @Override public Object myValidMethod(String arg1, int arg2) { @@ -117,15 +127,22 @@ public class MethodValidationTests { @Override public void myValidAsyncMethod(String arg1, int arg2) { } + + @Override + public String myGenericMethod(String value) { + return value; + } } - public interface MyValidInterface { + public interface MyValidInterface { @NotNull Object myValidMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); @MyValid @Async void myValidAsyncMethod(@NotNull(groups = OtherGroup.class) String arg1, @Max(10) int arg2); + + T myGenericMethod(@NotNull T value); } diff --git a/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java b/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java index 592e12e831..e70ce5b113 100644 --- a/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java +++ b/spring-orm-hibernate4/src/test/java/org/springframework/validation/hibernatevalidator5/MethodValidationTests.java @@ -72,21 +72,21 @@ public class MethodValidationTests { assertNotNull(proxy.myValidMethod("value", 5)); try { assertNotNull(proxy.myValidMethod("value", 15)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { assertNotNull(proxy.myValidMethod(null, 5)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { assertNotNull(proxy.myValidMethod("value", 0)); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected @@ -95,14 +95,23 @@ public class MethodValidationTests { proxy.myValidAsyncMethod("value", 5); try { proxy.myValidAsyncMethod("value", 15); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected } try { proxy.myValidAsyncMethod(null, 5); - fail("Should have thrown MethodConstraintViolationException"); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + + assertEquals("myValue", proxy.myGenericMethod("myValue")); + try { + proxy.myGenericMethod(null); + fail("Should have thrown ValidationException"); } catch (javax.validation.ValidationException ex) { // expected @@ -111,7 +120,7 @@ public class MethodValidationTests { @MyStereotype - public static class MyValidBean implements MyValidInterface { + public static class MyValidBean implements MyValidInterface { @Override public Object myValidMethod(String arg1, int arg2) { @@ -121,14 +130,22 @@ public class MethodValidationTests { @Override public void myValidAsyncMethod(String arg1, int arg2) { } + + @Override + public String myGenericMethod(String value) { + return value; + } } - public interface MyValidInterface { + public interface MyValidInterface { @NotNull Object myValidMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); - @Async void myValidAsyncMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); + @MyValid + @Async void myValidAsyncMethod(@NotNull(groups = OtherGroup.class) String arg1, @Max(10) int arg2); + + T myGenericMethod(@NotNull T value); } @@ -136,9 +153,19 @@ public class MethodValidationTests { } + public interface OtherGroup { + } + + @Validated({MyGroup.class, Default.class}) @Retention(RetentionPolicy.RUNTIME) public @interface MyStereotype { } + + @Validated({OtherGroup.class, Default.class}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MyValid { + } + }