web bind and lifecycle tests; polish

This commit is contained in:
Keith Donald
2009-06-17 15:56:07 +00:00
parent 62eae6d06a
commit 9368e76ffc
10 changed files with 295 additions and 53 deletions

View File

@@ -35,13 +35,18 @@ public interface BindingResult {
boolean isError();
/**
* If an error result, the error code; for example, "invalidFormat", "propertyNotFound", or "evaluationException".
* If an error result, the error code; for example, "invalidFormat" or "propertyNotFound".
*/
String getErrorCode();
/**
* If an error, result returns a default message describing what went wrong.
*/
String getErrorMessage();
/**
* If an error result, the cause of the error.
* @return the cause, or <code>null</code> if this is not an error
*/
Throwable getErrorCause();

View File

@@ -31,9 +31,12 @@ import java.util.Map;
import org.springframework.context.expression.MapAccessor;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultTypeConverter;
import org.springframework.core.style.StylerUtils;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
@@ -463,6 +466,10 @@ public class GenericBinder implements Binder {
return "invalidFormat";
}
public String getErrorMessage() {
return "Failed to bind to property '" + property + "'; the user value " + StylerUtils.style(formatted) + " has an invalid format and could no be parsed";
}
public Throwable getErrorCause() {
return e;
}
@@ -496,16 +503,33 @@ public class GenericBinder implements Binder {
public String getErrorCode() {
SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode();
if (spelCode==SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
return "typeConversionFailure";
} else if (spelCode==SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
return "propertyNotFound";
} else {
// TODO return more specific code based on underlying EvaluationException error code
return "couldNotSetValue";
return "couldNotSetValue";
}
}
public String getErrorMessage() {
SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode();
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
AccessException accessException = (AccessException) e.getCause();
if (accessException.getCause() != null) {
Throwable cause = accessException.getCause();
if (cause instanceof SpelEvaluationException && ((SpelEvaluationException)cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) {
ConversionFailedException failure = (ConversionFailedException) cause.getCause();
return "Failed to bind to property '" + property + "'; user value " + StylerUtils.style(formatted) + " could not be converted to property type [" + failure.getTargetType() + "]";
}
}
} else if (spelCode==SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
return "Failed to bind to property '" + property + "'; no such property exists on model";
}
return "Failed to bind to property '" + property + "'; reason = " + e.getLocalizedMessage();
}
public Throwable getErrorCause() {
return e;
}
@@ -538,6 +562,10 @@ public class GenericBinder implements Binder {
return null;
}
public String getErrorMessage() {
return null;
}
public Throwable getErrorCause() {
return null;
}

View File

@@ -21,9 +21,11 @@ import org.springframework.ui.binding.BindingResult;
import org.springframework.ui.binding.BindingResults;
import org.springframework.ui.binding.UserValues;
import org.springframework.ui.binding.support.WebBinder;
import org.springframework.ui.message.ResolvableArgument;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.MessageContext;
import org.springframework.ui.message.MessageResolver;
import org.springframework.ui.message.Severity;
import org.springframework.ui.validation.Validator;
/**
@@ -42,7 +44,7 @@ public class WebBindAndValidateLifecycle {
private Validator validator;
public WebBindAndValidateLifecycle(Object model, MessageContext messageContext) {
// TODO allow binder to be configured with bindings from model metadata
// TODO allow binder to be configured with bindings from @Model metadata
// TODO support @Bound property annotation?
// TODO support @StrictBinding class-level annotation?
this.binder = new WebBinder(model);
@@ -52,7 +54,7 @@ public class WebBindAndValidateLifecycle {
public void execute(Map<String, ? extends Object> userMap) {
UserValues values = binder.createUserValues(userMap);
BindingResults bindingResults = binder.bind(values);
if (validationDecider.shouldValidateAfter(bindingResults)) {
if (validator != null && validationDecider.shouldValidateAfter(bindingResults)) {
// TODO get validation results
validator.validate(binder.getModel(), bindingResults.successes().properties());
}
@@ -60,14 +62,16 @@ public class WebBindAndValidateLifecycle {
MessageBuilder builder = new MessageBuilder();
for (BindingResult result : bindingResults.failures()) {
MessageResolver message = builder.
severity(Severity.ERROR).
code(modelPropertyError(result)).
code(propertyError(result)).
code(typeError(result)).
code(error(result)).
resolvableArg("label", getModelProperty(result)).
arg("label", new ResolvableArgument(getModelProperty(result))).
arg("value", result.getUserValue()).
// TODO add binding el resolver allowing binding.format to be called
arg("binding", binder.getBinding(result.getProperty())).
defaultText(result.getErrorMessage()).
// TODO allow binding result to contribute additional arguments
build();
// TODO should model name be part of element id?

View File

@@ -20,6 +20,7 @@ import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
@@ -56,7 +57,12 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
// implementing MessageResolver
public Message resolveMessage(MessageSource messageSource, Locale locale) {
String messageString = messageSource.getMessage(this, locale);
String messageString;
try {
messageString = messageSource.getMessage(this, locale);
} catch (NoSuchMessageException e) {
throw new MessageResolutionException("Unable to resolve message in MessageSource [" + messageSource + "]", e);
}
Expression message;
try {
message = expressionParser.parseExpression(messageString, ParserContext.TEMPLATE_EXPRESSION);
@@ -70,7 +76,7 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
String text = (String) message.getValue(context);
return new TextMessage(severity, text);
} catch (EvaluationException e) {
throw new MessageResolutionException("Failed to evaluate expression to generate message text", e);
throw new MessageResolutionException("Failed to evaluate message expression '" + message.getExpressionString() + "' to generate final message text", e);
}
}
@@ -114,6 +120,7 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
}
@SuppressWarnings("unchecked")
static class MessageArgumentAccessor implements PropertyAccessor {
private MessageSource messageSource;

View File

@@ -21,8 +21,6 @@ import java.util.Map;
import java.util.Set;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -37,7 +35,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* new MessageBuilder().
* severity(Severity.ERROR).
* code("invalidFormat").
* resolvableArg("label", "mathForm.decimalField").
* arg("label", new LocalizedArgumentValue("mathForm.decimalField")).
* arg("format", "#,###.##").
* defaultText("The decimal field must be in format #,###.##").
* build();
@@ -89,27 +87,17 @@ public class MessageBuilder {
* Named message arguments are inserted by eval expressions denoted within the resolved message template.
* For example, the value of the 'format' argument would be inserted where a corresponding #{format} expression is defined in the message template.
* Successive calls to this method add additional arguments.
* May also add {@link ResolvableArgument resolvable arguments} whose values are resolved against the MessageSource passed to the {@link MessageResolver}.
* @param name the argument name
* @param value the argument value
* @return this, for fluent API usage
* @see ResolvableArgument
*/
public MessageBuilder arg(String name, Object value) {
args.put(name, value);
return this;
}
/**
* Add a message argument to insert into the message text, where the actual value to be inserted should be resolved by the {@link MessageSource}.
* Successive calls to this method add additional resolvable arguments.
* @param name the argument name
* @param code the code to use to resolve the argument value
* @return this, for fluent API usage
*/
public MessageBuilder resolvableArg(String name, Object code) {
args.put(name, new ResolvableArgumentValue(code));
return this;
}
/**
* Set the fallback text for the message.
* If the message has no codes, this will always be used as the text.
@@ -140,30 +128,4 @@ public class MessageBuilder {
return new DefaultMessageResolver(severity, codesArray, args, defaultText, expressionParser);
}
private static class ResolvableArgumentValue implements MessageSourceResolvable {
private Object code;
public ResolvableArgumentValue(Object code) {
this.code = code;
}
public Object[] getArguments() {
return null;
}
public String[] getCodes() {
return new String[] { code.toString() };
}
public String getDefaultMessage() {
return String.valueOf(code);
}
public String toString() {
return new ToStringCreator(this).append("code", code).toString();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2004-2009 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.ui.message;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.style.ToStringCreator;
/**
* A message argument value that is resolved from a MessageSource.
* Allows the value to be localized.
* @see MessageSource
* @author Keith Donald
*/
public class ResolvableArgument implements MessageSourceResolvable {
private String code;
/**
* Creates a resolvable argument.
* @param code the code that will be used to lookup the argument value from the message source
*/
public ResolvableArgument(String code) {
this.code = code;
}
public String[] getCodes() {
return new String[] { code.toString() };
}
public Object[] getArguments() {
return null;
}
public String getDefaultMessage() {
return String.valueOf(code);
}
public String toString() {
return new ToStringCreator(this).append("code", code).toString();
}
}