diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java index e488036d40..86bcdd675d 100644 --- a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java @@ -50,6 +50,7 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.support.StaticMessageSource; import org.springframework.util.ObjectUtils; +import org.springframework.util.SerializationTestUtils; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.beanvalidation.SpringValidatorAdapter; @@ -89,7 +90,7 @@ public class SpringValidatorAdapterTests { } @Test // SPR-13406 - public void testNoStringArgumentValue() { + public void testNoStringArgumentValue() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("pass"); testBean.setConfirmPassword("pass"); @@ -104,10 +105,11 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password must be between 8 and 128")); assertTrue(error.contains(ConstraintViolation.class)); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString())); } @Test // SPR-13406 - public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { + public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("password"); testBean.setConfirmPassword("PASSWORD"); @@ -122,6 +124,7 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Password must be same value as Password(Confirm)")); assertTrue(error.contains(ConstraintViolation.class)); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString())); } @Test // SPR-13406 @@ -518,10 +521,10 @@ public class SpringValidatorAdapterTests { .addPropertyNode(f.getName()) .addConstraintViolation(); } - } catch (IllegalAccessException ex) { + } + catch (IllegalAccessException ex) { throw new IllegalStateException(ex); } - }); return fieldsErros.isEmpty(); } 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 a55fff3d69..a6b6b5e46f 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 @@ -165,27 +165,15 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. String nestedField = bindingResult.getNestedPath() + field; if (nestedField.isEmpty()) { String[] errorCodes = bindingResult.resolveMessageCodes(errorCode); - ObjectError error = new ObjectError( - errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()) { - @Override - public boolean shouldRenderDefaultMessage() { - return requiresMessageFormat(violation); - } - }; - error.wrap(violation); + ObjectError error = new ViolationObjectError( + errors.getObjectName(), errorCodes, errorArgs, violation, this); bindingResult.addError(error); } else { Object rejectedValue = getRejectedValue(field, violation, bindingResult); String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); - FieldError error = new FieldError(errors.getObjectName(), nestedField, - rejectedValue, false, errorCodes, errorArgs, violation.getMessage()) { - @Override - public boolean shouldRenderDefaultMessage() { - return requiresMessageFormat(violation); - } - }; - error.wrap(violation); + FieldError error = new ViolationFieldError(errors.getObjectName(), nestedField, + rejectedValue, errorCodes, errorArgs, violation, this); bindingResult.addError(error); } } @@ -307,29 +295,6 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. return new DefaultMessageSourceResolvable(codes, field); } - /** - * Indicate whether this violation's interpolated message has remaining - * placeholders and therefore requires {@link java.text.MessageFormat} - * to be applied to it. Called for a Bean Validation defined message - * (coming out {@code ValidationMessages.properties}) when rendered - * as the default message in Spring's MessageSource. - *

The default implementation considers a Spring-style "{0}" placeholder - * for the field name as an indication for {@link java.text.MessageFormat}. - * Any other placeholder or escape syntax occurrences are typically a - * mismatch, coming out of regex pattern values or the like. Note that - * standard Bean Validation does not support "{0}" style placeholders at all; - * this is a feature typically used in Spring MessageSource resource bundles. - * @param violation the Bean Validation constraint violation, including - * BV-defined interpolation of named attribute references in its message - * @return {@code true} if {@code java.text.MessageFormat} is to be applied, - * or {@code false} if the violation's message should be used as-is - * @since 5.1.8 - * @see #getArgumentsForConstraint - */ - protected boolean requiresMessageFormat(ConstraintViolation violation) { - return violation.getMessage().contains("{0}"); - } - /** * Extract the rejected value behind the given constraint violation, * for exposure through the Spring errors representation. @@ -354,6 +319,33 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. return invalidValue; } + /** + * Indicate whether this violation's interpolated message has remaining + * placeholders and therefore requires {@link java.text.MessageFormat} + * to be applied to it. Called for a Bean Validation defined message + * (coming out {@code ValidationMessages.properties}) when rendered + * as the default message in Spring's MessageSource. + *

