We are using Spring MVC 3.0.6, but we are not using JSR 303 validation, only the Binding errors using BindingResult in our Controller methods that deal with our model form beans. I am going to try and simplify the examples below because the question isn't about how things are architected, as those decissions were made prior to my arrival. I am just trying to get things to work right within the parameters I have.
In this particular form I am working on I have a form bean that is a list of sub-beans, with the view allowing the user to add/remove a bunch of these sub-beans.
The form bean looks something like:
public class FormBean {
private List<SubBean> subBeans;
...
}
And the sub bean:
public class SubBean {
private Integer value1;
private Date value2;
private String value3;
}
In the view JSP we are doing something like:
<form:form modelAttribute="formBean">
<spring:hasBindErrors name="formBean">
<div class="error-box">
<div class="error-txt">
<form:errors path="*" cssClass="error" />
</div>
</div>
</spring:hasBindErrors>
<c:forEach items="${formBean.subBeans}" var="subBean" varStatus="subBeanStatus">
...
<form:input path="subBeans[${subBeanStatus.index}].value1" />
<form:input path="subBeans[${subBeanStatus.index}].value2" />
<form:input path="subBeans[${subBeanStatus.index}].value3" />
...
</c:forEach>
...
</form:form>
The problem comes when I submit the form with an value that doesn't pass Binding-mustard. For instance, if I add an invalid int value for value1, I get an error message like:
Failed to convert property value of type java.lang.String to required type java.lang.Integer for property subBeans[0].value1; nested exception is java.lang.NumberFormatException: For input string: "sdfs"
I know with non-nested beans, you can simply add a message to the Resource Bunder in the form:
typeMismatch.beanName.fieldName="This is my custom error message!!!"
But how do you control the error message when you have a List, as I do?
I didn't like the default messages either, and customized my own BindingErrorProcessor.
Basically what I want, usually, is just the "Last Field" name -- I want to say there's an Invalid value for Date, or Invalid value for Staff, or whatever. I include the rejected field text as well, which the standard Spring error-processor doesn't supply to the message.
public class SimpleMessage_BindingErrorProcessor
extends DefaultBindingErrorProcessor
{
@Override
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
// Create field error with the exceptions's code, e.g. "typeMismatch".
String field = ex.getPropertyName();
String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
Object rejectedValue = ex.getValue();
if (rejectedValue != null && rejectedValue.getClass().isArray()) {
rejectedValue = StringUtils.arrayToCommaDelimitedString(ObjectUtils.toObjectArray(rejectedValue));
}
Object[] arguments = getArgumentsForBindError( bindingResult.getObjectName(), field, rejectedValue);
FieldError fieldError = new FieldError(
bindingResult.getObjectName(), field, rejectedValue, true,
codes, arguments, ex.getLocalizedMessage());
bindingResult.addError( fieldError);
}
/**
* Return FieldError arguments for a binding error on the given field.
* <p>TW's implementation returns {0} simple field title, {1} rejected value, {2} FQ field resolvable as per Spring DefaultBindingErrorProcessor
* (of type DefaultMessageSourceResolvable, with "objectName.field" and "field" as codes).
* @param objectName the name of the target object
* @param propPath the field that caused the binding error
* @param rejectedValue the value that was rejected
* @return the Object array that represents the FieldError arguments
* @see org.springframework.validation.FieldError#getArguments
* @see org.springframework.context.support.DefaultMessageSourceResolvable
*/
protected Object[] getArgumentsForBindError (String objectName, String propPath, Object/*String*/ rejectedValue) {
// just the Simple Name of Field;
// (last field in path).
//
String lastField = getLastField_Title( propPath);
// create Resolvable for "Fully-Qualified" Field;
// -- Spring standard, too specific/ would require defining hundreds of distinct messages; we don't use these.
//
String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + propPath, propPath};
DefaultMessageSourceResolvable fqField_resolvable = new DefaultMessageSourceResolvable(codes, propPath);
// return Args; {0} simple name, {1} rejected text, {2} FQ complex name.
return new Object[]{
lastField, rejectedValue, fqField_resolvable
};
}
/**
* Return FieldError arguments for a binding error on the given field.
* <p>TW's implementation returns {0} simple field title, {1} FQ field resolvable as per Spring DefaultBindingErrorProcessor
* (of type DefaultMessageSourceResolvable, with "objectName.field" and "field" as codes).
* @param objectName the name of the target object
* @param propPath the field that caused the binding error
* @return the Object array that represents the FieldError arguments
* @see org.springframework.validation.FieldError#getArguments
* @see org.springframework.context.support.DefaultMessageSourceResolvable
*/
@Override
protected Object[] getArgumentsForBindError (String objectName, String propPath) {
// just the Simple Name of Field;
// (last field in path).
//
String lastField = getLastField_Title( propPath);
// create Resolvable for "Fully-Qualified" Field;
// -- Spring standard, too specific/ would require defining hundreds of distinct messages; we don't use these.
//
String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + propPath, propPath};
DefaultMessageSourceResolvable fqField_resolvable = new DefaultMessageSourceResolvable(codes, propPath);
// return Args; {0} simple name, {2} FQ complex name.
return new Object[]{
lastField, fqField_resolvable
};
}
protected String getLastField_Title (String propPath) {
int index = propPath.lastIndexOf('.');
String title = (index >= 0) ? propPath.substring(index+1) : propPath;
return StrUtil.capitalize( title);
}
}
This works well! Now all your messages.properties has to say is:
# Type Mismatch generally;
# INCOMING 21/8/13 -- use {0} as 'Simple Name' of field, when using SimpleMessage_BindingErrorProcessor; {1} is 'resolvable' FQN of field.
#
typeMismatch=Invalid value for {0}: "{1}"
# Method Invocation/ value conversion;
# INCOMING 21/8/13 -- only expected for certain 'Value Converting'/ self-parsing properties; SPEC.
#
methodInvocation.machine=Invalid value for {0}: "{1}"
This area wasn't very clear.. the whole Binding -> Error Processing -> Message Resolving system is fairly complicated, and (as far as I can see) stuck with the problem that the message codes are generally far too specific.
There's very little around on this (I didn't find anything directly relevant on Google), so I hope this helps people.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With