factored out alert from message; made binding responsible for alert generation
This commit is contained in:
parent
f749eacbc2
commit
4ea373b7dd
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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<String, List<Alert>> 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<Alert> getAlerts(String element);
|
||||
|
||||
/**
|
||||
* Add an alert to this context.
|
||||
* @param alert the alert to add
|
||||
*/
|
||||
public void add(Alert alert);
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>
|
||||
An API for alerts to display in a user interface.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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<String, List<Alert>> alertsByElement = new CachingMapDecorator<String, List<Alert>>(new LinkedHashMap<String, List<Alert>>()) {
|
||||
protected List<Alert> create(String element) {
|
||||
return new ArrayList<Alert>();
|
||||
}
|
||||
};
|
||||
|
||||
// implementing AlertContext
|
||||
|
||||
public Map<String, List<Alert>> getAlerts() {
|
||||
return Collections.unmodifiableMap(alertsByElement);
|
||||
}
|
||||
|
||||
public List<Alert> getAlerts(String element) {
|
||||
List<Alert> messages = alertsByElement.get(element);
|
||||
if (messages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
public void add(Alert alert) {
|
||||
List<Alert> alerts = alertsByElement.get(alert.getElement());
|
||||
alerts.add(alert);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("alertsByElement", alertsByElement).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Support implementation of the Alert API.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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<String, Binding>();
|
||||
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<BindingResult> results;
|
||||
|
||||
|
||||
public ArrayListBindingResults() {
|
||||
results = new ArrayList<BindingResult>();
|
||||
}
|
||||
|
||||
|
||||
public ArrayListBindingResults(int size) {
|
||||
results = new ArrayList<BindingResult>(size);
|
||||
}
|
||||
|
|
@ -170,27 +180,27 @@ public class GenericBinder implements Binder {
|
|||
}
|
||||
|
||||
// implementing Iterable
|
||||
|
||||
|
||||
public Iterator<BindingResult> 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 + "'";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, ? extends Object> 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 {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Object> args;
|
||||
|
|
@ -45,9 +43,8 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
|
|||
|
||||
private ExpressionParser expressionParser;
|
||||
|
||||
public DefaultMessageResolver(Severity severity, String[] codes, Map<String, Object> args, String defaultText,
|
||||
public DefaultMessageResolver(String[] codes, Map<String, Object> 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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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<String, List<Message>> 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<Message> 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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
* <p>
|
||||
* Usage example:
|
||||
* <pre>
|
||||
* 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);
|
||||
* </pre>
|
||||
* Example messages.properties loaded by the MessageSource:
|
||||
* <pre>
|
||||
|
|
@ -46,86 +44,75 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|||
* mathForm.decimalField=Decimal Field
|
||||
* </pre>
|
||||
* @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<String> codes = new LinkedHashSet<String>();
|
||||
|
||||
private Map<String, Object> args = new LinkedHashMap<String, Object>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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<String, List<Message>> messagesByElement = new CachingMapDecorator<String, List<Message>>(new LinkedHashMap<String, List<Message>>()) {
|
||||
protected List<Message> create(String element) {
|
||||
return new ArrayList<Message>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<String, List<Message>> getMessages() {
|
||||
return Collections.unmodifiableMap(messagesByElement);
|
||||
}
|
||||
|
||||
public List<Message> getMessages(String element) {
|
||||
List<Message> messages = messagesByElement.get(element);
|
||||
if (messages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
public void add(MessageResolver messageResolver, String element) {
|
||||
List<Message> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Support implementation of the MessageContext API.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String, Object> userMap = new HashMap<String, Object>();
|
||||
|
|
@ -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<String, Object> userMap = new HashMap<String, Object>();
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, String> messages = new HashMap<String, String>();
|
||||
|
|
|
|||
|
|
@ -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<String, List<Message>> 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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue