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
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -18,14 +18,19 @@ package org.springframework.validation;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link MessageCodesResolver} interface.
|
||||
*
|
||||
* <p>Will create two message codes for an object error, in the following order:
|
||||
* <p>Will create two message codes for an object error, in the following order (when
|
||||
* using the {@link Format#PREFIX_ERROR_CODE prefixed}
|
||||
* {@link #setMessageCodeFormatter(MessageCodeFormatter) formatter}):
|
||||
* <ul>
|
||||
* <li>1.: code + "." + object name
|
||||
* <li>2.: code
|
||||
@@ -68,11 +73,18 @@ import org.springframework.util.StringUtils;
|
||||
* <li>7. try "typeMismatch"
|
||||
* </ul>
|
||||
*
|
||||
* <p>By default the {@code errorCode}s will be placed at the beginning of constructed
|
||||
* message strings. The {@link #setMessageCodeFormatter(MessageCodeFormatter)
|
||||
* messageCodeFormatter} property can be used to specify an alternative concatenation
|
||||
* {@link MessageCodeFormatter format}.
|
||||
*
|
||||
* <p>In order to group all codes into a specific category within your resource bundles,
|
||||
* e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name",
|
||||
* consider specifying a {@link #setPrefix prefix} to be applied.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
* @author Chris Beams
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -83,9 +95,13 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
|
||||
*/
|
||||
public static final String CODE_SEPARATOR = ".";
|
||||
|
||||
private static final MessageCodeFormatter DEFAULT_FORMATTER = Format.PREFIX_ERROR_CODE;
|
||||
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private MessageCodeFormatter formatter = DEFAULT_FORMATTER;
|
||||
|
||||
|
||||
/**
|
||||
* Specify a prefix to be applied to any code built by this resolver.
|
||||
@@ -96,6 +112,15 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
|
||||
this.prefix = (prefix != null ? prefix : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the format for message codes built by this resolver.
|
||||
* <p>The default is {@link Format#PREFIX_ERROR_CODE}.
|
||||
* @since 3.2
|
||||
*/
|
||||
public void setMessageCodeFormatter(MessageCodeFormatter formatter) {
|
||||
this.formatter = (formatter == null ? DEFAULT_FORMATTER : formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prefix to be applied to any code built by this resolver.
|
||||
* <p>Returns an empty String in case of no prefix.
|
||||
@@ -106,9 +131,7 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
|
||||
|
||||
|
||||
public String[] resolveMessageCodes(String errorCode, String objectName) {
|
||||
return new String[] {
|
||||
postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName),
|
||||
postProcessMessageCode(errorCode)};
|
||||
return resolveMessageCodes(errorCode, objectName, "", null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,26 +144,32 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
|
||||
* @return the list of codes
|
||||
*/
|
||||
public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class<?> fieldType) {
|
||||
List<String> codeList = new ArrayList<String>();
|
||||
Set<String> codeList = new LinkedHashSet<String>();
|
||||
List<String> fieldList = new ArrayList<String>();
|
||||
buildFieldList(field, fieldList);
|
||||
for (String fieldInList : fieldList) {
|
||||
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + fieldInList));
|
||||
}
|
||||
addCodes(codeList, errorCode, objectName, fieldList);
|
||||
int dotIndex = field.lastIndexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
buildFieldList(field.substring(dotIndex + 1), fieldList);
|
||||
}
|
||||
for (String fieldInList : fieldList) {
|
||||
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldInList));
|
||||
}
|
||||
addCodes(codeList, errorCode, null, fieldList);
|
||||
if (fieldType != null) {
|
||||
codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldType.getName()));
|
||||
addCode(codeList, errorCode, null, fieldType.getName());
|
||||
}
|
||||
codeList.add(postProcessMessageCode(errorCode));
|
||||
addCode(codeList, errorCode, null, null);
|
||||
return StringUtils.toStringArray(codeList);
|
||||
}
|
||||
|
||||
private void addCodes(Collection<String> codeList, String errorCode, String objectName, Iterable<String> fields) {
|
||||
for (String field : fields) {
|
||||
addCode(codeList, errorCode, objectName, field);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCode(Collection<String> codeList, String errorCode, String objectName, String field) {
|
||||
codeList.add(postProcessMessageCode(this.formatter.format(errorCode, objectName, field)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add both keyed and non-keyed entries for the supplied <code>field</code>
|
||||
* to the supplied field list.
|
||||
@@ -173,4 +202,53 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial
|
||||
return getPrefix() + code;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Common message code formats.
|
||||
*
|
||||
* @author Phil Webb
|
||||
* @author Chris Beams
|
||||
* @since 3.2
|
||||
* @see MessageCodeFormatter
|
||||
* @see DefaultMessageCodesResolver#setMessageCodeFormatter(MessageCodeFormatter)
|
||||
*/
|
||||
public static enum Format implements MessageCodeFormatter {
|
||||
|
||||
/**
|
||||
* Prefix the error code at the beginning of the generated message code. e.g.:
|
||||
* {@code errorCode + "." + object name + "." + field}
|
||||
*/
|
||||
PREFIX_ERROR_CODE {
|
||||
public String format(String errorCode, String objectName, String field) {
|
||||
return toDelimitedString(errorCode, objectName, field);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Postfix the error code at the end of the generated message code. e.g.:
|
||||
* {@code object name + "." + field + "." + errorCode}
|
||||
*/
|
||||
POSTFIX_ERROR_CODE {
|
||||
public String format(String errorCode, String objectName, String field) {
|
||||
return toDelimitedString(objectName, field, errorCode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Concatenate the given elements, delimiting each with
|
||||
* {@link DefaultMessageCodesResolver#CODE_SEPARATOR}, skipping zero-length or
|
||||
* null elements altogether.
|
||||
*/
|
||||
public static String toDelimitedString(String... elements) {
|
||||
StringBuilder rtn = new StringBuilder();
|
||||
for (String element : elements) {
|
||||
if(StringUtils.hasLength(element)) {
|
||||
rtn.append(rtn.length() == 0 ? "" : CODE_SEPARATOR);
|
||||
rtn.append(element);
|
||||
}
|
||||
}
|
||||
return rtn.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.validation;
|
||||
|
||||
/**
|
||||
* A strategy interface for formatting message codes.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.2
|
||||
* @see DefaultMessageCodesResolver
|
||||
*/
|
||||
public interface MessageCodeFormatter {
|
||||
|
||||
/**
|
||||
* Build and return a message code consisting of the given fields, usually delimited
|
||||
* by {@link DefaultMessageCodesResolver#CODE_SEPARATOR}.
|
||||
* @param errorCode e.g.: "typeMismatch"
|
||||
* @param objectName e.g.: "user"
|
||||
* @param field e.g. "age"
|
||||
* @return concatenated message code, e.g.: "typeMismatch.user.age"
|
||||
* @see DefaultMessageCodesResolver.Format
|
||||
*/
|
||||
String format(String errorCode, String objectName, String field);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.TestBean;
|
||||
import org.springframework.validation.DefaultMessageCodesResolver.Format;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultMessageCodesResolver}.
|
||||
@@ -119,5 +120,40 @@ public class DefaultMessageCodesResolverTests {
|
||||
"errorCode.objectName.field",
|
||||
"errorCode.field",
|
||||
"errorCode" })));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportPostfixFormat() throws Exception {
|
||||
resolver.setMessageCodeFormatter(Format.POSTFIX_ERROR_CODE);
|
||||
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName");
|
||||
assertThat(codes, is(equalTo(new String[] {
|
||||
"objectName.errorCode",
|
||||
"errorCode" })));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportFieldPostfixFormat() throws Exception {
|
||||
resolver.setMessageCodeFormatter(Format.POSTFIX_ERROR_CODE);
|
||||
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName", "field",
|
||||
TestBean.class);
|
||||
assertThat(codes, is(equalTo(new String[] {
|
||||
"objectName.field.errorCode",
|
||||
"field.errorCode",
|
||||
"org.springframework.beans.TestBean.errorCode",
|
||||
"errorCode" })));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSupportCustomFormat() throws Exception {
|
||||
resolver.setMessageCodeFormatter(new MessageCodeFormatter() {
|
||||
public String format(String errorCode, String objectName, String field) {
|
||||
return DefaultMessageCodesResolver.Format.toDelimitedString(
|
||||
"CUSTOM-" + errorCode, objectName, field);
|
||||
}
|
||||
});
|
||||
String[] codes = resolver.resolveMessageCodes("errorCode", "objectName");
|
||||
assertThat(codes, is(equalTo(new String[] {
|
||||
"CUSTOM-errorCode.objectName",
|
||||
"CUSTOM-errorCode" })));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user