diff --git a/spring-webflow/src/main/java/org/springframework/webflow/validation/WebFlowMessageCodesResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/validation/WebFlowMessageCodesResolver.java index 46a08d17..0c2d46ca 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/validation/WebFlowMessageCodesResolver.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/validation/WebFlowMessageCodesResolver.java @@ -1,48 +1,140 @@ package org.springframework.webflow.validation; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.util.StringUtils; +import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.MessageCodesResolver; +/** + * Message Codes Resolver that implements the default Web Flow 2.x algorithm. The default algorithm differs from the + * Spring MVC {@link DefaultMessageCodesResolver} by appending the errorCode last instead of first. For + * example: myBean.myProperty.required instead of required.myBean.myProperty. + * + * Specifically: + *
+ * Will create two message codes for an object error, in the following order: + *
+ * Will create four message codes for a field error, in the following order: + *
+ * For example, in case of code "typeMismatch", object name "user", field "age" of type Integer: + *
+ * Default is none. Specify, for example, "validation." to get error codes like "validation.name.typeMismatch". */ - public String getFailureMessageCodePrefix() { - return failureMessageCodePrefix; + public void setPrefix(String prefix) { + this.prefix = (prefix != null ? prefix : ""); } /** - * A prefix to prepend to all validation failure message codes; default if not set explicitly is "validation". - * @param failureMessageCodePrefix the failure message code prefix + * Return the prefix to be applied to any code built by this resolver. + *
+ * Returns an empty String in case of no prefix. */ - public void setFailureMessageCodePrefix(String failureMessageCodePrefix) { - this.failureMessageCodePrefix = failureMessageCodePrefix; + protected String getPrefix() { + return this.prefix; } public String[] resolveMessageCodes(String errorCode, String objectName) { - String constraintMessageCode = appendFailureMessageCodePrefix().append(codeSeparator()).append(errorCode) - .toString(); - String objectConstraintMessageCode = appendFailureMessageCodePrefix().append(codeSeparator()) - .append(objectName).append(codeSeparator()).append(errorCode).toString(); - return new String[] { objectConstraintMessageCode, constraintMessageCode }; + return new String[] { postProcessMessageCode(objectName + CODE_SEPARATOR + errorCode), + postProcessMessageCode(errorCode) }; } + /** + * Build the code list for the given code and field: an object/field-specific code, a field-specific code, a plain + * error code. + *
+ * Arrays, Lists and Maps are resolved both for specific elements and the whole collection. + *
+ * See the {@link DefaultMessageCodesResolver class level Javadoc} for details on the generated codes.
+ * @return the list of codes
+ */
public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
- String propertyConstraintMessageCode = appendFailureMessageCodePrefix().append(codeSeparator()).append(
- objectName).append(codeSeparator()).append(field).append(codeSeparator()).append(errorCode).toString();
- String typeConstraintMessageCode = appendFailureMessageCodePrefix().append(codeSeparator()).append(fieldType)
- .append(codeSeparator()).append(errorCode).toString();
- return new String[] { propertyConstraintMessageCode, typeConstraintMessageCode, propertyConstraintMessageCode };
+ List codeList = new ArrayList();
+ List fieldList = new ArrayList();
+ buildFieldList(field, fieldList);
+ for (Iterator it = fieldList.iterator(); it.hasNext();) {
+ String fieldInList = (String) it.next();
+ codeList
+ .add(postProcessMessageCode(objectName + CODE_SEPARATOR + fieldInList + CODE_SEPARATOR + errorCode));
+ }
+ int dotIndex = field.lastIndexOf('.');
+ if (dotIndex != -1) {
+ buildFieldList(field.substring(dotIndex + 1), fieldList);
+ }
+ for (Iterator it = fieldList.iterator(); it.hasNext();) {
+ String fieldInList = (String) it.next();
+ codeList.add(postProcessMessageCode(fieldInList + CODE_SEPARATOR + errorCode));
+ }
+ if (fieldType != null) {
+ codeList.add(postProcessMessageCode(fieldType.getName() + CODE_SEPARATOR + errorCode));
+ }
+ codeList.add(postProcessMessageCode(errorCode));
+ return StringUtils.toStringArray(codeList);
}
- protected StringBuilder appendFailureMessageCodePrefix() {
- return new StringBuilder().append(failureMessageCodePrefix);
+ /**
+ * Add both keyed and non-keyed entries for the supplied field to the supplied field list.
+ */
+ protected void buildFieldList(String field, List fieldList) {
+ fieldList.add(field);
+ String plainField = field;
+ int keyIndex = plainField.lastIndexOf('[');
+ while (keyIndex != -1) {
+ int endKeyIndex = plainField.indexOf(']', keyIndex);
+ if (endKeyIndex != -1) {
+ plainField = plainField.substring(0, keyIndex) + plainField.substring(endKeyIndex + 1);
+ fieldList.add(plainField);
+ keyIndex = plainField.lastIndexOf('[');
+ } else {
+ keyIndex = -1;
+ }
+ }
}
- protected char codeSeparator() {
- return '.';
+ /**
+ * Post-process the given message code, built by this resolver.
+ *
+ * The default implementation applies the specified prefix, if any. + * @param code the message code as built by this resolver + * @return the final message code to be returned + * @see #setPrefix + */ + protected String postProcessMessageCode(String code) { + return getPrefix() + code; } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/validation/WebFlowMessageResolverTests.java b/spring-webflow/src/test/java/org/springframework/webflow/validation/WebFlowMessageResolverTests.java new file mode 100644 index 00000000..2ffb0ca4 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/validation/WebFlowMessageResolverTests.java @@ -0,0 +1,53 @@ +package org.springframework.webflow.validation; + +import junit.framework.TestCase; + +public class WebFlowMessageResolverTests extends TestCase { + private WebFlowMessageCodesResolver messageCodesResolver = new WebFlowMessageCodesResolver(); + + public void testResolveObjectMessageCodes() { + String[] codes = messageCodesResolver.resolveMessageCodes("required", "testBean"); + assertEquals(2, codes.length); + assertEquals("testBean.required", codes[0]); + assertEquals("required", codes[1]); + } + + public void testResolveObjectMessageCodesWithPrefix() { + messageCodesResolver.setPrefix("validation."); + String[] codes = messageCodesResolver.resolveMessageCodes("required", "testBean"); + assertEquals(2, codes.length); + assertEquals("validation.testBean.required", codes[0]); + assertEquals("validation.required", codes[1]); + } + + public void testResolveFieldMessageCodes() { + String[] codes = messageCodesResolver.resolveMessageCodes("required", "testBean", "foo", String.class); + assertEquals(4, codes.length); + assertEquals("testBean.foo.required", codes[0]); + assertEquals("foo.required", codes[1]); + assertEquals("java.lang.String.required", codes[2]); + assertEquals("required", codes[3]); + } + + public void testResolveFieldMessageCodesKeyedField() { + String[] codes = messageCodesResolver.resolveMessageCodes("required", "testBean", "foo[0]", String.class); + assertEquals(6, codes.length); + assertEquals("testBean.foo[0].required", codes[0]); + assertEquals("testBean.foo.required", codes[1]); + assertEquals("foo[0].required", codes[2]); + assertEquals("foo.required", codes[3]); + assertEquals("java.lang.String.required", codes[4]); + assertEquals("required", codes[5]); + } + + public void testResolveFieldMessageCodesWithPrefix() { + messageCodesResolver.setPrefix("validation."); + String[] codes = messageCodesResolver.resolveMessageCodes("required", "testBean", "foo", String.class); + assertEquals(4, codes.length); + assertEquals("validation.testBean.foo.required", codes[0]); + assertEquals("validation.foo.required", codes[1]); + assertEquals("validation.java.lang.String.required", codes[2]); + assertEquals("validation.required", codes[3]); + } + +}