message builder
This commit is contained in:
parent
05e3c00a98
commit
3f5c43aaf5
|
|
@ -79,7 +79,7 @@ public interface Binder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind source values in the map to the properties of the model object.
|
* Bind source values in the map to the properties of the model object.
|
||||||
* @param values the source values to bind
|
* @param sourceValues the source values to bind
|
||||||
* @return the results of the binding operation
|
* @return the results of the binding operation
|
||||||
*/
|
*/
|
||||||
BindingResults bind(Map<String, ? extends Object> sourceValues);
|
BindingResults bind(Map<String, ? extends Object> sourceValues);
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.expression.MapAccessor;
|
import org.springframework.context.expression.MapAccessor;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
|
@ -58,11 +59,12 @@ import org.springframework.ui.binding.BindingResults;
|
||||||
import org.springframework.ui.binding.FormatterRegistry;
|
import org.springframework.ui.binding.FormatterRegistry;
|
||||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||||
import org.springframework.ui.format.Formatter;
|
import org.springframework.ui.format.Formatter;
|
||||||
|
import org.springframework.ui.message.MessageBuilder;
|
||||||
|
import org.springframework.ui.message.ResolvableArgument;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic {@link Binder binder} suitable for use in most environments.
|
* A generic {@link Binder binder} suitable for use in most environments.
|
||||||
* TODO - localization of alert messages using MessageResolver/MessageSource
|
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @see #configureBinding(BindingConfiguration)
|
* @see #configureBinding(BindingConfiguration)
|
||||||
|
|
@ -87,6 +89,8 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
private static Formatter defaultFormatter = new DefaultFormatter();
|
private static Formatter defaultFormatter = new DefaultFormatter();
|
||||||
|
|
||||||
|
private MessageSource messageSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new binder for the model object.
|
* Creates a new binder for the model object.
|
||||||
* @param model the model object containing properties this binder will bind to
|
* @param model the model object containing properties this binder will bind to
|
||||||
|
|
@ -118,6 +122,25 @@ public class GenericBinder implements Binder {
|
||||||
this.formatterRegistry = formatterRegistry;
|
this.formatterRegistry = formatterRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
|
||||||
|
* @param messageSource the message source
|
||||||
|
*/
|
||||||
|
public void setMessageSource(MessageSource messageSource) {
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the TypeConverter that converts values as required by Binding setValue and getValue attempts.
|
||||||
|
* For a setValue attempt, the TypeConverter will be asked to perform a conversion if the value parsed by the Binding's Formatter is not assignable to the target property type.
|
||||||
|
* For a getValue attempt, the TypeConverter will be asked to perform a conversion if the property type does not match the type T required by the Binding's Formatter.
|
||||||
|
* @param typeConverter the type converter used by the binding system, which is based on Spring EL
|
||||||
|
* @see EvaluationContext#getTypeConverter()
|
||||||
|
*/
|
||||||
|
public void setTypeConverter(TypeConverter typeConverter) {
|
||||||
|
this.typeConverter = typeConverter;
|
||||||
|
}
|
||||||
|
|
||||||
public Binding configureBinding(BindingConfiguration configuration) {
|
public Binding configureBinding(BindingConfiguration configuration) {
|
||||||
Binding binding;
|
Binding binding;
|
||||||
try {
|
try {
|
||||||
|
|
@ -166,6 +189,9 @@ public class GenericBinder implements Binder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook subclasses may use to filter the source values to bind.
|
* Hook subclasses may use to filter the source values to bind.
|
||||||
|
* This hook allows the binder to pre-process the source values before binding occurs.
|
||||||
|
- * For example, a Binder might insert empty or default values for fields that are not present.
|
||||||
|
- * As another example, a Binder might collapse multiple source values into a single source value.
|
||||||
* @param sourceValues the original source values map provided by the caller
|
* @param sourceValues the original source values map provided by the caller
|
||||||
* @return the filtered source values map that will be used to bind
|
* @return the filtered source values map that will be used to bind
|
||||||
*/
|
*/
|
||||||
|
|
@ -334,7 +360,7 @@ public class GenericBinder implements Binder {
|
||||||
try {
|
try {
|
||||||
formatter = getFormatter();
|
formatter = getFormatter();
|
||||||
} catch (EvaluationException e) {
|
} catch (EvaluationException e) {
|
||||||
// could occur the property was not found or is not readable
|
// could occur if the property was not found or is not readable
|
||||||
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +378,7 @@ public class GenericBinder implements Binder {
|
||||||
try {
|
try {
|
||||||
formatter = getFormatter();
|
formatter = getFormatter();
|
||||||
} catch (EvaluationException e) {
|
} catch (EvaluationException e) {
|
||||||
// could occur the property was not found or is not readable
|
// could occur if the property was not found or is not readable
|
||||||
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
||||||
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e);
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +487,8 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class NoSuchBindingResult implements BindingResult {
|
class NoSuchBindingResult implements BindingResult {
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
private Object sourceValue;
|
private Object sourceValue;
|
||||||
|
|
@ -486,7 +513,6 @@ public class GenericBinder implements Binder {
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new AbstractAlert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
|
||||||
return getProperty();
|
return getProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,21 +525,30 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return "Failed to bind to property '" + property + "'; no binding has been added for the property";
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
builder.code(getCode());
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property
|
||||||
|
+ "'; no binding has been added for the property");
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class InvalidFormatResult implements BindingResult {
|
class InvalidFormatResult implements BindingResult {
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
private Object formatted;
|
private Object sourceValue;
|
||||||
|
|
||||||
public InvalidFormatResult(String property, Object formatted, ParseException e) {
|
private ParseException cause;
|
||||||
|
|
||||||
|
public InvalidFormatResult(String property, Object sourceValue, ParseException cause) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.formatted = formatted;
|
this.sourceValue = sourceValue;
|
||||||
|
this.cause = cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProperty() {
|
public String getProperty() {
|
||||||
|
|
@ -521,7 +556,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getSourceValue() {
|
public Object getSourceValue() {
|
||||||
return formatted;
|
return sourceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailure() {
|
public boolean isFailure() {
|
||||||
|
|
@ -531,7 +566,6 @@ public class GenericBinder implements Binder {
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new AbstractAlert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
|
||||||
return getProperty();
|
return getProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,26 +578,31 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return "Failed to bind to property '" + property + "'; the user value "
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
+ StylerUtils.style(formatted) + " has an invalid format and could no be parsed";
|
builder.code(getCode());
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.arg("errorOffset", cause.getErrorOffset());
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; the user value "
|
||||||
|
+ StylerUtils.style(sourceValue) + " has an invalid format and could no be parsed");
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO the if branching in here is not very clean
|
class ExpressionEvaluationErrorResult implements BindingResult {
|
||||||
static class ExpressionEvaluationErrorResult implements BindingResult {
|
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
private Object formatted;
|
private Object sourceValue;
|
||||||
|
|
||||||
private EvaluationException e;
|
private EvaluationException cause;
|
||||||
|
|
||||||
public ExpressionEvaluationErrorResult(String property, Object formatted, EvaluationException e) {
|
public ExpressionEvaluationErrorResult(String property, Object sourceValue, EvaluationException cause) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.formatted = formatted;
|
this.sourceValue = sourceValue;
|
||||||
this.e = e;
|
this.cause = cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProperty() {
|
public String getProperty() {
|
||||||
|
|
@ -571,7 +610,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getSourceValue() {
|
public Object getSourceValue() {
|
||||||
return formatted;
|
return sourceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailure() {
|
public boolean isFailure() {
|
||||||
|
|
@ -581,38 +620,22 @@ public class GenericBinder implements Binder {
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new AbstractAlert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
|
||||||
return getProperty();
|
return getProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
return getFailureCode();
|
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||||
return "typeConversionFailure";
|
return "conversionFailed";
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
||||||
return "propertyNotFound";
|
return "propertyNotFound";
|
||||||
} else {
|
} else {
|
||||||
// TODO return more specific code based on underlying EvaluationException error code
|
|
||||||
return "couldNotSetValue";
|
return "couldNotSetValue";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Severity getFailureSeverity() {
|
public Severity getSeverity() {
|
||||||
SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode();
|
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
||||||
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||||
return Severity.FATAL;
|
return Severity.FATAL;
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
||||||
|
|
@ -622,36 +645,55 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFailureMessage() {
|
public String getMessage() {
|
||||||
SpelMessage spelCode = ((SpelEvaluationException) e).getMessageCode();
|
SpelMessage spelCode = ((SpelEvaluationException) cause).getMessageCode();
|
||||||
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
if (spelCode == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||||
AccessException accessException = (AccessException) e.getCause();
|
AccessException accessException = (AccessException) cause.getCause();
|
||||||
if (accessException.getCause() != null) {
|
if (accessException.getCause() != null) {
|
||||||
Throwable cause = accessException.getCause();
|
Throwable cause = accessException.getCause();
|
||||||
if (cause instanceof SpelEvaluationException
|
if (cause instanceof SpelEvaluationException
|
||||||
&& ((SpelEvaluationException) cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) {
|
&& ((SpelEvaluationException) cause).getMessageCode() == SpelMessage.TYPE_CONVERSION_ERROR) {
|
||||||
ConversionFailedException failure = (ConversionFailedException) cause.getCause();
|
ConversionFailedException failure = (ConversionFailedException) cause.getCause();
|
||||||
return "Failed to bind to property '" + property + "'; user value "
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
+ StylerUtils.style(formatted) + " could not be converted to property type ["
|
builder.code("conversionFailed");
|
||||||
+ failure.getTargetType().getName() + "]";
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; user value "
|
||||||
|
+ StylerUtils.style(sourceValue) + " could not be converted to property type ["
|
||||||
|
+ failure.getTargetType().getName() + "]");
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
} else if (spelCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
|
||||||
return "Failed to bind to property '" + property + "'; no such property exists on model";
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
}
|
builder.code(getCode());
|
||||||
return "Failed to bind to property '" + property + "'; reason = " + e.getLocalizedMessage();
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; no such property exists on model");
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
builder.code("couldNotSetValue");
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Failed to bind to property '" + property + "'; reason = " + cause.getLocalizedMessage());
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SuccessResult implements BindingResult {
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SuccessResult implements BindingResult {
|
||||||
|
|
||||||
private String property;
|
private String property;
|
||||||
|
|
||||||
private Object formatted;
|
private Object sourceValue;
|
||||||
|
|
||||||
public SuccessResult(String property, Object formatted) {
|
public SuccessResult(String property, Object sourceValue) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.formatted = formatted;
|
this.sourceValue = sourceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProperty() {
|
public String getProperty() {
|
||||||
|
|
@ -659,7 +701,7 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getSourceValue() {
|
public Object getSourceValue() {
|
||||||
return formatted;
|
return sourceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailure() {
|
public boolean isFailure() {
|
||||||
|
|
@ -669,7 +711,6 @@ public class GenericBinder implements Binder {
|
||||||
public Alert getAlert() {
|
public Alert getAlert() {
|
||||||
return new AbstractAlert() {
|
return new AbstractAlert() {
|
||||||
public String getElement() {
|
public String getElement() {
|
||||||
// TODO append model first? e.g. model.property
|
|
||||||
return getProperty();
|
return getProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -682,7 +723,12 @@ public class GenericBinder implements Binder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return "Sucessfully bound user value " + StylerUtils.style(formatted) + "to property '" + property + "'";
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
builder.code("bindSuccess");
|
||||||
|
builder.arg("label", new ResolvableArgument(property));
|
||||||
|
builder.arg("value", sourceValue);
|
||||||
|
builder.defaultMessage("Successfully bound user value " + StylerUtils.style(sourceValue) + "to property '" + property + "'");
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
|
||||||
|
|
||||||
private Map<String, Object> args;
|
private Map<String, Object> args;
|
||||||
|
|
||||||
private String defaultText;
|
private String defaultMessage;
|
||||||
|
|
||||||
private ExpressionParser expressionParser;
|
private ExpressionParser expressionParser;
|
||||||
|
|
||||||
|
|
@ -47,18 +47,25 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
|
||||||
ExpressionParser expressionParser) {
|
ExpressionParser expressionParser) {
|
||||||
this.codes = codes;
|
this.codes = codes;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.defaultText = defaultText;
|
this.defaultMessage = defaultText;
|
||||||
this.expressionParser = expressionParser;
|
this.expressionParser = expressionParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementing MessageResolver
|
// implementing MessageResolver
|
||||||
|
|
||||||
public String resolveMessage(MessageSource messageSource, Locale locale) {
|
public String resolveMessage(MessageSource messageSource, Locale locale) {
|
||||||
|
if (messageSource == null) {
|
||||||
|
if (defaultMessage != null) {
|
||||||
|
return defaultMessage;
|
||||||
|
} else {
|
||||||
|
throw new MessageResolutionException("Unable to resolve message; MessagSource argument is null and no defaultMessage is configured");
|
||||||
|
}
|
||||||
|
}
|
||||||
String messageString;
|
String messageString;
|
||||||
try {
|
try {
|
||||||
messageString = messageSource.getMessage(this, locale);
|
messageString = messageSource.getMessage(this, locale);
|
||||||
} catch (NoSuchMessageException e) {
|
} catch (NoSuchMessageException e) {
|
||||||
throw new MessageResolutionException("Unable to resolve message in MessageSource [" + messageSource + "]", e);
|
throw new MessageResolutionException("Unable to resolve message in" + messageSource, e);
|
||||||
}
|
}
|
||||||
Expression message;
|
Expression message;
|
||||||
try {
|
try {
|
||||||
|
|
@ -88,11 +95,11 @@ final class DefaultMessageResolver implements MessageResolver, MessageSourceReso
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultMessage() {
|
public String getDefaultMessage() {
|
||||||
return defaultText;
|
return defaultMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringCreator(this).append("codes", codes).append("defaultText", defaultText).toString();
|
return new ToStringCreator(this).append("codes", codes).append("defaultText", defaultMessage).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* 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.Locale;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds 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>
|
||||||
|
* String message = new MessageBuilder(messageSource).
|
||||||
|
* code("invalidFormat").
|
||||||
|
* arg("label", new ResolvableArgument("mathForm.decimalField")).
|
||||||
|
* arg("format", "#,###.##").
|
||||||
|
* defaultMessage("The decimal field must be in format #,###.##").
|
||||||
|
* build();
|
||||||
|
* </pre>
|
||||||
|
* Example messages.properties loaded by the MessageSource:
|
||||||
|
* <pre>
|
||||||
|
* invalidFormat=The #{label} must be in format #{format}.
|
||||||
|
* mathForm.decimalField=Decimal Field
|
||||||
|
* </pre>
|
||||||
|
* @author Keith Donald
|
||||||
|
* @since 3.0
|
||||||
|
* @see #code(String)
|
||||||
|
* @see #arg(String, Object)
|
||||||
|
* @see #defaultMessage(String)
|
||||||
|
* @see #locale(Locale)
|
||||||
|
*/
|
||||||
|
public class MessageBuilder {
|
||||||
|
|
||||||
|
private MessageSource messageSource;
|
||||||
|
|
||||||
|
private Locale locale;
|
||||||
|
|
||||||
|
private MessageResolverBuilder builder = new MessageResolverBuilder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new MessageBuilder that builds messages from message templates defined in the MessageSource
|
||||||
|
* @param messageSource the message source
|
||||||
|
*/
|
||||||
|
public MessageBuilder(MessageSource messageSource) {
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 a message code to try
|
||||||
|
* @return this, for fluent API usage
|
||||||
|
*/
|
||||||
|
public MessageBuilder code(String code) {
|
||||||
|
builder.code(code);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @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) {
|
||||||
|
builder.arg(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default message.
|
||||||
|
* If there are no codes to try, this will be used as the message.
|
||||||
|
* If there are 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 defaultMessage(String message) {
|
||||||
|
builder.defaultMessage(message);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the message locale.
|
||||||
|
* If not set, the default locale the Locale of the current request obtained from {@link LocaleContextHolder#getLocale()}.
|
||||||
|
* @param message the locale
|
||||||
|
* @return this, for fluent API usage
|
||||||
|
*/
|
||||||
|
public MessageBuilder locale(Locale locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the resolver for the message.
|
||||||
|
* Call after recording all builder instructions.
|
||||||
|
* @return the built message resolver
|
||||||
|
* @throws IllegalStateException if no codes have been added and there is no default message
|
||||||
|
*/
|
||||||
|
public String build() {
|
||||||
|
return builder.build().resolveMessage(messageSource, getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
private Locale getLocale() {
|
||||||
|
if (locale != null) {
|
||||||
|
return locale;
|
||||||
|
} else {
|
||||||
|
return LocaleContextHolder.getLocale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,14 @@ package org.springframework.ui.message;
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class MessageResolutionException extends RuntimeException {
|
public class MessageResolutionException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new message resolution exception.
|
||||||
|
* @param message a messaging describing the failure
|
||||||
|
*/
|
||||||
|
public MessageResolutionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new message resolution exception.
|
* Creates a new message resolution exception.
|
||||||
* @param message a messaging describing the failure
|
* @param message a messaging describing the failure
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ import java.util.Locale;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for a localized message.
|
* A factory for a localized message resolved from a MessageSource.
|
||||||
* TODO - consider putting this abstraction together with MessageSource; does it need to be in its own package?
|
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
* @see MessageSource
|
* @see MessageSource
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ public class GenericBinderTests {
|
||||||
BindingResults results = binder.bind(values);
|
BindingResults results = binder.bind(values);
|
||||||
assertEquals(3, results.size());
|
assertEquals(3, results.size());
|
||||||
assertTrue(results.get(1).isFailure());
|
assertTrue(results.get(1).isFailure());
|
||||||
assertEquals("typeConversionFailure", results.get(1).getAlert().getCode());
|
assertEquals("conversionFailed", results.get(1).getAlert().getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -229,7 +229,7 @@ public class GenericBinderTests {
|
||||||
assertEquals(0, b.getCollectionValues().length);
|
assertEquals(0, b.getCollectionValues().length);
|
||||||
BindingResult result = b.setValue(new String[] { "BAR", "BOGUS", "BOOP" });
|
BindingResult result = b.setValue(new String[] { "BAR", "BOGUS", "BOOP" });
|
||||||
assertTrue(result.isFailure());
|
assertTrue(result.isFailure());
|
||||||
assertEquals("typeConversionFailure", result.getAlert().getCode());
|
assertEquals("conversionFailed", result.getAlert().getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.springframework.ui.message;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MessageBuilderTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildMessage() {
|
||||||
|
MockMessageSource messageSource = new MockMessageSource();
|
||||||
|
messageSource.addMessage("invalidFormat", Locale.US, "#{label} must be in format #{format}");
|
||||||
|
messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field");
|
||||||
|
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||||
|
String message = builder.code("invalidFormat").arg("label", new ResolvableArgument("mathForm.decimalField"))
|
||||||
|
.arg("format", "#,###.##").defaultMessage("Field must be in format #,###.##").build();
|
||||||
|
assertEquals("Decimal Field must be in format #,###.##", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue