Skip to content

Commit

Permalink
Avoid MessageFormat processing for default Bean Validation messages
Browse files Browse the repository at this point in the history
Closes gh-22761
  • Loading branch information
jhoeller committed Apr 8, 2019
1 parent fc9ce7c commit a1efe3c
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,6 +34,7 @@
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
import javax.validation.Validation;
Expand All @@ -50,11 +51,13 @@
import org.springframework.context.support.StaticMessageSource;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.StringContains.*;
import static org.junit.Assert.*;

/**
Expand All @@ -73,7 +76,7 @@ public class SpringValidatorAdapterTests {
@Before
public void setupSpringValidatorAdapter() {
messageSource.addMessage("Size", Locale.ENGLISH, "Size of {0} is must be between {2} and {1}");
messageSource.addMessage("Same", Locale.ENGLISH, "{2} must be same value with {1}");
messageSource.addMessage("Same", Locale.ENGLISH, "{2} must be same value as {1}");
messageSource.addMessage("password", Locale.ENGLISH, "Password");
messageSource.addMessage("confirmPassword", Locale.ENGLISH, "Password(Confirm)");
}
Expand All @@ -96,8 +99,11 @@ public void testNoStringArgumentValue() {

assertThat(errors.getFieldErrorCount("password"), is(1));
assertThat(errors.getFieldValue("password"), is("pass"));
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
is("Size of Password is must be between 8 and 128"));
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.contains(ConstraintViolation.class));
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("password"));
}

@Test // SPR-13406
Expand All @@ -111,8 +117,11 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLog

assertThat(errors.getFieldErrorCount("password"), is(1));
assertThat(errors.getFieldValue("password"), is("password"));
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
is("Password must be same value with Password(Confirm)"));
FieldError error = errors.getFieldError("password");
assertNotNull(error);
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"));
}

@Test // SPR-13406
Expand All @@ -127,10 +136,16 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithUnresolvedL
assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(errors.getFieldValue("email"), is("[email protected]"));
assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
is("email must be same value with confirmEmail"));
assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH),
is("Email required"));
FieldError error1 = errors.getFieldError("email");
FieldError error2 = errors.getFieldError("confirmEmail");
assertNotNull(error1);
assertNotNull(error2);
assertThat(messageSource.getMessage(error1, Locale.ENGLISH), is("email must be same value as confirmEmail"));
assertThat(messageSource.getMessage(error2, Locale.ENGLISH), is("Email required"));
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
Expand All @@ -147,10 +162,34 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMe
assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(errors.getFieldValue("email"), is("[email protected]"));
assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
is("email must be same value with confirmEmail"));
assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH),
is("Email required"));
FieldError error1 = errors.getFieldError("email");
FieldError error2 = errors.getFieldError("confirmEmail");
assertNotNull(error1);
assertNotNull(error2);
assertThat(messageSource.getMessage(error1, Locale.ENGLISH), is("email must be same value as confirmEmail"));
assertThat(messageSource.getMessage(error2, Locale.ENGLISH), is("Email required"));
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
public void testPatternMessage() {
TestBean testBean = new TestBean();
testBean.setEmail("X");
testBean.setConfirmEmail("X");

BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
validatorAdapter.validate(testBean, errors);

assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(errors.getFieldValue("email"), is("X"));
FieldError error = errors.getFieldError("email");
assertNotNull(error);
assertThat(messageSource.getMessage(error, Locale.ENGLISH), containsString("[\\w.'-]{1,}@[\\w.'-]{1,}"));
assertTrue(error.contains(ConstraintViolation.class));
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("email"));
}

@Test // SPR-16177
Expand Down Expand Up @@ -243,6 +282,7 @@ static class TestBean {

private String confirmPassword;

@Pattern(regexp = "[\\w.'-]{1,}@[\\w.'-]{1,}")
private String email;

@Pattern(regexp = "[\\p{L} -]*", message = "Email required")
Expand Down Expand Up @@ -403,13 +443,13 @@ public static class Child {

private Integer id;

@javax.validation.constraints.NotNull
@NotNull
private String name;

@javax.validation.constraints.NotNull
@NotNull
private Integer age;

@javax.validation.constraints.NotNull
@NotNull
private Parent parent;

public Integer getId() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -284,6 +284,9 @@ public void testListValidation() {
errors.initConversion(new DefaultConversionService());
validator.validate(listContainer, errors);

FieldError fieldError = errors.getFieldError("list[1]");
assertNotNull(fieldError);
assertEquals("X", fieldError.getRejectedValue());
assertEquals("X", errors.getFieldValue("list[1]"));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -287,6 +287,12 @@ protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale lo
String defaultMessage = resolvable.getDefaultMessage();
String[] codes = resolvable.getCodes();
if (defaultMessage != null) {
if (resolvable instanceof DefaultMessageSourceResolvable &&
!((DefaultMessageSourceResolvable) resolvable).shouldRenderDefaultMessage()) {
// Given default message does not contain any argument placeholders
// (and isn't escaped for alwaysUseMessageFormat either) -> return as-is.
return defaultMessage;
}
if (!ObjectUtils.isEmpty(codes) && defaultMessage.equals(codes[0])) {
// Never format a code-as-default-message, even with alwaysUseMessageFormat=true
return defaultMessage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -129,13 +129,28 @@ public String getDefaultMessage() {
return this.defaultMessage;
}

/**
* Indicate whether the specified default message needs to be rendered for
* substituting placeholders and/or {@link java.text.MessageFormat} escaping.
* @return {@code true} if the default message may contain argument placeholders;
* {@code false} if it definitely does not contain placeholders or custom escaping
* and can therefore be simply exposed as-is
* @since 5.1.7
* @see #getDefaultMessage()
* @see #getArguments()
* @see AbstractMessageSource#renderDefaultMessage
*/
public boolean shouldRenderDefaultMessage() {
return true;
}


