Skip to content

Commit b06423a

Browse files
committed
AbstractMessageSource does not attempt to format code-as-default-message
Issue: SPR-15123
1 parent 8084da5 commit b06423a

File tree

2 files changed

+78
-37
lines changed

2 files changed

+78
-37
lines changed

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

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -160,30 +160,21 @@ public final String getMessage(String code, Object[] args, Locale locale) throws
160160
}
161161

162162
@Override
163-
public final String getMessage(MessageSourceResolvable resolvable, Locale locale)
164-
throws NoSuchMessageException {
165-
163+
public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
166164
String[] codes = resolvable.getCodes();
167-
if (codes == null) {
168-
codes = new String[0];
169-
}
170-
for (String code : codes) {
171-
String msg = getMessageInternal(code, resolvable.getArguments(), locale);
172-
if (msg != null) {
173-
return msg;
165+
if (codes != null) {
166+
for (String code : codes) {
167+
String message = getMessageInternal(code, resolvable.getArguments(), locale);
168+
if (message != null) {
169+
return message;
170+
}
174171
}
175172
}
176-
String defaultMessage = resolvable.getDefaultMessage();
173+
String defaultMessage = getDefaultMessage(resolvable, locale);
177174
if (defaultMessage != null) {
178-
return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
175+
return defaultMessage;
179176
}
180-
if (codes.length > 0) {
181-
String fallback = getDefaultMessage(codes[0]);
182-
if (fallback != null) {
183-
return fallback;
184-
}
185-
}
186-
throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale);
177+
throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);
187178
}
188179

189180

@@ -194,7 +185,7 @@ public final String getMessage(MessageSourceResolvable resolvable, Locale locale
194185
* @param code the code to lookup up, such as 'calculator.noRateSet'
195186
* @param args array of arguments that will be filled in for params
196187
* within the message
197-
* @param locale the Locale in which to do the lookup
188+
* @param locale the locale in which to do the lookup
198189
* @return the resolved message, or {@code null} if not found
199190
* @see #getMessage(String, Object[], String, Locale)
200191
* @see #getMessage(String, Object[], Locale)
@@ -249,11 +240,11 @@ protected String getMessageInternal(String code, Object[] args, Locale locale) {
249240
}
250241

251242
/**
252-
* Try to retrieve the given message from the parent MessageSource, if any.
243+
* Try to retrieve the given message from the parent {@code MessageSource}, if any.
253244
* @param code the code to lookup up, such as 'calculator.noRateSet'
254245
* @param args array of arguments that will be filled in for params
255246
* within the message
256-
* @param locale the Locale in which to do the lookup
247+
* @param locale the locale in which to do the lookup
257248
* @return the resolved message, or {@code null} if not found
258249
* @see #getParentMessageSource()
259250
*/
@@ -274,11 +265,36 @@ protected String getMessageFromParent(String code, Object[] args, Locale locale)
274265
return null;
275266
}
276267

