Skip to content

Commit 7c399d7

Browse files
committed
Merge pull request #167 from philwebb/SPR-9707
# By Chris Beams (1) and Phillip Webb (1) * SPR-9707: Introduce MessageCodeFormatter abstraction Provide alternative message code resolver styles
2 parents a57ff50 + 38bfb2b commit 7c399d7

File tree

3 files changed

+167
-15
lines changed

3 files changed

+167
-15
lines changed

spring-context/src/main/java/org/springframework/validation/DefaultMessageCodesResolver.java

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -18,14 +18,19 @@
1818

1919
import java.io.Serializable;
2020
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.LinkedHashSet;
2123
import java.util.List;
24+
import java.util.Set;
2225

2326
import org.springframework.util.StringUtils;
2427

2528
/**
2629
* Default implementation of the {@link MessageCodesResolver} interface.
2730
*
28-
* <p>Will create two message codes for an object error, in the following order:
31+
* <p>Will create two message codes for an object error, in the following order (when
32+
* using the {@link Format#PREFIX_ERROR_CODE prefixed}
33+
* {@link #setMessageCodeFormatter(MessageCodeFormatter) formatter}):
2934
* <ul>
3035
* <li>1.: code + "." + object name
3136
* <li>2.: code
@@ -68,11 +73,18 @@
6873
* <li>7. try "typeMismatch"
6974
* </ul>
7075
*
76+
* <p>By default the {@code errorCode}s will be placed at the beginning of constructed
77+
* message strings. The {@link #setMessageCodeFormatter(MessageCodeFormatter)
78+
* messageCodeFormatter} property can be used to specify an alternative concatenation
79+
* {@link MessageCodeFormatter format}.
80+
*
7181
* <p>In order to group all codes into a specific category within your resource bundles,
7282
* e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name",
7383
* consider specifying a {@link #setPrefix prefix} to be applied.
7484
*
7585
* @author Juergen Hoeller
86+
* @author Phillip Webb
87+
* @author Chris Beams
7688
* @since 1.0.1
7789
*/
7890
@SuppressWarnings("serial")
@@ -83,9 +95,13 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
8395
*/
8496
public static final String CODE_SEPARATOR = ".";
8597

98+
private static final MessageCodeFormatter DEFAULT_FORMATTER = Format.PREFIX_ERROR_CODE;
99+
86100

87101
private String prefix = "";
88102

103+
private MessageCodeFormatter formatter = DEFAULT_FORMATTER;
104+
89105

90106
/**
91107
* Specify a prefix to be applied to any code built by this resolver.
@@ -96,6 +112,15 @@ public void setPrefix(String prefix) {
96112
this.prefix = (prefix != null ? prefix : "");
97113
}
98114

115+
/**
116+
* Specify the format for message codes built by this resolver.
117+
* <p>The default is {@link Format#PREFIX_ERROR_CODE}.
118+
* @since 3.2
119+
*/
120+
public void setMessageCodeFormatter(MessageCodeFormatter formatter) {
121+
this.formatter = (formatter == null ? DEFAULT_FORMATTER : formatter);
122+
}
123+
99124
/**
100125
* Return the prefix to be applied to any code built by this resolver.
101126
* <p>Returns an empty String in case of no prefix.
@@ -106,9 +131,7 @@ protected String getPrefix() {
106131

107132

108133
public String[] resolveMessageCodes(String errorCode, String objectName) {
109-
return new String[] {
110-
postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName),
111-
postProcessMessageCode(errorCode)};
134+
return resolveMessageCodes(errorCode, objectName, "", null);
112135
}
113136

114137
/**
@@ -121,26 +144,32 @@ public String[] resolveMessageCodes(String errorCode, String objectName) {
121144
* @return the list of codes
122145
*/
123146
public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class<?> fieldType) {
124-
List<String> codeList = new ArrayList<String>();
147+
Set<String> codeList = new LinkedHashSet<String>();
125148
List<String> fieldList = new ArrayList<String>();
126149
buildFieldList(field, fieldList);
127-
for (String fieldInList : fieldList) {
128-
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + fieldInList));
129-
}
150+
addCodes(codeList, errorCode, objectName, fieldList);
130151
int dotIndex = field.lastIndexOf('.');
131152
if (dotIndex != -1) {
132153
buildFieldList(field.substring(dotIndex + 1), fieldList);
133154
}
134-
for (String fieldInList : fieldList) {
135-
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldInList));
136-
}
155+
addCodes(codeList, errorCode, null, fieldList);
137156
if (fieldType != null) {
138-
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldType.getName()));
157+
addCode(codeList, errorCode, null, fieldType.getName());
139158
}
140-
codeList.add(postProcessMessageCode(errorCode));
159+
addCode(codeList, errorCode, null, null);
141160
return StringUtils.toStringArray(codeList);
142161
}
143162