/**
* Build a default String representation for this MessageSourceResolvable:
* including codes, arguments, and default message.
*/
protected final String resolvableToString() {
StringBuilder result = new StringBuilder();
StringBuilder result = new StringBuilder(64);
result.append("codes [").append(StringUtils.arrayToDelimitedString(this.codes, ","));
result.append("]; arguments [").append(StringUtils.arrayToDelimitedString(this.arguments, ","));
result.append("]; default message [").append(this.defaultMessage).append(']');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -148,6 +148,7 @@ private Class<?>[] asValidationGroups(Object... validationHints) {
* @param violations the JSR-303 ConstraintViolation results
* @param errors the Spring errors object to register to
*/
@SuppressWarnings("serial")
protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
for (ConstraintViolation<Object> violation : violations) {
String field = determineField(violation);
Expand All @@ -165,15 +166,25 @@ protected void processConstraintViolations(Set<ConstraintViolation<Object>> viol
if (nestedField.isEmpty()) {
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
ObjectError error = new ObjectError(
errors.getObjectName(), errorCodes, errorArgs, violation.getMessage());
errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()) {
@Override
public boolean shouldRenderDefaultMessage() {
return false;
}
};
error.wrap(violation);
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());
rejectedValue, false, errorCodes, errorArgs, violation.getMessage()) {
@Override
public boolean shouldRenderDefaultMessage() {
return false;
}
};
error.wrap(violation);
bindingResult.addError(error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -54,6 +54,7 @@
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.StringContains.*;
import static org.junit.Assert.*;

/**
Expand Down Expand Up @@ -170,6 +171,24 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMe
assertThat(error2.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("confirmEmail"));
}

@Test
public void testPatternMessage() {
TestBean testBean = new TestBean();
testBean.setEmail("X");
testBean.setConfirmEmail("X");

BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
validatorAdapter.validate(testBean, errors);

assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(errors.getFieldValue("email"), is("X"));
FieldError error = errors.getFieldError("email");
assertNotNull(error);
assertThat(messageSource.getMessage(error, Locale.ENGLISH), containsString("[\\w.'-]{1,}@[\\w.'-]{1,}"));
assertTrue(error.contains(ConstraintViolation.class));
assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString(), is("email"));
}

@Test // SPR-16177
public void testWithList() {
Parent parent = new Parent();
Expand Down Expand Up @@ -218,6 +237,7 @@ static class TestBean {

private String confirmPassword;

@Pattern(regexp = "[\\w.'-]{1,}@[\\w.'-]{1,}")
private String email;

@Pattern(regexp = "[\\p{L} -]*", message = "Email required")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -284,6 +284,8 @@ public void testListValidation() {
validator.validate(listContainer, errors);

FieldError fieldError = errors.getFieldError("list[1]");
assertNotNull(fieldError);
assertEquals("X", fieldError.getRejectedValue());
assertEquals("X", errors.getFieldValue("list[1]"));
}

Expand Down

0 comments on commit a1efe3c

Please sign in to comment.