This commit is contained in:
Keith Donald
2009-02-27 22:07:40 +00:00
parent fa97580264
commit 8a1eb749f5
5 changed files with 177 additions and 4 deletions

View File

@@ -66,6 +66,7 @@ public class DefaultValidationFailureMessageResolverFactory implements Validatio
ConversionService conversionService) {
Assert.notNull(expressionParser, "The expressionParser is required");
this.expressionParser = expressionParser;
this.conversionService = conversionService;
}
/**
@@ -92,6 +93,8 @@ public class DefaultValidationFailureMessageResolverFactory implements Validatio
private static final String LABEL_ARGUMENT = "label";
private static final String VALUE_ARGUMENT = "value";
private ValidationFailure failure;
private ValidationFailureModelContext modelContext;
@@ -116,7 +119,11 @@ public class DefaultValidationFailureMessageResolverFactory implements Validatio
} else {
label = appendLabelPrefix().append(CODE_SEPARATOR).append(modelContext.getObjectName()).toString();
}
stringArgs.put(LABEL_ARGUMENT, new DefaultMessageSourceResolvable(label));
stringArgs.put(LABEL_ARGUMENT, new DefaultMessageSourceResolvable(new String[] { label }, failure
.getPropertyName()));
}
if (!stringArgs.containsKey(VALUE_ARGUMENT) && failure.getPropertyName() != null) {
stringArgs.put(VALUE_ARGUMENT, modelContext.getInvalidUserValue());
}
if (failure.getArguments() != null) {
Iterator it = failure.getArguments().entrySet().iterator();
@@ -140,7 +147,8 @@ public class DefaultValidationFailureMessageResolverFactory implements Validatio
}
private String[] buildCodes() {
String constraintMessageCode = appendMessageCodePrefix().append(failure.getConstraint()).toString();
String constraintMessageCode = appendMessageCodePrefix().append(CODE_SEPARATOR).append(
failure.getConstraint()).toString();
if (failure.getPropertyName() != null) {
String propertyConstraintMessageCode = appendMessageCodePrefix().append(CODE_SEPARATOR).append(
modelContext.getObjectName()).append(CODE_SEPARATOR).append(failure.getPropertyName()).append(

View File

@@ -19,6 +19,8 @@ import java.util.Map;
import java.util.TreeMap;
import org.springframework.binding.message.Severity;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
/**
* A builder that provides a fluent interface for configuring a Validation failure. Example:
@@ -80,7 +82,9 @@ public class ValidationFailureBuilder {
}
/**
* Sets the failure to be of warning severity.
* Adds a failure message argument.
* @param name the arg name
* @param value the arg value
* @return this, for fluent call chaining
*/
public ValidationFailureBuilder arg(String name, Object value) {
@@ -91,6 +95,18 @@ public class ValidationFailureBuilder {
return this;
}
/**
* Adds a failure message argument whose value is also message source resolvable. Use this when the argument value
* itself needs to be localized.
* @param name the arg name
* @param code the code that will be used to resolve the arg
* @see MessageSourceResolvable
* @return this, for fluent call chaining
*/
public ValidationFailureBuilder resolvableArg(String name, String code) {
return arg(name, new DefaultMessageSourceResolvable(code));
}
/**
* Build the ValidationFailure. Called after setting builder properties.
* @return this, for fluent call chaining

View File

@@ -12,14 +12,19 @@ public class ValidationFailureModelContext {
private String propertyTypeConverter;
private Object invalidValue;
/**
* Creates a new validation model context.
* @param objectName the object name
* @param invalidValue the invalid value the user entered
* @param propertyType the property type (may be null)
* @param propertyTypeConverter the id of the property type converter (may be null)
*/
public ValidationFailureModelContext(String objectName, Class propertyType, String propertyTypeConverter) {
public ValidationFailureModelContext(String objectName, Object invalidValue, Class propertyType,
String propertyTypeConverter) {
this.objectName = objectName;
this.invalidValue = invalidValue;
this.propertyType = propertyType;
this.propertyTypeConverter = propertyTypeConverter;
}
@@ -31,6 +36,13 @@ public class ValidationFailureModelContext {
return objectName;
}
/**
* The user entered value.
*/
public Object getInvalidUserValue() {
return invalidValue;
}
/**
* When reporting a property validation failure, the type of the property that failed to validate.
*/

View File

@@ -0,0 +1,127 @@
package org.springframework.binding.validation;
import java.util.Date;
import java.util.Locale;
import junit.framework.TestCase;
import org.jboss.el.ExpressionFactoryImpl;
import org.springframework.binding.convert.converters.StringToObject;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.binding.expression.el.ELExpressionParser;
import org.springframework.binding.message.Message;
import org.springframework.binding.message.MessageResolver;
import org.springframework.context.support.ResourceBundleMessageSource;
public class DefaultValidationFailureMessageResolverFactoryTests extends TestCase {
private ELExpressionParser parser = new ELExpressionParser(new ExpressionFactoryImpl());
private DefaultConversionService conversionService = new DefaultConversionService();
private ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
private DefaultValidationFailureMessageResolverFactory factory;
public void setUp() {
factory = new DefaultValidationFailureMessageResolverFactory(parser, conversionService);
messageSource.setBasename("org.springframework.binding.validation.messages");
}
public void testResolveMessage() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("foo").constraint("required").build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"", String.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Foo is required", message.getText());
}
public void testResolveMessageNoPropertyLabel() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("bogus").constraint("required").build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"", String.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("bogus is required", message.getText());
}
public void testResolveMessageWithCustomArg() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("checkinDate").constraint("invalidFormat").arg("format",
"yyyy-MM-dd").build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"bogus", Date.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Check In Date must be in format yyyy-MM-dd", message.getText());
}
public void testResolveMessageWithCustomResolvableArg() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("checkinDate").constraint("invalidFormat").resolvableArg(
"format", "formats.dateFormat").build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"bogus", Date.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Check In Date must be in format yyyy-MM-dd", message.getText());
}
public void testResolveMessageWithValue() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("checkinDate").constraint("invalidFormat2").resolvableArg(
"format", "formats.dateFormat").build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"bogus", Date.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Check In Date must be in format yyyy-MM-dd but it was 'bogus'", message.getText());
}
public void testResolveMessageWithArgDefaultConversion() {
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("amount").constraint("range").arg("min", new Integer(1)).arg(
"max", new Integer(100)).build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"bogus", Integer.class, null));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Amount must be between 1 and 100", message.getText());
}
public void testResolveMessageWithArgCustomConversion() {
conversionService.addConverter("stringToMoney", new StringToMoney());
ValidationFailureBuilder builder = new ValidationFailureBuilder();
ValidationFailure failure = builder.forProperty("amount").constraint("range").arg("min", new Money(1)).arg(
"max", new Money(100)).build();
MessageResolver resolver = factory.createMessageResolver(failure, new ValidationFailureModelContext("testBean",
"bogus", Money.class, "stringToMoney"));
Message message = resolver.resolveMessage(messageSource, Locale.getDefault());
assertEquals("Amount must be between $1 and $100", message.getText());
}
public static class Money {
private int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
}
public static class StringToMoney extends StringToObject {
public StringToMoney() {
super(Money.class);
}
protected Object toObject(String string, Class targetClass) throws Exception {
throw new UnsupportedOperationException("Not supported");
}
protected String toString(Object object) throws Exception {
return "$" + String.valueOf(((Money) object).amount);
}
}
}

View File

@@ -0,0 +1,10 @@
validation.required=#{label} is required
validation.invalidFormat=#{label} must be in format #{format}
validation.invalidFormat2=#{label} must be in format #{format} but it was '#{value}'
validation.range=#{label} must be between #{min} and #{max}
label.testBean.foo=Foo
label.testBean.checkinDate=Check In Date
label.testBean.amount=Amount
formats.dateFormat=yyyy-MM-dd