diff --git a/org.springframework.context/src/main/java/org/springframework/ui/alert/Alert.java b/org.springframework.context/src/main/java/org/springframework/ui/alert/Alert.java new file mode 100644 index 00000000000..12297ff3d50 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/Alert.java @@ -0,0 +1,48 @@ +/* + * 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.alert; + +/** + * Communicates an event of interest to the user. + * For example, an alert may inform a user of a web application a business rule was violated. + * TODO - should we introduce detail messages here + * @author Keith Donald + * @since 3.0 + */ +public interface Alert { + + /** + * The user interface element this alert is associated with; for example, "registration.password" + */ + public String getElement(); + + /** + * The code uniquely identifying this kind of alert; for example, "weakPassword". + * May be used as a key to lookup additional alert details. + */ + public String getCode(); + + /** + * The level of impact this alert has on the user. + */ + public Severity getSeverity(); + + /** + * The localized message to display to the user; for example, "Please enter a stronger password". + */ + public String getMessage(); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/alert/AlertContext.java b/org.springframework.context/src/main/java/org/springframework/ui/alert/AlertContext.java new file mode 100644 index 00000000000..44554463e3a --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/AlertContext.java @@ -0,0 +1,48 @@ +/* + * 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.alert; + +import java.util.List; +import java.util.Map; + +/** + * A context for adding and getting alerts for display in a user interface. + * @author Keith Donald + * @since 3.0 + */ +public interface AlertContext { + + /** + * Return all alerts in this context indexed by the UI element they are associated with. + * @return the message map + */ + public Map> getAlerts(); + + /** + * Get all alerts on the UI element provided. + * Returns an empty list if no alerts have been added for the element. + * Alerts are returned in the order they were added. + * @param element the id of the element to lookup alerts against + */ + public List getAlerts(String element); + + /** + * Add an alert to this context. + * @param alert the alert to add + */ + public void add(Alert alert); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/Severity.java b/org.springframework.context/src/main/java/org/springframework/ui/alert/Severity.java similarity index 86% rename from org.springframework.context/src/main/java/org/springframework/ui/message/Severity.java rename to org.springframework.context/src/main/java/org/springframework/ui/alert/Severity.java index 15d6abcfe19..fa58fd9a1ff 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/Severity.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/Severity.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.ui.message; +package org.springframework.ui.alert; /** - * Enum exposing supported message severities. - * + * The set of alert severities. * @author Keith Donald * @since 3.0 - * @see Message + * @see Alert */ public enum Severity { @@ -30,7 +29,7 @@ public enum Severity { INFO, /** - * The "Warning" severity. Used to indicate there is a minor problem, or to inform the message receiver of possible + * The "Warning" severity. Used to indicate there is a minor problem, or to inform the user of possible * misuse, or to indicate a problem may arise in the future. */ WARNING, @@ -41,7 +40,7 @@ public enum Severity { ERROR, /** - * The "Fatal" severity. Used to indicate a fatal problem like a system error. + * The "Fatal" severity. Used to indicate a fatal problem like a system error or runtime exception. */ FATAL diff --git a/org.springframework.context/src/main/java/org/springframework/ui/alert/package.html b/org.springframework.context/src/main/java/org/springframework/ui/alert/package.html new file mode 100644 index 00000000000..478e1080e71 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/package.html @@ -0,0 +1,7 @@ + + +

+An API for alerts to display in a user interface. +

+ + \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/alert/support/DefaultAlertContext.java b/org.springframework.context/src/main/java/org/springframework/ui/alert/support/DefaultAlertContext.java new file mode 100644 index 00000000000..e02bdb79e56 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/support/DefaultAlertContext.java @@ -0,0 +1,66 @@ +/* + * 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.alert.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.core.style.ToStringCreator; +import org.springframework.ui.alert.Alert; +import org.springframework.ui.alert.AlertContext; +import org.springframework.util.CachingMapDecorator; + +/** + * The default alert context implementation. + * @author Keith Donald + * @since 3.0 + */ +public class DefaultAlertContext implements AlertContext { + + @SuppressWarnings("serial") + private Map> alertsByElement = new CachingMapDecorator>(new LinkedHashMap>()) { + protected List create(String element) { + return new ArrayList(); + } + }; + + // implementing AlertContext + + public Map> getAlerts() { + return Collections.unmodifiableMap(alertsByElement); + } + + public List getAlerts(String element) { + List messages = alertsByElement.get(element); + if (messages.isEmpty()) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(messages); + } + + public void add(Alert alert) { + List alerts = alertsByElement.get(alert.getElement()); + alerts.add(alert); + } + + public String toString() { + return new ToStringCreator(this).append("alertsByElement", alertsByElement).toString(); + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/alert/support/package.html b/org.springframework.context/src/main/java/org/springframework/ui/alert/support/package.html new file mode 100644 index 00000000000..44fd00ef3a7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/alert/support/package.html @@ -0,0 +1,7 @@ + + +

+Support implementation of the Alert API. +

+ + \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java index 5135b38084f..c9e0957fd84 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/BindingResult.java @@ -15,6 +15,8 @@ */ package org.springframework.ui.binding; +import org.springframework.ui.alert.Alert; + /** * The result of a bind operation. * @author Keith Donald @@ -28,32 +30,24 @@ public interface BindingResult { * The name of the model property associated with this binding result. */ String getProperty(); - - /** - * Indicates if this result is an error result. - */ - boolean isError(); - - /** - * 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. - */ - Throwable getErrorCause(); /** * The raw user-entered value for which binding was attempted. - * If not an error result, this value was successfully bound to the model. + * If not a failure, this value was successfully bound to the model. + * @see #isFailure() */ Object getUserValue(); + + /** + * Indicates if the binding failed. + */ + boolean isFailure(); + + /** + * Gets the alert for this binding result, appropriate for rendering the result to the user. + * An alert describing a successful binding will have info severity. + * An alert describing a failed binding will have either warning, error, or fatal severity. + */ + Alert getAlert(); } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Bound.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Bound.java new file mode 100644 index 00000000000..120af93adc8 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Bound.java @@ -0,0 +1,14 @@ +package org.springframework.ui.binding; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Bound { + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/Model.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/Model.java new file mode 100644 index 00000000000..dde5a9df323 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/Model.java @@ -0,0 +1,25 @@ +package org.springframework.ui.binding; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Model { + + /** + * The name of the model + */ + String value() default ""; + + /** + * Configures strict model binding. + * @see Binder#setStrict(boolean) + */ + boolean strict() default false; + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java index f5b9ebf39d4..c964f4319c0 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/binding/support/GenericBinder.java @@ -48,6 +48,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; +import org.springframework.ui.alert.Alert; +import org.springframework.ui.alert.Severity; import org.springframework.ui.binding.Binder; import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.BindingConfiguration; @@ -58,9 +60,11 @@ import org.springframework.ui.binding.UserValue; import org.springframework.ui.binding.UserValues; import org.springframework.ui.format.AnnotationFormatterFactory; import org.springframework.ui.format.Formatter; +import org.springframework.util.Assert; /** * A generic {@link Binder binder} suitable for use in most environments. + * TODO - localization of alert messages using MessageResolver/MesageSource * @author Keith Donald * @since 3.0 * @see #add(BindingConfiguration) @@ -90,6 +94,7 @@ public class GenericBinder implements Binder { * @param model the model object containing properties this binder will bind to */ public GenericBinder(Object model) { + Assert.notNull(model, "The model Object is reqyured"); this.model = model; bindings = new HashMap(); int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull @@ -106,6 +111,11 @@ public class GenericBinder implements Binder { this.strict = strict; } + public void setFormatterRegistry(FormatterRegistry formatterRegistry) { + Assert.notNull(formatterRegistry, "The FormatterRegistry is required"); + this.formatterRegistry = formatterRegistry; + } + public Binding add(BindingConfiguration binding) { Binding newBinding; try { @@ -156,11 +166,11 @@ public class GenericBinder implements Binder { static class ArrayListBindingResults implements BindingResults { private List results; - + public ArrayListBindingResults() { results = new ArrayList(); } - + public ArrayListBindingResults(int size) { results = new ArrayList(size); } @@ -170,27 +180,27 @@ public class GenericBinder implements Binder { } // implementing Iterable - + public Iterator iterator() { return results.iterator(); } // implementing BindingResults - + public BindingResults successes() { ArrayListBindingResults results = new ArrayListBindingResults(); for (BindingResult result : this) { - if (!result.isError()) { + if (!result.isFailure()) { results.add(result); } } return results; } - + public BindingResults failures() { ArrayListBindingResults results = new ArrayListBindingResults(); for (BindingResult result : this) { - if (result.isError()) { + if (result.isFailure()) { results.add(result); } } @@ -212,10 +222,9 @@ public class GenericBinder implements Binder { public int size() { return results.size(); } - + } - - + class BindingImpl implements Binding { private Expression property; @@ -446,39 +455,47 @@ public class GenericBinder implements Binder { private Object formatted; - private ParseException e; - public InvalidFormatResult(String property, Object formatted, ParseException e) { this.property = property; this.formatted = formatted; - this.e = e; } public String getProperty() { return property; } - public boolean isError() { - return true; - } - - public String getErrorCode() { - 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; - } - public Object getUserValue() { return formatted; } + + public boolean isFailure() { + return true; + } + + public Alert getAlert() { + return new Alert() { + public String getElement() { + // TODO append model first? e.g. model.property + return getProperty(); + } + + public String getCode() { + return "invalidFormat"; + } + + public Severity getSeverity() { + return Severity.ERROR; + } + + public String getMessage() { + return "Failed to bind to property '" + property + "'; the user value " + + StylerUtils.style(formatted) + " has an invalid format and could no be parsed"; + } + }; + } } + // TODO the if branching in here is not very clean static class ExpressionEvaluationErrorResult implements BindingResult { private String property; @@ -497,45 +514,76 @@ public class GenericBinder implements Binder { return property; } - public boolean isError() { + public Object getUserValue() { + return formatted; + } + + public boolean isFailure() { return true; } - public String getErrorCode() { - SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode(); - 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 + public Alert getAlert() { + return new Alert() { + public String getElement() { + // TODO append model first? e.g. model.property + return getProperty(); + } + + public String getCode() { + return getFailureCode(); + } + + public Severity getSeverity() { + return getFailureSeverity(); + } + + public String getMessage() { + return getFailureMessage(); + } + }; + } + + public String getFailureCode() { + SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode(); + 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"; - } + } + } + + public Severity getFailureSeverity() { + SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode(); + if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) { + return Severity.FATAL; + } else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) { + return Severity.WARNING; + } else { + return Severity.FATAL; + } } - 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; - } - - public Object getUserValue() { - return formatted; + public String getFailureMessage() { + 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().getName() + "]"; + } + } + } 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(); } } @@ -553,25 +601,35 @@ public class GenericBinder implements Binder { public String getProperty() { return property; } - - public boolean isError() { - return false; - } - - public String getErrorCode() { - return null; - } - - public String getErrorMessage() { - return null; - } - public Throwable getErrorCause() { - return null; - } - public Object getUserValue() { return formatted; } + + public boolean isFailure() { + return false; + } + + public Alert getAlert() { + return new Alert() { + public String getElement() { + // TODO append model first? e.g. model.property + return getProperty(); + } + + public String getCode() { + return "bindSuccess"; + } + + public Severity getSeverity() { + return Severity.INFO; + } + + public String getMessage() { + return "Sucessfully bound user value " + StylerUtils.style(formatted) + "to property '" + property + "'"; + } + }; + } + } } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/lifecycle/WebBindAndValidateLifecycle.java b/org.springframework.context/src/main/java/org/springframework/ui/lifecycle/WebBindAndValidateLifecycle.java index e58a90a1312..235141e0b1e 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/lifecycle/WebBindAndValidateLifecycle.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/lifecycle/WebBindAndValidateLifecycle.java @@ -17,15 +17,12 @@ package org.springframework.ui.lifecycle; import java.util.Map; +import org.springframework.ui.alert.AlertContext; import org.springframework.ui.binding.BindingResult; import org.springframework.ui.binding.BindingResults; +import org.springframework.ui.binding.FormatterRegistry; 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; /** @@ -37,18 +34,22 @@ public class WebBindAndValidateLifecycle { private final WebBinder binder; - private final MessageContext messageContext; + private final AlertContext alertContext; private ValidationDecider validationDecider = ValidationDecider.ALWAYS_VALIDATE; private Validator validator; - public WebBindAndValidateLifecycle(Object model, MessageContext messageContext) { + public WebBindAndValidateLifecycle(Object model, AlertContext alertContext) { // 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); - this.messageContext = messageContext; + this.alertContext = alertContext; + } + + public void setFormatterRegistry(FormatterRegistry registry) { + binder.setFormatterRegistry(registry); } public void execute(Map userMap) { @@ -58,54 +59,12 @@ public class WebBindAndValidateLifecycle { // TODO get validation results validator.validate(binder.getModel(), bindingResults.successes().properties()); } - // TODO make message translation pluggable - 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)). - 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? - messageContext.add(message, result.getProperty()); + alertContext.add(result.getAlert()); } // TODO translate validation results into messages } - private String modelPropertyError(BindingResult result) { - return getModelProperty(result) + "." + result.getErrorCode(); - } - - private String propertyError(BindingResult result) { - return result.getProperty() + "." + result.getErrorCode(); - } - - private String typeError(BindingResult result) { - return binder.getBinding(result.getProperty()).getType().getName() + "." + result.getErrorCode(); - } - - private String error(BindingResult result) { - return result.getErrorCode(); - } - - private String getModelProperty(BindingResult result) { - return getModel() + "." + result.getProperty(); - } - - private String getModel() { - // TODO would be nice if model name was module.ClassName by default where module is subpackage of app base package - // TODO model name should probably be specifiable using class-level annotation - return binder.getModel().getClass().getName(); - } - interface ValidationDecider { boolean shouldValidateAfter(BindingResults results); @@ -116,4 +75,5 @@ public class WebBindAndValidateLifecycle { } }; } + } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/DefaultMessageResolver.java b/org.springframework.context/src/main/java/org/springframework/ui/message/DefaultMessageResolver.java index 5d36cd99226..f71342cb186 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/DefaultMessageResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/message/DefaultMessageResolver.java @@ -35,8 +35,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; final class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable { - private Severity severity; - private String[] codes; private Map args; @@ -45,9 +43,8 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso private ExpressionParser expressionParser; - public DefaultMessageResolver(Severity severity, String[] codes, Map args, String defaultText, + public DefaultMessageResolver(String[] codes, Map args, String defaultText, ExpressionParser expressionParser) { - this.severity = severity; this.codes = codes; this.args = args; this.defaultText = defaultText; @@ -56,8 +53,8 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso // implementing MessageResolver - public Message resolveMessage(MessageSource messageSource, Locale locale) { - String messageString; + public String resolveMessage(MessageSource messageSource, Locale locale) { + String messageString; try { messageString = messageSource.getMessage(this, locale); } catch (NoSuchMessageException e) { @@ -73,10 +70,10 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso StandardEvaluationContext context = new StandardEvaluationContext(); context.setRootObject(args); context.addPropertyAccessor(new MessageArgumentAccessor(messageSource, locale)); - String text = (String) message.getValue(context); - return new TextMessage(severity, text); + return (String) message.getValue(context); } catch (EvaluationException e) { - throw new MessageResolutionException("Failed to evaluate message expression '" + message.getExpressionString() + "' to generate final message text", e); + throw new MessageResolutionException("Failed to evaluate message expression '" + + message.getExpressionString() + "' to generate final message text", e); } } @@ -95,36 +92,14 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso } public String toString() { - return new ToStringCreator(this).append("severity", severity).append("codes", codes).append("defaultText", - defaultText).toString(); - } - - private static class TextMessage implements Message { - - private Severity severity; - - private String text; - - public TextMessage(Severity severity, String text) { - this.severity = severity; - this.text = text; - } - - public Severity getSeverity() { - return severity; - } - - public String getText() { - return text; - } - + return new ToStringCreator(this).append("codes", codes).append("defaultText", defaultText).toString(); } @SuppressWarnings("unchecked") static class MessageArgumentAccessor implements PropertyAccessor { private MessageSource messageSource; - + private Locale locale; public MessageArgumentAccessor(MessageSource messageSource, Locale locale) { @@ -133,11 +108,16 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso } public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { - return (((Map) target).containsKey(name)); + return true; } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - Object o = ((Map) target).get(name); + Map map = (Map) target; + Object o = map.get(name); + if (o == null) { + throw new AccessException("No message argument named '" + name + + "' is defined in the argument map; arguments available are " + map.keySet(), null); + } if (o instanceof MessageSourceResolvable) { String message = messageSource.getMessage((MessageSourceResolvable) o, locale); return new TypedValue(message); @@ -145,7 +125,7 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso return new TypedValue(o); } } - + public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { return false; } @@ -154,7 +134,7 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso throws AccessException { throw new UnsupportedOperationException("Should not be called"); } - + public Class[] getSpecificTargetClasses() { return new Class[] { Map.class }; } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/Message.java b/org.springframework.context/src/main/java/org/springframework/ui/message/Message.java deleted file mode 100644 index 5eb465978d6..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/Message.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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; - -/** - * Communicates information of interest to the user. - * For example, a error message may inform a user of a web application a business rule was violated. - * TODO - should we introduce summary/detail fields instead of just text - * @author Keith Donald - * @since 3.0 - */ -public interface Message { - - /** - * The severity of this message. - * The severity indicates the intensity or priority of the message. - * @return the message severity - */ - public Severity getSeverity(); - - /** - * The message text. - * The text is the message's communication payload. - * @return the message text - */ - public String getText(); - -} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageContext.java b/org.springframework.context/src/main/java/org/springframework/ui/message/MessageContext.java deleted file mode 100644 index 47f92c5f5a2..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageContext.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 java.util.List; -import java.util.Map; - -/** - * A context for recording and retrieving messages for display in a user interface. - * @author Keith Donald - * @since 3.0 - */ -public interface MessageContext { - - /** - * Return all messages in this context indexed by the UI element they are associated with. - * @return the message map - */ - public Map> getMessages(); - - /** - * Get all messages on the UI element provided. - * Returns an empty list if no messages have been recorded against the element. - * Messages are returned in the order they were added. - * @param element the id of the element to lookup messages against - */ - public List getMessages(String element); - - /** - * Add a new message to an element. - * @param message the resolver that will resolve the message to be added; typically constructed by a {@link MessageBuilder}. - * @param element the id of the UI element the message should be associated with - */ - public void add(MessageResolver message, String element); - -} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolver.java b/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolver.java index 6746878e105..d628c5a8c1d 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolver.java @@ -20,10 +20,10 @@ import java.util.Locale; import org.springframework.context.MessageSource; /** - * A factory for a localized Message. + * A factory for a localized message. + * TODO - consider putting this abstraction together with MessageSource; does it need to be in its own package? * @author Keith Donald * @since 3.0 - * @see Message * @see MessageSource */ public interface MessageResolver { @@ -35,5 +35,5 @@ public interface MessageResolver { * @return the resolved message * @throws MessageResolutionException if a resolution failure occurs */ - public Message resolveMessage(MessageSource messageSource, Locale locale); + public String resolveMessage(MessageSource messageSource, Locale locale); } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageBuilder.java b/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolverBuilder.java similarity index 54% rename from org.springframework.context/src/main/java/org/springframework/ui/message/MessageBuilder.java rename to org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolverBuilder.java index eb6e39ca42e..f67eb381a3f 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/MessageBuilder.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/message/MessageResolverBuilder.java @@ -20,25 +20,23 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.springframework.context.MessageSource; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; /** - * A builder for building {@link MessageResolver} objects. - * Typically used by Controllers to {@link MessageContext#add(MessageResolver, String) add} messages to display in a user interface. - * Supports MessageResolvers that hard-code the message text, as well as MessageResolvers that resolve the message text from a localized {@link MessageSource}. - * Also supports named arguments whose values can be inserted into messages using #{eval expressions}. + * Builds a {@link MessageResolver} that can resolve a localized message for display in a user interface. + * Allows convenient specification of the codes to try to resolve the message. + * Also supports named arguments that can inserted into a message template using eval #{expressions}. *

* Usage example: *

- * new MessageBuilder().
- *     severity(Severity.ERROR).
+ * MessageResolver resolver = new MessageResolverBuilder().
  *     code("invalidFormat").
- *     arg("label", new LocalizedArgumentValue("mathForm.decimalField")).
+ *     arg("label", new ResolvableArgument("mathForm.decimalField")).
  *     arg("format", "#,###.##").
- *     defaultText("The decimal field must be in format #,###.##").
+ *     defaultMessage("The decimal field must be in format #,###.##").
  *     build();
+ * String message = resolver.resolveMessage(messageSource, locale);
  * 
* Example messages.properties loaded by the MessageSource: *
@@ -46,86 +44,75 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
  * mathForm.decimalField=Decimal Field
  * 
* @author Keith Donald - * @since 3.0 - * @see MessageContext#add(MessageResolver, String) + * @since 3.0 + * @see #code(String) + * @see #arg(String, Object) + * @see #defaultMessage(String) */ -public class MessageBuilder { +public class MessageResolverBuilder { - private Severity severity; - private Set codes = new LinkedHashSet(); private Map args = new LinkedHashMap(); - private String defaultText; + private String defaultMessage; private ExpressionParser expressionParser = new SpelExpressionParser(); /** - * Set the severity of the message. - * @return this, for fluent API usage - */ - public MessageBuilder severity(Severity severity) { - this.severity = severity; - return this; - } - - /** - * Add a code to use to resolve the template for generating the localized message text. + * Add a code that will be tried to lookup the message template used to create the localized message. * Successive calls to this method add additional codes. * Codes are tried in the order they are added. - * @param code the message code + * @param code a message code to try * @return this, for fluent API usage */ - public MessageBuilder code(String code) { + public MessageResolverBuilder code(String code) { codes.add(code); return this; } /** - * Add a message argument to insert into the message text. - * Named message arguments are inserted by eval expressions denoted within the resolved message template. + * Add an argument to insert into the message. + * Named arguments are inserted by eval #{expressions} denoted within the 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}. + * May also add {@link ResolvableArgument resolvable arguments} whose values are resolved against the MessageSource passed to + * {@link MessageResolver#resolveMessage(org.springframework.context.MessageSource, java.util.Locale)}. * @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) { + public MessageResolverBuilder arg(String name, Object value) { args.put(name, value); return this; } /** - * Set the fallback text for the message. - * If the message has no codes, this will always be used as the text. - * If the message has codes but none can be resolved, this will always be used as the text. - * @param text the default text + * Set the default message. + * If the MessageResolver has no codes to try, this will be used as the message. + * If the MessageResolver has codes to try but none of those resolve to a message, this will be used as the message. + * @param message the default text * @return this, for fluent API usage */ - public MessageBuilder defaultText(String text) { - defaultText = text; + public MessageResolverBuilder defaultMessage(String message) { + defaultMessage = message; return this; } /** - * Builds the message that will be resolved. + * Builds the resolver for the message. * Call after recording all builder instructions. * @return the built message resolver - * @throws Illegal + * @throws IllegalStateException if no codes have been added and there is no default message */ public MessageResolver build() { - if (severity == null) { - severity = Severity.INFO; - } - if (codes == null && defaultText == null) { + if (codes == null && defaultMessage == null) { throw new IllegalStateException( "A message code or the message text is required to build this message resolver"); } String[] codesArray = (String[]) codes.toArray(new String[codes.size()]); - return new DefaultMessageResolver(severity, codesArray, args, defaultText, expressionParser); + return new DefaultMessageResolver(codesArray, args, defaultMessage, expressionParser); } } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/package.html b/org.springframework.context/src/main/java/org/springframework/ui/message/package.html index e13cbb51fec..8e7dcdd2311 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/package.html +++ b/org.springframework.context/src/main/java/org/springframework/ui/message/package.html @@ -1,7 +1,7 @@

-An API for creating and managing localized messages to display in a UI. +An API for creating localized messages to display in a user interface.

\ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/support/DefaultMessageContext.java b/org.springframework.context/src/main/java/org/springframework/ui/message/support/DefaultMessageContext.java deleted file mode 100644 index 50b975a0b30..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/support/DefaultMessageContext.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.support; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.context.MessageSource; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.style.ToStringCreator; -import org.springframework.ui.message.Message; -import org.springframework.ui.message.MessageContext; -import org.springframework.ui.message.MessageResolver; -import org.springframework.ui.message.Severity; -import org.springframework.util.Assert; -import org.springframework.util.CachingMapDecorator; - -/** - * The default message context implementation. - * Uses a {@link MessageSource} to resolve messages that are added by callers. - * @author Keith Donald - * @since 3.0 - */ -public class DefaultMessageContext implements MessageContext { - - private final MessageSource messageSource; - - @SuppressWarnings("serial") - private Map> messagesByElement = new CachingMapDecorator>(new LinkedHashMap>()) { - protected List create(String element) { - return new ArrayList(); - } - }; - - /** - * Creates a new default message context. - * @param messageSource the message source to resolve messages added to this context - */ - public DefaultMessageContext(MessageSource messageSource) { - Assert.notNull(messageSource, "The MessageSource is required"); - this.messageSource = messageSource; - } - - /** - * The message source configured to resolve message text. - * @return the message source - */ - public MessageSource getMessageSource() { - return messageSource; - } - - // implementing message context - - public Map> getMessages() { - return Collections.unmodifiableMap(messagesByElement); - } - - public List getMessages(String element) { - List messages = messagesByElement.get(element); - if (messages.isEmpty()) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(messages); - } - - public void add(MessageResolver messageResolver, String element) { - List messages = messagesByElement.get(element); - messages.add(new ResolvableMessage(messageResolver)); - } - - public String toString() { - return new ToStringCreator(this).append("messagesByElement", messagesByElement).toString(); - } - - // internal helpers - - class ResolvableMessage implements Message { - - private MessageResolver resolver; - - private Message resolvedMessage; - - public ResolvableMessage(MessageResolver resolver) { - this.resolver = resolver; - } - - public Severity getSeverity() { - return getMessage().getSeverity(); - } - - public String getText() { - return getMessage().getText(); - } - - public Message getMessage() { - if (resolvedMessage == null) { - resolvedMessage = resolver.resolveMessage(messageSource, LocaleContextHolder.getLocale()); - } - return resolvedMessage; - } - } -} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/ui/message/support/package.html b/org.springframework.context/src/main/java/org/springframework/ui/message/support/package.html deleted file mode 100644 index e52c72dae17..00000000000 --- a/org.springframework.context/src/main/java/org/springframework/ui/message/support/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -

-Support implementation of the MessageContext API. -

- - \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/ui/alert/DefaultMessageContextTests.java b/org.springframework.context/src/test/java/org/springframework/ui/alert/DefaultMessageContextTests.java new file mode 100644 index 00000000000..41ced47d0f2 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/alert/DefaultMessageContextTests.java @@ -0,0 +1,43 @@ +package org.springframework.ui.alert; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ui.alert.Alert; +import org.springframework.ui.alert.Severity; +import org.springframework.ui.alert.support.DefaultAlertContext; + +public class DefaultMessageContextTests { + + private DefaultAlertContext context; + + @Before + public void setUp() { + context = new DefaultAlertContext(); + } + + @Test + public void addAlert() { + Alert alert = new Alert() { + public String getElement() { + return "form.property"; + } + + public String getCode() { + return "invalidFormat"; + } + + public String getMessage() { + return "Please enter a value in format yyy-dd-mm"; + } + + public Severity getSeverity() { + return Severity.ERROR; + } + }; + context.add(alert); + assertEquals(1, context.getAlerts().size()); + assertEquals("invalidFormat", context.getAlerts("form.property").get(0).getCode()); + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java index 4fa62d94e9e..8c2c7aa231c 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/binding/support/GenericBinderTests.java @@ -19,7 +19,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.expression.EvaluationException; import org.springframework.ui.binding.Binder; import org.springframework.ui.binding.Binding; import org.springframework.ui.binding.BindingConfiguration; @@ -61,18 +60,15 @@ public class GenericBinderTests { assertEquals(3, results.size()); assertEquals("string", results.get(0).getProperty()); - assertFalse(results.get(0).isError()); - assertNull(results.get(0).getErrorCause()); + assertFalse(results.get(0).isFailure()); assertEquals("test", results.get(0).getUserValue()); assertEquals("integer", results.get(1).getProperty()); - assertFalse(results.get(1).isError()); - assertNull(results.get(1).getErrorCause()); + assertFalse(results.get(1).isFailure()); assertEquals("3", results.get(1).getUserValue()); assertEquals("foo", results.get(2).getProperty()); - assertFalse(results.get(2).isError()); - assertNull(results.get(2).getErrorCause()); + assertFalse(results.get(2).isFailure()); assertEquals("BAR", results.get(2).getUserValue()); assertEquals("test", bean.getString()); @@ -89,8 +85,8 @@ public class GenericBinderTests { values.add("foo", "BAR"); BindingResults results = binder.bind(values); assertEquals(3, results.size()); - assertTrue(results.get(1).isError()); - assertEquals("typeConversionFailure", results.get(1).getErrorCode()); + assertTrue(results.get(1).isFailure()); + assertEquals("typeConversionFailure", results.get(1).getAlert().getCode()); } @Test @@ -131,8 +127,8 @@ public class GenericBinderTests { public void bindSingleValuePropertyNotFound() throws ParseException { BindingResults results = binder.bind(UserValues.single("bogus", "2009-06-01")); assertEquals(1, results.size()); - assertTrue(results.get(0).isError()); - assertEquals("propertyNotFound", results.get(0).getErrorCode()); + assertTrue(results.get(0).isFailure()); + assertEquals("propertyNotFound", results.get(0).getAlert().getCode()); } @Test @@ -156,7 +152,7 @@ public class GenericBinderTests { assertEquals("0", b.getValue()); BindingResult result = b.setValue("5"); assertEquals("5", b.getValue()); - assertFalse(result.isError()); + assertFalse(result.isFailure()); } @Test @@ -170,7 +166,7 @@ public class GenericBinderTests { assertEquals("0", b.getValue()); BindingResult result = b.setValue("5"); assertEquals("5", b.getValue()); - assertFalse(result.isError()); + assertFalse(result.isFailure()); } @Test @@ -214,9 +210,8 @@ public class GenericBinderTests { assertTrue(b.isCollection()); assertEquals(0, b.getCollectionValues().length); BindingResult result = b.setValue(new String[] { "BAR", "BOGUS", "BOOP" }); - assertTrue(result.isError()); - assertTrue(result.getErrorCause() instanceof EvaluationException); - assertEquals("typeConversionFailure", result.getErrorCode()); + assertTrue(result.isFailure()); + assertEquals("typeConversionFailure", result.getAlert().getCode()); } @Test diff --git a/org.springframework.context/src/test/java/org/springframework/ui/lifecycle/WebBindAndLifecycleTests.java b/org.springframework.context/src/test/java/org/springframework/ui/lifecycle/WebBindAndLifecycleTests.java index 2039dede44f..6575bc83ca9 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/lifecycle/WebBindAndLifecycleTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/lifecycle/WebBindAndLifecycleTests.java @@ -6,38 +6,29 @@ import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import org.springframework.ui.alert.Severity; +import org.springframework.ui.alert.support.DefaultAlertContext; +import org.springframework.ui.binding.support.GenericFormatterRegistry; import org.springframework.ui.format.number.CurrencyFormat; -import org.springframework.ui.message.MockMessageSource; -import org.springframework.ui.message.Severity; -import org.springframework.ui.message.support.DefaultMessageContext; +import org.springframework.ui.format.number.IntegerFormatter; public class WebBindAndLifecycleTests { private WebBindAndValidateLifecycle lifecycle; - private DefaultMessageContext messages; + private TestBean model; + + private DefaultAlertContext alertContext; @Before public void setUp() { - MockMessageSource messageSource = new MockMessageSource(); - messageSource - .addMessage( - "invalidFormat", - Locale.US, - "#{label} must be a ${objectType} in format #{format}; parsing of your value '#{value}' failed at the #{errorPosition} character"); - messageSource.addMessage("typeConversionFailure", Locale.US, - "The value '#{value}' entered into the #{label} field could not be converted"); - messageSource.addMessage("org.springframework.ui.lifecycle.WebBindAndLifecycleTests$TestBean.integer", - Locale.US, "Integer"); - messages = new DefaultMessageContext(messageSource); - TestBean model = new TestBean(); - lifecycle = new WebBindAndValidateLifecycle(model, messages); + model = new TestBean(); + alertContext = new DefaultAlertContext(); + lifecycle = new WebBindAndValidateLifecycle(model, alertContext); } @Test @@ -47,10 +38,9 @@ public class WebBindAndLifecycleTests { userMap.put("integer", "3"); userMap.put("foo", "BAR"); lifecycle.execute(userMap); - assertEquals(0, messages.getMessages().size()); + assertEquals(0, alertContext.getAlerts().size()); } - @Ignore("Disabled test until it passes consistently in the Ant build on Mac OS X") @Test public void testExecuteLifecycleBindingErrors() { Map userMap = new HashMap(); @@ -58,10 +48,24 @@ public class WebBindAndLifecycleTests { userMap.put("integer", "bogus"); userMap.put("foo", "BAR"); lifecycle.execute(userMap); - assertEquals(1, messages.getMessages().size()); - assertEquals(Severity.ERROR, messages.getMessages("integer").get(0).getSeverity()); - assertEquals("The value 'bogus' entered into the Integer field could not be converted", messages.getMessages( - "integer").get(0).getText()); + assertEquals(1, alertContext.getAlerts().size()); + assertEquals(Severity.FATAL, alertContext.getAlerts("integer").get(0).getSeverity()); + assertEquals("Failed to bind to property 'integer'; user value 'bogus' could not be converted to property type [java.lang.Integer]", alertContext.getAlerts("integer").get(0).getMessage()); + } + + @Test + public void testExecuteLifecycleInvalidFormatBindingErrors() { + Map userMap = new HashMap(); + GenericFormatterRegistry registry = new GenericFormatterRegistry(); + registry.add(new IntegerFormatter(), Integer.class); + lifecycle.setFormatterRegistry(registry); + userMap.put("string", "test"); + userMap.put("integer", "bogus"); + userMap.put("foo", "BAR"); + lifecycle.execute(userMap); + assertEquals(1, alertContext.getAlerts().size()); + assertEquals(Severity.ERROR, alertContext.getAlerts("integer").get(0).getSeverity()); + assertEquals("Failed to bind to property 'integer'; the user value 'bogus' has an invalid format and could no be parsed", alertContext.getAlerts("integer").get(0).getMessage()); } public static enum FooEnum { diff --git a/org.springframework.context/src/test/java/org/springframework/ui/message/MessageBuilderTests.java b/org.springframework.context/src/test/java/org/springframework/ui/message/MessageResolverBuilderTests.java similarity index 50% rename from org.springframework.context/src/test/java/org/springframework/ui/message/MessageBuilderTests.java rename to org.springframework.context/src/test/java/org/springframework/ui/message/MessageResolverBuilderTests.java index d9273709bbb..f8c4a9af821 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/message/MessageBuilderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/message/MessageResolverBuilderTests.java @@ -6,19 +6,18 @@ import java.util.Locale; import org.junit.Test; -public class MessageBuilderTests { +public class MessageResolverBuilderTests { - private MessageBuilder builder = new MessageBuilder(); + private MessageResolverBuilder builder = new MessageResolverBuilder(); @Test public void buildMessage() { - MessageResolver resolver = builder.severity(Severity.ERROR).code("invalidFormat").arg("label", new ResolvableArgument("mathForm.decimalField")) - .arg("format", "#,###.##").defaultText("Field must be in format #,###.##").build(); + MessageResolver resolver = builder.code("invalidFormat").arg("label", new ResolvableArgument("mathForm.decimalField")) + .arg("format", "#,###.##").defaultMessage("Field must be in format #,###.##").build(); MockMessageSource messageSource = new MockMessageSource(); messageSource.addMessage("invalidFormat", Locale.US, "#{label} must be in format #{format}"); messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field"); - Message message = resolver.resolveMessage(messageSource, Locale.US); - assertEquals(Severity.ERROR, message.getSeverity()); - assertEquals("Decimal Field must be in format #,###.##", message.getText()); + String message = resolver.resolveMessage(messageSource, Locale.US); + assertEquals("Decimal Field must be in format #,###.##", message); } } diff --git a/org.springframework.context/src/test/java/org/springframework/ui/message/MockMessageSource.java b/org.springframework.context/src/test/java/org/springframework/ui/message/MockMessageSource.java index 6327bd1e500..87d8d2b851b 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/message/MockMessageSource.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/message/MockMessageSource.java @@ -8,7 +8,7 @@ import java.util.Map; import org.springframework.context.support.AbstractMessageSource; import org.springframework.util.Assert; -public class MockMessageSource extends AbstractMessageSource { +class MockMessageSource extends AbstractMessageSource { /** Map from 'code + locale' keys to message Strings */ private final Map messages = new HashMap(); diff --git a/org.springframework.context/src/test/java/org/springframework/ui/message/support/DefaultMessageContextTests.java b/org.springframework.context/src/test/java/org/springframework/ui/message/support/DefaultMessageContextTests.java deleted file mode 100644 index 474cbcd7c94..00000000000 --- a/org.springframework.context/src/test/java/org/springframework/ui/message/support/DefaultMessageContextTests.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.springframework.ui.message.support; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.ui.message.ResolvableArgument; -import org.springframework.ui.message.Message; -import org.springframework.ui.message.MessageBuilder; -import org.springframework.ui.message.MessageResolver; -import org.springframework.ui.message.MockMessageSource; -import org.springframework.ui.message.Severity; - -public class DefaultMessageContextTests { - - private DefaultMessageContext context; - - @Before - public void setUp() { - MockMessageSource messageSource = new MockMessageSource(); - messageSource.addMessage("invalidFormat", Locale.US, "#{label} must be in format #{format}"); - messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field"); - context = new DefaultMessageContext(messageSource); - LocaleContextHolder.setLocale(Locale.US); - } - - @After - public void tearDown() { - LocaleContextHolder.setLocale(null); - } - - @Test - public void addMessage() { - MessageBuilder builder = new MessageBuilder(); - MessageResolver message = builder.severity(Severity.ERROR).code("invalidFormat").arg("label", new ResolvableArgument("mathForm.decimalField")). - arg("format", "#,###.##").defaultText("Field must be in format #,###.##").build(); - context.add(message, "mathForm.decimalField"); - Map> messages = context.getMessages(); - assertEquals(1, messages.size()); - assertEquals("Decimal Field must be in format #,###.##", messages.get("mathForm.decimalField").get(0).getText()); - assertEquals("Decimal Field must be in format #,###.##", context.getMessages("mathForm.decimalField").get(0).getText()); - } -}