The default implementation considers a Spring-style "{0}" placeholder + * for the field name as an indication for {@link java.text.MessageFormat}. + * Any other placeholder or escape syntax occurrences are typically a + * mismatch, coming out of regex pattern values or the like. Note that + * standard Bean Validation does not support "{0}" style placeholders at all; + * this is a feature typically used in Spring MessageSource resource bundles. + * @param violation the Bean Validation constraint violation, including + * BV-defined interpolation of named attribute references in its message + * @return {@code true} if {@code java.text.MessageFormat} is to be applied, + * or {@code false} if the violation's message should be used as-is + * @since 5.1.8 + * @see #getArgumentsForConstraint + */ + protected boolean requiresMessageFormat(ConstraintViolation violation) { + return containsSpringStylePlaceholder(violation.getMessage()); + } + + private static boolean containsSpringStylePlaceholder(@Nullable String message) { + return (message != null && message.contains("{0}")); + } + //--------------------------------------------------------------------- // Implementation of JSR-303 Validator interface @@ -436,6 +428,71 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. public String getDefaultMessage() { return this.resolvableString; } + + @Override + public String toString() { + return this.resolvableString; + } + } + + + /** + * Subclass of {@code ObjectError} with Spring-style default message rendering. + */ + @SuppressWarnings("serial") + private static class ViolationObjectError extends ObjectError implements Serializable { + + @Nullable + private transient SpringValidatorAdapter adapter; + + @Nullable + private transient ConstraintViolation violation; + + public ViolationObjectError(String objectName, String[] codes, Object[] arguments, + ConstraintViolation violation, SpringValidatorAdapter adapter) { + + super(objectName, codes, arguments, violation.getMessage()); + this.adapter = adapter; + this.violation = violation; + wrap(violation); + } + + @Override + public boolean shouldRenderDefaultMessage() { + return (this.adapter != null && this.violation != null ? + this.adapter.requiresMessageFormat(this.violation) : + containsSpringStylePlaceholder(getDefaultMessage())); + } + } + + + /** + * Subclass of {@code FieldError} with Spring-style default message rendering. + */ + @SuppressWarnings("serial") + private static class ViolationFieldError extends FieldError implements Serializable { + + @Nullable + private transient SpringValidatorAdapter adapter; + + @Nullable + private transient ConstraintViolation violation; + + public ViolationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, + Object[] arguments, ConstraintViolation violation, SpringValidatorAdapter adapter) { + + super(objectName, field, rejectedValue, false, codes, arguments, violation.getMessage()); + this.adapter = adapter; + this.violation = violation; + wrap(violation); + } + + @Override + public boolean shouldRenderDefaultMessage() { + return (this.adapter != null && this.violation != null ? + this.adapter.requiresMessageFormat(this.violation) : + containsSpringStylePlaceholder(getDefaultMessage())); + } } } 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 482eba19a8..414769cba4 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 @@ -48,6 +48,7 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.support.StaticMessageSource; import org.springframework.util.ObjectUtils; +import org.springframework.util.SerializationTestUtils; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; @@ -86,7 +87,7 @@ public class SpringValidatorAdapterTests { } @Test // SPR-13406 - public void testNoStringArgumentValue() { + public void testNoStringArgumentValue() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("pass"); testBean.setConfirmPassword("pass"); @@ -101,10 +102,11 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password must be between 8 and 128")); assertTrue(error.contains(ConstraintViolation.class)); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString())); } @Test // SPR-13406 - public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { + public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("password"); testBean.setConfirmPassword("PASSWORD"); @@ -119,6 +121,7 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Password must be same value as Password(Confirm)")); assertTrue(error.contains(ConstraintViolation.class)); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password")); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString())); } @Test // SPR-13406 @@ -473,10 +476,10 @@ public class SpringValidatorAdapterTests { .addPropertyNode(f.getName()) .addConstraintViolation(); } - } catch (IllegalAccessException ex) { + } + catch (IllegalAccessException ex) { throw new IllegalStateException(ex); } - }); return fieldsErros.isEmpty(); } diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index ccd01ad779..c9fcc6195e 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -1,7 +1,6 @@ - @@ -46,7 +45,9 @@ - + + +