Object/FieldError exposes source object through unwrap/contains methods
Issue: SPR-16372
This commit is contained in:
parent
6bbc5b0f26
commit
37609e4ede
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}.
|
||||
* <p>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)
|
||||
* <p>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> T unwrap(Class<T> 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}.
|
||||
* <p>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())));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue