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 @@