SpringValidatorAdapter's ObjectError subclasses are serializable
Closes gh-23181
This commit is contained in:
parent
2aec175ccc
commit
aeef95938e
|
@ -50,6 +50,7 @@ import org.springframework.beans.BeanWrapper;
|
||||||
import org.springframework.beans.BeanWrapperImpl;
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
import org.springframework.context.support.StaticMessageSource;
|
import org.springframework.context.support.StaticMessageSource;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.SerializationTestUtils;
|
||||||
import org.springframework.validation.BeanPropertyBindingResult;
|
import org.springframework.validation.BeanPropertyBindingResult;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
|
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
|
||||||
|
@ -89,7 +90,7 @@ public class SpringValidatorAdapterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
public void testNoStringArgumentValue() {
|
public void testNoStringArgumentValue() throws Exception {
|
||||||
TestBean testBean = new TestBean();
|
TestBean testBean = new TestBean();
|
||||||
testBean.setPassword("pass");
|
testBean.setPassword("pass");
|
||||||
testBean.setConfirmPassword("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"));
|
assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password must be between 8 and 128"));
|
||||||
assertTrue(error.contains(ConstraintViolation.class));
|
assertTrue(error.contains(ConstraintViolation.class));
|
||||||
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
||||||
|
assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() {
|
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception {
|
||||||
TestBean testBean = new TestBean();
|
TestBean testBean = new TestBean();
|
||||||
testBean.setPassword("password");
|
testBean.setPassword("password");
|
||||||
testBean.setConfirmPassword("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)"));
|
assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Password must be same value as Password(Confirm)"));
|
||||||
assertTrue(error.contains(ConstraintViolation.class));
|
assertTrue(error.contains(ConstraintViolation.class));
|
||||||
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
||||||
|
assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
|
@ -518,10 +521,10 @@ public class SpringValidatorAdapterTests {
|
||||||
.addPropertyNode(f.getName())
|
.addPropertyNode(f.getName())
|
||||||
.addConstraintViolation();
|
.addConstraintViolation();
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException ex) {
|
}
|
||||||
|
catch (IllegalAccessException ex) {
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return fieldsErros.isEmpty();
|
return fieldsErros.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,27 +165,15 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||||
String nestedField = bindingResult.getNestedPath() + field;
|
String nestedField = bindingResult.getNestedPath() + field;
|
||||||
if (nestedField.isEmpty()) {
|
if (nestedField.isEmpty()) {
|
||||||
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
|
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
|
||||||
ObjectError error = new ObjectError(
|
ObjectError error = new ViolationObjectError(
|
||||||
errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()) {
|
errors.getObjectName(), errorCodes, errorArgs, violation, this);
|
||||||
@Override
|
|
||||||
public boolean shouldRenderDefaultMessage() {
|
|
||||||
return requiresMessageFormat(violation);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
error.wrap(violation);
|
|
||||||
bindingResult.addError(error);
|
bindingResult.addError(error);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Object rejectedValue = getRejectedValue(field, violation, bindingResult);
|
Object rejectedValue = getRejectedValue(field, violation, bindingResult);
|
||||||
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
|
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
|
||||||
FieldError error = new FieldError(errors.getObjectName(), nestedField,
|
FieldError error = new ViolationFieldError(errors.getObjectName(), nestedField,
|
||||||
rejectedValue, false, errorCodes, errorArgs, violation.getMessage()) {
|
rejectedValue, errorCodes, errorArgs, violation, this);
|
||||||
@Override
|
|
||||||
public boolean shouldRenderDefaultMessage() {
|
|
||||||
return requiresMessageFormat(violation);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
error.wrap(violation);
|
|
||||||
bindingResult.addError(error);
|
bindingResult.addError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,29 +295,6 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||||
return new DefaultMessageSourceResolvable(codes, field);
|
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.
|
|
||||||
* <p>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,
|
* Extract the rejected value behind the given constraint violation,
|
||||||
* for exposure through the Spring errors representation.
|
* for exposure through the Spring errors representation.
|
||||||
|
@ -354,6 +319,33 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||||
return invalidValue;
|
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.
|
||||||
|
* <p>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
|
// Implementation of JSR-303 Validator interface
|
||||||
|
@ -436,6 +428,71 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||||
public String getDefaultMessage() {
|
public String getDefaultMessage() {
|
||||||
return this.resolvableString;
|
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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.springframework.beans.BeanWrapper;
|
||||||
import org.springframework.beans.BeanWrapperImpl;
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
import org.springframework.context.support.StaticMessageSource;
|
import org.springframework.context.support.StaticMessageSource;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.SerializationTestUtils;
|
||||||
import org.springframework.validation.BeanPropertyBindingResult;
|
import org.springframework.validation.BeanPropertyBindingResult;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ public class SpringValidatorAdapterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
public void testNoStringArgumentValue() {
|
public void testNoStringArgumentValue() throws Exception {
|
||||||
TestBean testBean = new TestBean();
|
TestBean testBean = new TestBean();
|
||||||
testBean.setPassword("pass");
|
testBean.setPassword("pass");
|
||||||
testBean.setConfirmPassword("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"));
|
assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Size of Password must be between 8 and 128"));
|
||||||
assertTrue(error.contains(ConstraintViolation.class));
|
assertTrue(error.contains(ConstraintViolation.class));
|
||||||
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
||||||
|
assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() {
|
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception {
|
||||||
TestBean testBean = new TestBean();
|
TestBean testBean = new TestBean();
|
||||||
testBean.setPassword("password");
|
testBean.setPassword("password");
|
||||||
testBean.setConfirmPassword("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)"));
|
assertThat(messageSource.getMessage(error, Locale.ENGLISH), is("Password must be same value as Password(Confirm)"));
|
||||||
assertTrue(error.contains(ConstraintViolation.class));
|
assertTrue(error.contains(ConstraintViolation.class));
|
||||||
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
|
||||||
|
assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString()), is(error.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13406
|
@Test // SPR-13406
|
||||||
|
@ -473,10 +476,10 @@ public class SpringValidatorAdapterTests {
|
||||||
.addPropertyNode(f.getName())
|
.addPropertyNode(f.getName())
|
||||||
.addConstraintViolation();
|
.addConstraintViolation();
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException ex) {
|
}
|
||||||
|
catch (IllegalAccessException ex) {
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return fieldsErros.isEmpty();
|
return fieldsErros.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||||
|
|
||||||
<!-- Suppressions -->
|
<!-- Suppressions -->
|
||||||
<module name="SuppressionFilter">
|
<module name="SuppressionFilter">
|
||||||
<property name="file" value="${config_loc}/checkstyle-suppressions.xml"/>
|
<property name="file" value="${config_loc}/checkstyle-suppressions.xml"/>
|
||||||
|
@ -46,7 +45,9 @@
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck" />
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.InterfaceIsTypeCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.InterfaceIsTypeCheck" />
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck" />
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.MutableExceptionCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.MutableExceptionCheck">
|
||||||
|
<property name="format" value="^.*Exception$" />
|
||||||
|
</module>
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck" />
|
||||||
<module name="com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck" />
|
<module name="com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck" />
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue