diff --git a/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java b/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java index 75ebd3f7e1..094c04e494 100644 --- a/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java @@ -77,7 +77,7 @@ public class DefaultBindingErrorProcessor implements BindingErrorProcessor { } FieldError error = new FieldError(bindingResult.getObjectName(), field, rejectedValue, true, codes, arguments, ex.getLocalizedMessage()); - error.initSource(ex); + error.wrap(ex); bindingResult.addError(error); } diff --git a/spring-context/src/main/java/org/springframework/validation/ObjectError.java b/spring-context/src/main/java/org/springframework/validation/ObjectError.java index ef3378ea2f..f177b1e786 100644 --- a/spring-context/src/main/java/org/springframework/validation/ObjectError.java +++ b/spring-context/src/main/java/org/springframework/validation/ObjectError.java @@ -74,7 +74,7 @@ public class ObjectError extends DefaultMessageSourceResolvable { } /** - * Initialize the source behind this error: possibly an {@link Exception} + * Preserve the source behind this error: possibly an {@link Exception} * (typically {@link org.springframework.beans.PropertyAccessException}) * or a Bean Validation {@link javax.validation.ConstraintViolation}. *

Note that any such source object is being stored as transient: @@ -82,22 +82,51 @@ public class ObjectError extends DefaultMessageSourceResolvable { * @param source the source object * @since 5.0.4 */ - public void initSource(Object source) { - Assert.state(this.source == null, "Source already initialized"); + public void wrap(Object source) { + if (this.source != null) { + throw new IllegalStateException("Already wrapping " + this.source); + } this.source = source; } /** - * Return the source behind this error: possibly an {@link Exception} + * Unwrap the source behind this error: possibly an {@link Exception} * (typically {@link org.springframework.beans.PropertyAccessException}) * or a Bean Validation {@link javax.validation.ConstraintViolation}. - * @return the source object, or {@code null} if none available - * (none specified or not available anymore after deserialization) + *

The cause of the outermost exception will be introspected as well, + * e.g. the underlying conversion exception or exception thrown from a setter + * (instead of having to unwrap the {@code PropertyAccessException} in turn). + * @return the source object of the given type + * @throws IllegalArgumentException if no such source object is available + * (i.e. none specified or not available anymore after deserialization) * @since 5.0.4 */ - @Nullable - public Object getSource() { - return this.source; + public T unwrap(Class sourceType) { + if (sourceType.isInstance(this.source)) { + return sourceType.cast(this.source); + } + else if (this.source instanceof Throwable) { + Throwable cause = ((Throwable) this.source).getCause(); + if (sourceType.isInstance(cause)) { + return sourceType.cast(cause); + } + } + throw new IllegalArgumentException("No source object of the given type available: " + sourceType); + } + + /** + * Check the source behind this error: possibly an {@link Exception} + * (typically {@link org.springframework.beans.PropertyAccessException}) + * or a Bean Validation {@link javax.validation.ConstraintViolation}. + *

The cause of the outermost exception will be introspected as well, + * e.g. the underlying conversion exception or exception thrown from a setter + * (instead of having to unwrap the {@code PropertyAccessException} in turn). + * @return whether this error has been caused by a source object of the given type + * @since 5.0.4 + */ + public boolean contains(Class sourceType) { + return (sourceType.isInstance(this.source) || + (this.source instanceof Throwable && sourceType.isInstance(((Throwable) this.source).getCause()))); } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 4856779277..59d824a0d4 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -144,7 +144,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. String[] errorCodes = bindingResult.resolveMessageCodes(errorCode); ObjectError error = new ObjectError( errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()); - error.initSource(violation); + error.wrap(violation); bindingResult.addError(error); } else { @@ -152,7 +152,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); FieldError error = new FieldError(errors.getObjectName(), nestedField, rejectedValue, false, errorCodes, errorArgs, violation.getMessage()); - error.initSource(violation); + error.wrap(violation); bindingResult.addError(error); } } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index 10ab8e3095..539406e3f5 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -238,18 +238,25 @@ public class DataBinderTests { assertTrue("Has age errors", br.hasFieldErrors("age")); assertTrue("Correct number of age errors", br.getFieldErrorCount("age") == 1); - assertEquals("typeMismatch", binder.getBindingResult().getFieldError("age").getCode()); assertEquals("32x", binder.getBindingResult().getFieldValue("age")); - assertEquals("32x", binder.getBindingResult().getFieldError("age").getRejectedValue()); - assertTrue(binder.getBindingResult().getFieldError("age").getSource() instanceof TypeMismatchException); + FieldError ageError = binder.getBindingResult().getFieldError("age"); + assertNotNull(ageError); + assertEquals("typeMismatch", ageError.getCode()); + assertEquals("32x", ageError.getRejectedValue()); + assertTrue(ageError.contains(TypeMismatchException.class)); + assertTrue(ageError.contains(NumberFormatException.class)); + assertTrue(ageError.unwrap(NumberFormatException.class).getMessage().contains("32x")); assertEquals(0, tb.getAge()); assertTrue("Has touchy errors", br.hasFieldErrors("touchy")); assertTrue("Correct number of touchy errors", br.getFieldErrorCount("touchy") == 1); - assertEquals("methodInvocation", binder.getBindingResult().getFieldError("touchy").getCode()); assertEquals("m.y", binder.getBindingResult().getFieldValue("touchy")); - assertEquals("m.y", binder.getBindingResult().getFieldError("touchy").getRejectedValue()); - assertTrue(binder.getBindingResult().getFieldError("touchy").getSource() instanceof MethodInvocationException); + FieldError touchyError = binder.getBindingResult().getFieldError("touchy"); + assertNotNull(touchyError); + assertEquals("methodInvocation", touchyError.getCode()); + assertEquals("m.y", touchyError.getRejectedValue()); + assertTrue(touchyError.contains(MethodInvocationException.class)); + assertTrue(touchyError.unwrap(MethodInvocationException.class).getCause().getMessage().contains("a .")); assertNull(tb.getTouchy()); rod = new TestBean(); @@ -331,16 +338,20 @@ public class DataBinderTests { assertTrue("Has age errors", br.hasFieldErrors("age")); assertTrue("Correct number of age errors", br.getFieldErrorCount("age") == 1); - assertEquals("typeMismatch", binder.getBindingResult().getFieldError("age").getCode()); assertEquals("32x", binder.getBindingResult().getFieldValue("age")); - assertEquals("32x", binder.getBindingResult().getFieldError("age").getRejectedValue()); + FieldError ageError = binder.getBindingResult().getFieldError("age"); + assertNotNull(ageError); + assertEquals("typeMismatch", ageError.getCode()); + assertEquals("32x", ageError.getRejectedValue()); assertEquals(0, tb.getAge()); assertTrue("Has touchy errors", br.hasFieldErrors("touchy")); assertTrue("Correct number of touchy errors", br.getFieldErrorCount("touchy") == 1); - assertEquals("methodInvocation", binder.getBindingResult().getFieldError("touchy").getCode()); assertEquals("m.y", binder.getBindingResult().getFieldValue("touchy")); - assertEquals("m.y", binder.getBindingResult().getFieldError("touchy").getRejectedValue()); + FieldError touchyError = binder.getBindingResult().getFieldError("touchy"); + assertNotNull(touchyError); + assertEquals("methodInvocation", touchyError.getCode()); + assertEquals("m.y", touchyError.getRejectedValue()); assertNull(tb.getTouchy()); assertTrue("Does not have spouse errors", !br.hasFieldErrors("spouse")); diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java index 2d366ce81e..85e09bdbb1 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java @@ -98,8 +98,8 @@ public class SpringValidatorAdapterTests { FieldError error = errors.getFieldError("password"); assertNotNull(error); assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password is must be between 8 and 128")); - assertTrue(error.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error.getSource()).getPropertyPath().toString(), is("password")); + assertTrue(error.contains(ConstraintViolation.class)); + assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); } @Test // SPR-13406 @@ -116,8 +116,8 @@ public class SpringValidatorAdapterTests { FieldError error = errors.getFieldError("password"); assertNotNull(error); assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Password must be same value with Password(Confirm)")); - assertTrue(error.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error.getSource()).getPropertyPath().toString(), is("password")); + assertTrue(error.contains(ConstraintViolation.class)); + assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); } @Test // SPR-13406 @@ -138,10 +138,10 @@ public class SpringValidatorAdapterTests { assertNotNull(error2); assertThat(messageSource.getMessage(error1, Locale.ENGLISH), is("email must be same value with confirmEmail")); assertThat(messageSource.getMessage(error2, Locale.ENGLISH), is("Email required")); - assertTrue(error1.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error1.getSource()).getPropertyPath().toString(), is("email")); - assertTrue(error2.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error2.getSource()).getPropertyPath().toString(), is("confirmEmail")); + assertTrue(error1.contains(ConstraintViolation.class)); + assertThat(error1.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("email")); + assertTrue(error2.contains(ConstraintViolation.class)); + assertThat(error2.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("confirmEmail")); } @Test // SPR-15123 @@ -164,10 +164,10 @@ public class SpringValidatorAdapterTests { assertNotNull(error2); assertThat(messageSource.getMessage(error1, Locale.ENGLISH), is("email must be same value with confirmEmail")); assertThat(messageSource.getMessage(error2, Locale.ENGLISH), is("Email required")); - assertTrue(error1.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error1.getSource()).getPropertyPath().toString(), is("email")); - assertTrue(error2.getSource() instanceof ConstraintViolation); - assertThat(((ConstraintViolation) error2.getSource()).getPropertyPath().toString(), is("confirmEmail")); + assertTrue(error1.contains(ConstraintViolation.class)); + assertThat(error1.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("email")); + assertTrue(error2.contains(ConstraintViolation.class)); + assertThat(error2.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("confirmEmail")); } @Test // SPR-16177