validation testing
This commit is contained in:
@@ -124,7 +124,7 @@ public class DefaultValidationFailureMessageResolverFactory implements Validatio
|
|||||||
|
|
||||||
public Message resolveMessage(MessageSource messageSource, Locale locale) {
|
public Message resolveMessage(MessageSource messageSource, Locale locale) {
|
||||||
DefaultMessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(failureMessageCodesFactory
|
DefaultMessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(failureMessageCodesFactory
|
||||||
.createMessageCodes(failure, modelContext), failure.getDefaultMessage());
|
.createMessageCodes(failure, modelContext), failure.getMessage());
|
||||||
String text = messageSource.getMessage(resolvable, locale);
|
String text = messageSource.getMessage(resolvable, locale);
|
||||||
Expression expression = expressionParser.parseExpression(text, new FluentParserContext()
|
Expression expression = expressionParser.parseExpression(text, new FluentParserContext()
|
||||||
.evaluate(Map.class).template());
|
.evaluate(Map.class).template());
|
||||||
|
|||||||
@@ -46,10 +46,41 @@ public interface ValidationContext {
|
|||||||
public Object getUserValue(String property);
|
public Object getUserValue(String property);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a validation failure to this context. Called by a validator to report failures against the current object
|
* Set the property of the model that is about to be validated. This property becomes the "current property"
|
||||||
* being validated. Use to report a general failure against the object, or a specific failure against a property on
|
* associated with this context.
|
||||||
* the object. The failure code provided is typically mapped to one or message codes that result in a Message being
|
* @param property the name of the property that will be validated
|
||||||
* added to the {@link MessageContext}.
|
*/
|
||||||
|
public void setProperty(String property);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the current property using the constraint provided. The value of the current property will be passed
|
||||||
|
* directly to the constraint for validation. Internally, the constraint may use {@link #addDefaultFailure()} to
|
||||||
|
* report a default failure, or {@link #addFailure(ValidationFailure)} to report a custom failure.
|
||||||
|
* @param constraint the validation constraint to invoek
|
||||||
|
*/
|
||||||
|
public void validate(Object constraint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the current property context using the constraint provided. Call this method when additional context
|
||||||
|
* besides just the property value is required for constraint validation. Internally, the constraint may use
|
||||||
|
* {@link #addDefaultFailure()} to report a default failure, or {@link #addFailure(ValidationFailure)} to report a
|
||||||
|
* custom failure.
|
||||||
|
* @param constraint the validation constraint to invoke
|
||||||
|
* @param propertyContext the property context object to validate
|
||||||
|
*/
|
||||||
|
public void validate(Object constraint, Object propertyContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the default validation failure for the current context. Called by a validation constraint to report failures
|
||||||
|
* against the current object being validated.
|
||||||
|
*/
|
||||||
|
public void addDefaultFailure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a validation failure to this context. Called by a validation constraint to report failures against the
|
||||||
|
* current object being validated. Use to report a general failure against the object, or a specific failure against
|
||||||
|
* a property on the object. The failure code provided is typically mapped to one or message codes that result in a
|
||||||
|
* Message being added to the {@link MessageContext}.
|
||||||
* @param failure the validation failure
|
* @param failure the validation failure
|
||||||
* @see #getMessageContext()
|
* @see #getMessageContext()
|
||||||
* @see ValidationFailureMessageResolverFactory
|
* @see ValidationFailureMessageResolverFactory
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.binding.validation;
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.binding.message.Severity;
|
import org.springframework.binding.message.Severity;
|
||||||
@@ -22,9 +23,10 @@ import org.springframework.util.Assert;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An indication of a validation failure. A failure is generated when a validation constraint is violated; for example
|
* An indication of a validation failure. A failure is generated when a validation constraint is violated; for example
|
||||||
* "required", "maxLength", "invalidFormat", or "range". A failure has a severity describing the intensity of the
|
* "required", "length", "invalidFormat". A failure has a severity describing the intensity of the violation. A failure
|
||||||
* violation. A failure may have additional arguments that can be used as named parameters in a UI display message. A
|
* may have an explicit message summarizing what went wrong, and may also provide additional details, such as a cause or
|
||||||
* failure can also have a default message to display if no suitable message can be resolved.
|
* suggested recovery action. A failure may also have additional arguments that can be used as named parameters in any
|
||||||
|
* UI display messages.
|
||||||
*/
|
*/
|
||||||
public class ValidationFailure {
|
public class ValidationFailure {
|
||||||
|
|
||||||
@@ -34,38 +36,43 @@ public class ValidationFailure {
|
|||||||
|
|
||||||
private Severity severity;
|
private Severity severity;
|
||||||
|
|
||||||
private Map arguments;
|
private String message;
|
||||||
|
|
||||||
private String defaultMessage;
|
private Map details;
|
||||||
|
|
||||||
|
private Map arguments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new validation failure
|
* Creates a new validation failure
|
||||||
* @param property the property that failed to validate (may be null to indicate a general failure)
|
|
||||||
* @param constraint the name of the validation constraint that failed (required)
|
|
||||||
* @param severity the severity of the failure (required)
|
* @param severity the severity of the failure (required)
|
||||||
|
* @param property the property that failed to validate (may be null)
|
||||||
|
* @param constraint the name of the validation constraint that failed (may be null)
|
||||||
|
* @param message an explicit failure message (may be null)
|
||||||
|
* @param details additional failure details (may be null)
|
||||||
* @param arguments named failure arguments (may be null)
|
* @param arguments named failure arguments (may be null)
|
||||||
* @param defaultMessage the default message text (may be null)
|
|
||||||
*/
|
*/
|
||||||
public ValidationFailure(String property, String constraint, Severity severity, Map arguments, String defaultMessage) {
|
public ValidationFailure(Severity severity, String property, String constraint, String message, Map details,
|
||||||
Assert.hasText(constraint, "The constraint is required");
|
Map arguments) {
|
||||||
Assert.notNull(severity, "The severity is required");
|
Assert.notNull(severity, "The severity is required");
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.constraint = constraint;
|
this.constraint = constraint;
|
||||||
this.severity = severity;
|
this.message = message;
|
||||||
this.arguments = arguments;
|
this.details = details != null ? Collections.unmodifiableMap(details) : Collections.EMPTY_MAP;
|
||||||
this.defaultMessage = defaultMessage;
|
this.arguments = arguments != null ? Collections.unmodifiableMap(arguments) : Collections.EMPTY_MAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the property that failed to validate. May be null to indicate a general failure against the validated
|
* The name of the property that failed to validate. May be null to indicate a failure against the currently
|
||||||
* object.
|
* validating object.
|
||||||
*/
|
*/
|
||||||
public String getProperty() {
|
public String getProperty() {
|
||||||
return property;
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The validation constraint that caused this failure to be reported.
|
* The name of the validation constraint that caused this failure to be reported. This constraint name can be used
|
||||||
|
* to resolve the failure message if no explicit {@link #getMessage()} is configured. May be null to indicate a
|
||||||
|
* failure against the currently validating constraint.
|
||||||
*/
|
*/
|
||||||
public String getConstraint() {
|
public String getConstraint() {
|
||||||
return constraint;
|
return constraint;
|
||||||
@@ -79,20 +86,34 @@ public class ValidationFailure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An map of arguments that can be used as named parameters in the message associated with this validation failure.
|
* The message summarizing this failure. May be a literal string or a resolvable message code. Can be null. If null,
|
||||||
* Each constraint that can fail defines a set of arguments that are specific to it. For example, a range constriant
|
* the failure message will be resolved using the constraint that was being validated when this failure was
|
||||||
* might define arguments of "min" and "max" of Integer values. In the message bundle, you then might see
|
* reported.
|
||||||
* "range=The ${label} field value must be between ${min} and ${max}".
|
*/
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of details providing additional information about this failure. Each entry in this map is a failure detail
|
||||||
|
* item that has a name and value. The name uniquely identifies the failure detail and describes its purpose; for
|
||||||
|
* example, a "cause" or "recommendedAction". The value is the failure detail message, either a literal string or
|
||||||
|
* resolvable code. If resolvable, the detail code is relative to the constraint associated with this failure.
|
||||||
|
* Returns an empty map if no details are present.
|
||||||
|
*/
|
||||||
|
public Map getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An map of arguments that can be used as named parameters in resolvable messages associated with this validation
|
||||||
|
* failure. Each constraint that can fail defines a set of arguments that are specific to it. For example, a length
|
||||||
|
* constraint might define arguments of "min" and "max" of Integer values. In the message bundle, you then might see
|
||||||
|
* "length=The ${label} field value must be between ${min} and ${max}". Returns an empty map if no arguments are
|
||||||
|
* present.
|
||||||
*/
|
*/
|
||||||
public Map getArguments() {
|
public Map getArguments() {
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The default message to display if no suitable display message can be resolved for this failure.
|
|
||||||
*/
|
|
||||||
public String getDefaultMessage() {
|
|
||||||
return defaultMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.binding.validation;
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
@@ -41,7 +42,27 @@ public class ValidationFailureBuilder {
|
|||||||
|
|
||||||
private Map args;
|
private Map args;
|
||||||
|
|
||||||
private String defaultText;
|
private String message;
|
||||||
|
|
||||||
|
private Map details;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the failure to be of warning severity.
|
||||||
|
* @return this, for fluent call chaining
|
||||||
|
*/
|
||||||
|
public ValidationFailureBuilder warning() {
|
||||||
|
severity = Severity.WARNING;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the failure to be of error severity. This is the default Severity.
|
||||||
|
* @return this, for fluent call chaining
|
||||||
|
*/
|
||||||
|
public ValidationFailureBuilder error() {
|
||||||
|
severity = Severity.ERROR;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the property the failure occurred against.
|
* Sets the property the failure occurred against.
|
||||||
@@ -64,20 +85,38 @@ public class ValidationFailureBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the failure to be of warning severity.
|
* Sets an explicit failure message. The value may be a literal string or a resolvable message code.
|
||||||
|
* @param message the failure message
|
||||||
* @return this, for fluent call chaining
|
* @return this, for fluent call chaining
|
||||||
*/
|
*/
|
||||||
public ValidationFailureBuilder warning() {
|
public ValidationFailureBuilder message(String message) {
|
||||||
severity = Severity.WARNING;
|
this.message = message;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the failure to be of error severity.
|
* Add a detail to associate with the failure. The value provided serves as both the logical name of the detail and
|
||||||
|
* the code used to resolve the detail message text.
|
||||||
|
* @param nameAndValue the value to use as both the logical name of the message and the constraint-relative message
|
||||||
|
* code; for example, "cause" or "recommendedAction".
|
||||||
* @return this, for fluent call chaining
|
* @return this, for fluent call chaining
|
||||||
*/
|
*/
|
||||||
public ValidationFailureBuilder error() {
|
public ValidationFailureBuilder detail(String nameAndValue) {
|
||||||
severity = Severity.ERROR;
|
return detail(nameAndValue, nameAndValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a detail to associate with the failure.
|
||||||
|
* @param name the logical name of the message; for example, "cause" or "recommendedAction"
|
||||||
|
* @param value the detail value, either a hard coded message string or a constraint-relative message code used to
|
||||||
|
* resolve the detail message text
|
||||||
|
* @return this, for fluent call chaining
|
||||||
|
*/
|
||||||
|
public ValidationFailureBuilder detail(String name, String value) {
|
||||||
|
if (details == null) {
|
||||||
|
details = new HashMap();
|
||||||
|
}
|
||||||
|
details.put(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +147,20 @@ public class ValidationFailureBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the ValidationFailure. Called after setting builder properties.
|
* Build the ValidationFailure. Call after setting builder properties.
|
||||||
|
* @see #forProperty(String)
|
||||||
|
* @see #constraint(String)
|
||||||
|
* @see #message(String)
|
||||||
|
* @see #detail(String)
|
||||||
|
* @see #arg(String, Object)
|
||||||
|
* @see #resolvableArg(String, String)
|
||||||
* @return this, for fluent call chaining
|
* @return this, for fluent call chaining
|
||||||
*/
|
*/
|
||||||
public ValidationFailure build() {
|
public ValidationFailure build() {
|
||||||
if (severity == null) {
|
if (severity == null) {
|
||||||
severity = Severity.ERROR;
|
severity = Severity.ERROR;
|
||||||
}
|
}
|
||||||
return new ValidationFailure(property, constraint, severity, args, defaultText);
|
return new ValidationFailure(severity, property, constraint, message, details, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
import org.springframework.binding.validation.ConfirmationConstraint.ConfirmationForm;
|
||||||
|
|
||||||
|
public class AccountRegistrationForm {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String confirmedPassword;
|
||||||
|
|
||||||
|
public void validate(ValidationContext context) {
|
||||||
|
context.setProperty("username");
|
||||||
|
context.validate(new RequiredConstraint());
|
||||||
|
context.validate(new LengthConstraint(3, 10));
|
||||||
|
|
||||||
|
context.setProperty("password");
|
||||||
|
context.validate(new RequiredConstraint());
|
||||||
|
context.validate(new LengthConstraint(6, 10));
|
||||||
|
context.validate(new PasswordStrengthConstraint());
|
||||||
|
|
||||||
|
context.setProperty("confirmedPassword");
|
||||||
|
context.validate(new ConfirmationConstraint(), new ConfirmationForm(password, confirmedPassword));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
public class ConfirmationConstraint {
|
||||||
|
|
||||||
|
public void validate(ConfirmationForm form, ValidationContext context) {
|
||||||
|
if (form.getValue() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ObjectUtils.nullSafeEquals(form.getValue(), form.getConfirmedValue())) {
|
||||||
|
context.addDefaultFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConfirmationForm {
|
||||||
|
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
private Object confirmedValue;
|
||||||
|
|
||||||
|
public ConfirmationForm(String value, String confirmedValue) {
|
||||||
|
this.value = value;
|
||||||
|
this.confirmedValue = confirmedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getConfirmedValue() {
|
||||||
|
return confirmedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfirmedValue(Object confirmedValue) {
|
||||||
|
this.confirmedValue = confirmedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
public class LengthConstraint {
|
||||||
|
|
||||||
|
private Integer min;
|
||||||
|
|
||||||
|
private Integer max;
|
||||||
|
|
||||||
|
public LengthConstraint(int min, int max) {
|
||||||
|
this.min = new Integer(min);
|
||||||
|
this.max = new Integer(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(String value, ValidationContext context) {
|
||||||
|
if (value == null || value.length() < min.intValue() || value.length() > max.intValue()) {
|
||||||
|
context.addFailure(createFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure createFailure() {
|
||||||
|
return new ValidationFailureBuilder().arg("min", getMin()).arg("max", getMax()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class PasswordStrengthConstraint {
|
||||||
|
|
||||||
|
private Pattern pattern = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]$");
|
||||||
|
|
||||||
|
public void validate(String password, ValidationContext context) {
|
||||||
|
Matcher matcher = pattern.matcher(password);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
context.addFailure(createFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ValidationFailure createFailure() {
|
||||||
|
return new ValidationFailureBuilder().warning().detail("cause").detail("recommendedAction")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.springframework.binding.validation;
|
||||||
|
|
||||||
|
public class RequiredConstraint {
|
||||||
|
|
||||||
|
public void validate(Object value, ValidationContext context) {
|
||||||
|
if (value == null) {
|
||||||
|
context.addDefaultFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -48,6 +48,22 @@ public class DefaultValidationContext implements ValidationContext {
|
|||||||
return result != null ? result.getOriginalValue() : null;
|
return result != null ? result.getOriginalValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setProperty(String property) {
|
||||||
|
throw new UnsupportedOperationException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(Object constraint, Object propertyContext) {
|
||||||
|
throw new UnsupportedOperationException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(Object constraint) {
|
||||||
|
throw new UnsupportedOperationException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDefaultFailure() {
|
||||||
|
throw new UnsupportedOperationException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
public void addFailure(final ValidationFailure failure) {
|
public void addFailure(final ValidationFailure failure) {
|
||||||
ValidationFailureModelContext modelContext = new ValidationFailureModelContext() {
|
ValidationFailureModelContext modelContext = new ValidationFailureModelContext() {
|
||||||
public String getModel() {
|
public String getModel() {
|
||||||
|
|||||||
Reference in New Issue
Block a user