From b06423a5f8a05318976f7c1c1340d3d4b650d922 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 12 Jan 2017 21:16:48 +0100 Subject: [PATCH] AbstractMessageSource does not attempt to format code-as-default-message Issue: SPR-15123 --- .../support/AbstractMessageSource.java | 68 ++++++++++++------- .../SpringValidatorAdapterTests.java | 47 ++++++++++--- 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java b/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java index 0cca055234d..6dcd90989bf 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java +++ b/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"); * you may not use this file except in compliance with the License. @@ -160,30 +160,21 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme } @Override - public final String getMessage(MessageSourceResolvable resolvable, Locale locale) - throws NoSuchMessageException { - + public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { String[] codes = resolvable.getCodes(); - if (codes == null) { - codes = new String[0]; - } - for (String code : codes) { - String msg = getMessageInternal(code, resolvable.getArguments(), locale); - if (msg != null) { - return msg; + if (codes != null) { + for (String code : codes) { + String message = getMessageInternal(code, resolvable.getArguments(), locale); + if (message != null) { + return message; + } } } - String defaultMessage = resolvable.getDefaultMessage(); + String defaultMessage = getDefaultMessage(resolvable, locale); if (defaultMessage != null) { - return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale); + return defaultMessage; } - if (codes.length > 0) { - String fallback = getDefaultMessage(codes[0]); - if (fallback != null) { - return fallback; - } - } - throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale); + throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? 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 args array of arguments that will be filled in for params * 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 * @see #getMessage(String, Object[], String, 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 args array of arguments that will be filled in for params * 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 * @see #getParentMessageSource() */ @@ -274,11 +265,36 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme return null; } + /** + * Get a default message for the given {@code MessageSourceResolvable}. + *

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. *

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 - * receive a NoSuchMessageException from {@code getMessage}. + * receive a {@code NoSuchMessageException} from {@code getMessage}. * @param code the message code that we couldn't resolve * and that we didn't receive an explicit default message for * @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, * it is advisable to circumvent MessageFormat for messages without arguments. * @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) * @return the message String, or {@code null} if not found * @see #resolveCode @@ -352,7 +368,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme * for messages without arguments, not involving MessageFormat. * See the {@link #resolveCodeWithoutArguments} javadoc for details. * @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) * @return the MessageFormat for the message, or {@code null} if not found * @see #resolveCodeWithoutArguments(String, java.util.Locale) 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 90d0c480267..de7bc833a1d 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 @@ -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"); * 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.Payload; import javax.validation.Validation; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import org.junit.Before; @@ -45,6 +46,7 @@ import static org.junit.Assert.*; /** * @author Kazuki Shimizu + * @author Juergen Hoeller * @since 4.3 */ 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 public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { TestBean testBean = new TestBean(); @@ -76,7 +92,6 @@ public class SpringValidatorAdapterTests { assertThat(errors.getFieldErrorCount("password"), is(1)); assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), is("Password must be same value with Password(Confirm)")); - } @Test // SPR-13406 @@ -89,24 +104,30 @@ public class SpringValidatorAdapterTests { validatorAdapter.validate(testBean, errors); assertThat(errors.getFieldErrorCount("email"), is(1)); + 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")); } - @Test // SPR-13406 - public void testNoStringArgumentValue() { + @Test // SPR-15123 + public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMessageFormat() { + messageSource.setAlwaysUseMessageFormat(true); + TestBean testBean = new TestBean(); - testBean.setPassword("pass"); - testBean.setConfirmPassword("pass"); + testBean.setEmail("test@example.com"); + testBean.setConfirmEmail("TEST@EXAMPLE.IO"); 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")); - + assertThat(errors.getFieldErrorCount("email"), is(1)); + 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")); } @@ -116,9 +137,12 @@ public class SpringValidatorAdapterTests { @Size(min = 8, max = 128) private String password; + private String confirmPassword; private String email; + + @Pattern(regexp = "[\\p{L} -]*", message = "Email required") private String confirmEmail; public String getPassword() { @@ -190,6 +214,7 @@ public class SpringValidatorAdapterTests { Same[] value(); } + public static class SameValidator implements ConstraintValidator { private String field;