Browse Source

AbstractMessageSource does not attempt to format code-as-default-message

Issue: SPR-15123
pull/1296/head
Juergen Hoeller 9 years ago
parent
commit
b06423a5f8
  1. 68
      spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java
  2. 47
      spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java

68
spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -160,30 +160,21 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
} }
@Override @Override
public final String getMessage(MessageSourceResolvable resolvable, Locale locale) public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
throws NoSuchMessageException {
String[] codes = resolvable.getCodes(); String[] codes = resolvable.getCodes();
if (codes == null) { if (codes != null) {
codes = new String[0]; for (String code : codes) {
} String message = getMessageInternal(code, resolvable.getArguments(), locale);
for (String code : codes) { if (message != null) {
String msg = getMessageInternal(code, resolvable.getArguments(), locale); return message;
if (msg != null) { }
return msg;
} }
} }
String defaultMessage = resolvable.getDefaultMessage(); String defaultMessage = getDefaultMessage(resolvable, locale);
if (defaultMessage != null) { if (defaultMessage != null) {
return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale); return defaultMessage;
} }
if (codes.length > 0) { throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);
String fallback = getDefaultMessage(codes[0]);
if (fallback != null) {
return fallback;
}
}
throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale);
} }
@ -194,7 +185,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
* @param code the code to lookup up, such as 'calculator.noRateSet' * @param code the code to lookup up, such as 'calculator.noRateSet'
* @param args array of arguments that will be filled in for params * @param args array of arguments that will be filled in for params
* within the message * within the message
* @param locale the Locale in which to do the lookup * @param locale the locale in which to do the lookup
* @return the resolved message, or {@code null} if not found * @return the resolved message, or {@code null} if not found
* @see #getMessage(String, Object[], String, Locale) * @see #getMessage(String, Object[], String, Locale)
* @see #getMessage(String, Object[], Locale) * @see #getMessage(String, Object[], Locale)
@ -249,11 +240,11 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
} }
/** /**
* Try to retrieve the given message from the parent MessageSource, if any. * Try to retrieve the given message from the parent {@code MessageSource}, if any.
* @param code the code to lookup up, such as 'calculator.noRateSet' * @param code the code to lookup up, such as 'calculator.noRateSet'
* @param args array of arguments that will be filled in for params * @param args array of arguments that will be filled in for params
* within the message * within the message
* @param locale the Locale in which to do the lookup * @param locale the locale in which to do the lookup
* @return the resolved message, or {@code null} if not found * @return the resolved message, or {@code null} if not found
* @see #getParentMessageSource() * @see #getParentMessageSource()
*/ */
@ -274,11 +265,36 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
return null; return null;
} }
/**
* Get a default message for the given {@code MessageSourceResolvable}.
* <p>This implementation fully renders the default message if available,
* or just returns the plain default message {@code String} if the primary
* message code is being used as a default message.
* @param resolvable the value object to resolve a default message for
* @param locale the current locale
* @return the default message, or {@code null} if none
* @since 4.3.6
* @see #renderDefaultMessage(String, Object[], Locale)
* @see #getDefaultMessage(String)
*/
protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale locale) {
String defaultMessage = resolvable.getDefaultMessage();
String[] codes = resolvable.getCodes();
if (defaultMessage != null) {
if (!ObjectUtils.isEmpty(codes) && defaultMessage.equals(codes[0])) {
// Never format a code-as-default-message, even with alwaysUseMessageFormat=true
return defaultMessage;
}
return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
}
return (!ObjectUtils.isEmpty(codes) ? getDefaultMessage(codes[0]) : null);
}
/** /**
* Return a fallback default message for the given code, if any. * Return a fallback default message for the given code, if any.
* <p>Default is to return the code itself if "useCodeAsDefaultMessage" is activated, * <p>Default is to return the code itself if "useCodeAsDefaultMessage" is activated,
* or return no fallback else. In case of no fallback, the caller will usually * or return no fallback else. In case of no fallback, the caller will usually
* receive a NoSuchMessageException from {@code getMessage}. * receive a {@code NoSuchMessageException} from {@code getMessage}.
* @param code the message code that we couldn't resolve * @param code the message code that we couldn't resolve
* and that we didn't receive an explicit default message for * and that we didn't receive an explicit default message for
* @return the default message to use, or {@code null} if none * @return the default message to use, or {@code null} if none
@ -328,7 +344,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
* pattern doesn't contain argument placeholders in the first place. Therefore, * pattern doesn't contain argument placeholders in the first place. Therefore,
* it is advisable to circumvent MessageFormat for messages without arguments. * it is advisable to circumvent MessageFormat for messages without arguments.
* @param code the code of the message to resolve * @param code the code of the message to resolve
* @param locale the Locale to resolve the code for * @param locale the locale to resolve the code for
* (subclasses are encouraged to support internationalization) * (subclasses are encouraged to support internationalization)
* @return the message String, or {@code null} if not found * @return the message String, or {@code null} if not found
* @see #resolveCode * @see #resolveCode
@ -352,7 +368,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
* for messages without arguments, not involving MessageFormat.</b> * for messages without arguments, not involving MessageFormat.</b>
* See the {@link #resolveCodeWithoutArguments} javadoc for details. * See the {@link #resolveCodeWithoutArguments} javadoc for details.
* @param code the code of the message to resolve * @param code the code of the message to resolve
* @param locale the Locale to resolve the code for * @param locale the locale to resolve the code for
* (subclasses are encouraged to support internationalization) * (subclasses are encouraged to support internationalization)
* @return the MessageFormat for the message, or {@code null} if not found * @return the MessageFormat for the message, or {@code null} if not found
* @see #resolveCodeWithoutArguments(String, java.util.Locale) * @see #resolveCodeWithoutArguments(String, java.util.Locale)

47
spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,6 +27,7 @@ import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload; import javax.validation.Payload;
import javax.validation.Validation; import javax.validation.Validation;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.junit.Before; import org.junit.Before;
@ -45,6 +46,7 @@ import static org.junit.Assert.*;
/** /**
* @author Kazuki Shimizu * @author Kazuki Shimizu
* @author Juergen Hoeller
* @since 4.3 * @since 4.3
*/ */
public class SpringValidatorAdapterTests { public class SpringValidatorAdapterTests {
@ -64,6 +66,20 @@ public class SpringValidatorAdapterTests {
} }
@Test // SPR-13406
public void testNoStringArgumentValue() {
TestBean testBean = new TestBean();
testBean.setPassword("pass");
testBean.setConfirmPassword("pass");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
validatorAdapter.validate(testBean, errors);
assertThat(errors.getFieldErrorCount("password"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
is("Size of Password is must be between 8 and 128"));
}
@Test // SPR-13406 @Test // SPR-13406
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() {
TestBean testBean = new TestBean(); TestBean testBean = new TestBean();
@ -76,7 +92,6 @@ public class SpringValidatorAdapterTests {
assertThat(errors.getFieldErrorCount("password"), is(1)); assertThat(errors.getFieldErrorCount("password"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
is("Password must be same value with Password(Confirm)")); is("Password must be same value with Password(Confirm)"));
} }
@Test // SPR-13406 @Test // SPR-13406
@ -89,24 +104,30 @@ public class SpringValidatorAdapterTests {
validatorAdapter.validate(testBean, errors); validatorAdapter.validate(testBean, errors);
assertThat(errors.getFieldErrorCount("email"), is(1)); assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH), assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
is("email must be same value with confirmEmail")); is("email must be same value with confirmEmail"));
assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH),
is("Email required"));
} }
@Test // SPR-13406 @Test // SPR-15123
public void testNoStringArgumentValue() { public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMessageFormat() {
messageSource.setAlwaysUseMessageFormat(true);
TestBean testBean = new TestBean(); TestBean testBean = new TestBean();
testBean.setPassword("pass"); testBean.setEmail("test@example.com");
testBean.setConfirmPassword("pass"); testBean.setConfirmEmail("TEST@EXAMPLE.IO");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
validatorAdapter.validate(testBean, errors); validatorAdapter.validate(testBean, errors);
assertThat(errors.getFieldErrorCount("password"), is(1)); assertThat(errors.getFieldErrorCount("email"), is(1));
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
is("Size of Password is must be between 8 and 128")); 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"));
} }
@ -116,9 +137,12 @@ public class SpringValidatorAdapterTests {
@Size(min = 8, max = 128) @Size(min = 8, max = 128)
private String password; private String password;
private String confirmPassword; private String confirmPassword;
private String email; private String email;
@Pattern(regexp = "[\\p{L} -]*", message = "Email required")
private String confirmEmail; private String confirmEmail;
public String getPassword() { public String getPassword() {
@ -190,6 +214,7 @@ public class SpringValidatorAdapterTests {
Same[] value(); Same[] value();
} }
public static class SameValidator implements ConstraintValidator<Same, Object> { public static class SameValidator implements ConstraintValidator<Same, Object> {
private String field; private String field;

Loading…
Cancel
Save