163+
private void addCodes(Collection<String> codeList, String errorCode, String objectName, Iterable<String> fields) {
164+
for (String field : fields) {
165+
addCode(codeList, errorCode, objectName, field);
166+
}
167+
}
168+
169+
private void addCode(Collection<String> codeList, String errorCode, String objectName, String field) {
170+
codeList.add(postProcessMessageCode(this.formatter.format(errorCode, objectName, field)));
171+
}
172+
144173
/**
145174
* Add both keyed and non-keyed entries for the supplied <code>field</code>
146175
* to the supplied field list.
@@ -173,4 +202,53 @@ protected String postProcessMessageCode(String code) {
173202
return getPrefix() + code;
174203
}
175204

205+
206+
/**
207+
* Common message code formats.
208+
*
209+
* @author Phil Webb
210+
* @author Chris Beams
211+
* @since 3.2
212+
* @see MessageCodeFormatter
213+
* @see DefaultMessageCodesResolver#setMessageCodeFormatter(MessageCodeFormatter)
214+
*/
215+
public static enum Format implements MessageCodeFormatter {
216+
217+
/**
218+
* Prefix the error code at the beginning of the generated message code. e.g.:
219+
* {@code errorCode + "." + object name + "." + field}
220+
*/
221+
PREFIX_ERROR_CODE {
222+
public String format(String errorCode, String objectName, String field) {
223+
return toDelimitedString(errorCode, objectName, field);
224+
}
225+
},
226+
227+
/**
228+
* Postfix the error code at the end of the generated message code. e.g.:
229+
* {@code object name + "." + field + "." + errorCode}
230+
*/
231+
POSTFIX_ERROR_CODE {
232+
public String format(String errorCode, String objectName, String field) {
233+
return toDelimitedString(objectName, field, errorCode);
234+
}
235+
};
236+
237+
/**
238+
* Concatenate the given elements, delimiting each with
239+
* {@link DefaultMessageCodesResolver#CODE_SEPARATOR}, skipping zero-length or
240+
* null elements altogether.
241+
*/
242+
public static String toDelimitedString(String... elements) {
243+
StringBuilder rtn = new StringBuilder();
244+
for (String element : elements) {
245+
if(StringUtils.hasLength(element)) {
246+
rtn.append(rtn.length() == 0 ? "" : CODE_SEPARATOR);
247+
rtn.append(element);
248+
}
249+
}
250+
return rtn.toString();
251+
}
252+
}
253+
176254
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.validation;
18+
19+
/**
20+
* A strategy interface for formatting message codes.
21+
*
22+
* @author Chris Beams
23+
* @since 3.2
24+
* @see DefaultMessageCodesResolver
25+
*/
26+
public interface MessageCodeFormatter {
27+
28+
/**
29+
* Build and return a message code consisting of the given fields, usually delimited
30+
* by {@link DefaultMessageCodesResolver#CODE_SEPARATOR}.
31+
* @param errorCode e.g.: "typeMismatch"
32+
* @param objectName e.g.: "user"
33+
* @param field e.g. "age"
34+
* @return concatenated message code, e.g.: "typeMismatch.user.age"
35+
* @see DefaultMessageCodesResolver.Format
36+
*/
37+
String format(String errorCode, String objectName, String field);
38+
}

spring-context/src/test/java/org/springframework/validation/DefaultMessageCodesResolverTests.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.junit.Test;
2424
import org.springframework.beans.TestBean;
25+
import org.springframework.validation.DefaultMessageCodesResolver.Format;
2526

2627
/**
2728
* Tests for {@link DefaultMessageCodesResolver}.
@@ -119,5 +120,40 @@ public void shouldSupportNullFieldType() throws Exception {
119120
"errorCode.objectName.field",
120121
"errorCode.field",
121122
"errorCode" })));
122-
}
123+
}
124+
125+
@Test
126+
public void shouldSupportPostfixFormat() throws Exception {
127+
resolver.setMessageCodeFormatter(Format.POSTFIX_ERROR_CODE);
128+
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName");
129+
assertThat(codes, is(equalTo(new String[] {
130+
"objectName.errorCode",
131+
"errorCode" })));
132+
}
133+
134+
@Test
135+
public void shouldSupportFieldPostfixFormat() throws Exception {
136+
resolver.setMessageCodeFormatter(Format.POSTFIX_ERROR_CODE);
137+
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName", "field",
138+
TestBean.class);
139+
assertThat(codes, is(equalTo(new String[] {
140+
"objectName.field.errorCode",
141+
"field.errorCode",
142+
"org.springframework.beans.TestBean.errorCode",
143+
"errorCode" })));
144+
}
145+
146+
@Test
147+
public void shouldSupportCustomFormat() throws Exception {
148+
resolver.setMessageCodeFormatter(new MessageCodeFormatter() {
149+
public String format(String errorCode, String objectName, String field) {
150+
return DefaultMessageCodesResolver.Format.toDelimitedString(
151+
"CUSTOM-" + errorCode, objectName, field);
152+
}
153+
});
154+
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName");
155+
assertThat(codes, is(equalTo(new String[] {
156+
"CUSTOM-errorCode.objectName",
157+
"CUSTOM-errorCode" })));
158+
}
123159
}

0 commit comments

Comments
 (0)