268+
/**
269+
* Get a default message for the given {@code MessageSourceResolvable}.
270+
* <p>This implementation fully renders the default message if available,
271+
* or just returns the plain default message {@code String} if the primary
272+
* message code is being used as a default message.
273+
* @param resolvable the value object to resolve a default message for
274+
* @param locale the current locale
275+
* @return the default message, or {@code null} if none
276+
* @since 4.3.6
277+
* @see #renderDefaultMessage(String, Object[], Locale)
278+
* @see #getDefaultMessage(String)
279+
*/
280+
protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale locale) {
281+
String defaultMessage = resolvable.getDefaultMessage();
282+
String[] codes = resolvable.getCodes();
283+
if (defaultMessage != null) {
284+
if (!ObjectUtils.isEmpty(codes) && defaultMessage.equals(codes[0])) {
285+
// Never format a code-as-default-message, even with alwaysUseMessageFormat=true
286+
return defaultMessage;
287+
}
288+
return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
289+
}
290+
return (!ObjectUtils.isEmpty(codes) ? getDefaultMessage(codes[0]) : null);
291+
}
292+
277293
/**
278294
* Return a fallback default message for the given code, if any.
279295
* <p>Default is to return the code itself if "useCodeAsDefaultMessage" is activated,
280296
* or return no fallback else. In case of no fallback, the caller will usually
281-
* receive a NoSuchMessageException from {@code getMessage}.
297+
* receive a {@code NoSuchMessageException} from {@code getMessage}.
282298
* @param code the message code that we couldn't resolve
283299
* and that we didn't receive an explicit default message for
284300
* @return the default message to use, or {@code null} if none
@@ -328,7 +344,7 @@ protected Object[] resolveArguments(Object[] args, Locale locale) {
328344
* pattern doesn't contain argument placeholders in the first place. Therefore,
329345
* it is advisable to circumvent MessageFormat for messages without arguments.
330346
* @param code the code of the message to resolve
331-
* @param locale the Locale to resolve the code for
347+
* @param locale the locale to resolve the code for
332348
* (subclasses are encouraged to support internationalization)
333349
* @return the message String, or {@code null} if not found
334350
* @see #resolveCode
@@ -352,7 +368,7 @@ protected String resolveCodeWithoutArguments(String code, Locale locale) {
352368
* for messages without arguments, not involving MessageFormat.</b>
353369
* See the {@link #resolveCodeWithoutArguments} javadoc for details.
354370
* @param code the code of the message to resolve
355-
* @param locale the Locale to resolve the code for
371+
* @param locale the locale to resolve the code for
356372
* (subclasses are encouraged to support internationalization)
357373
* @return the MessageFormat for the message, or {@code null} if not found
358374
* @see #resolveCodeWithoutArguments(String, java.util.Locale)

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import javax.validation.ConstraintValidatorContext;
2828
import javax.validation.Payload;
2929
import javax.validation.Validation;
30+
import javax.validation.constraints.Pattern;
3031
import javax.validation.constraints.Size;
3132

3233
import org.junit.Before;
@@ -45,6 +46,7 @@
4546

4647
/**
4748
* @author Kazuki Shimizu
49+
* @author Juergen Hoeller
4850
* @since 4.3
4951
*/
5052
public class SpringValidatorAdapterTests {
@@ -64,6 +66,20 @@ public void setupSpringValidatorAdapter() {
6466
}
6567

6668

69+
@Test // SPR-13406
70+
public void testNoStringArgumentValue() {
71+
TestBean testBean = new TestBean();
72+
testBean.setPassword("pass");
73+
testBean.setConfirmPassword("pass");
74+
75+
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean");
76+
validatorAdapter.validate(testBean, errors);
77+
78+
assertThat(errors.getFieldErrorCount("password"), is(1));
79+
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
80+
is("Size of Password is must be between 8 and 128"));
81+
}
82+
6783
@Test // SPR-13406
6884
public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() {
6985
TestBean testBean = new TestBean();
@@ -76,7 +92,6 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLog
7692
assertThat(errors.getFieldErrorCount("password"), is(1));
7793
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
7894
is("Password must be same value with Password(Confirm)"));
79-
8095
}
8196

8297
@Test // SPR-13406
@@ -89,24 +104,30 @@ public void testApplyMessageSourceResolvableToStringArgumentValueWithUnresolvedL
89104
validatorAdapter.validate(testBean, errors);
90105

91106
assertThat(errors.getFieldErrorCount("email"), is(1));
107+
assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
92108
assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
93109
is("email must be same value with confirmEmail"));
94-
110+
assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH),
111+
is("Email required"));
95112
}
96113

97-
@Test // SPR-13406
98-
public void testNoStringArgumentValue() {
114+
@Test // SPR-15123
115+
public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMessageFormat() {
116+
messageSource.setAlwaysUseMessageFormat(true);
117+
99118
TestBean testBean = new TestBean();
100-
testBean.setPassword("pass");
101-
testBean.setConfirmPassword("pass");
119+
testBean.setEmail("test@example.com");
120+
testBean.setConfirmEmail("TEST@EXAMPLE.IO");
102121

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

106-
assertThat(errors.getFieldErrorCount("password"), is(1));
107-
assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH),
108-
is("Size of Password is must be between 8 and 128"));
109-
125+
assertThat(errors.getFieldErrorCount("email"), is(1));
126+
assertThat(errors.getFieldErrorCount("confirmEmail"), is(1));
127+
assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH),
128+
is("email must be same value with confirmEmail"));
129+
assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH),
130+
is("Email required"));
110131
}
111132

112133

@@ -116,9 +137,12 @@ static class TestBean {
116137

117138
@Size(min = 8, max = 128)
118139
private String password;
140+
119141
private String confirmPassword;
120142

121143
private String email;
144+
145+
@Pattern(regexp = "[\\p{L} -]*", message = "Email required")
122146
private String confirmEmail;
123147

124148
public String getPassword() {
@@ -190,6 +214,7 @@ public void setConfirmEmail(String confirmEmail) {
190214
Same[] value();
191215
}
192216

217+
193218
public static class SameValidator implements ConstraintValidator<Same, Object> {
194219

195220
private String field;

0 commit comments

Comments
 (0)