core context.message, context.alert, model.binder modules; includes SpEL-based GenericBinder implementation
This commit is contained in:
parent
2bd664f7ee
commit
78304c0ccf
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.context.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.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface Alert {
|
||||
|
||||
/**
|
||||
* 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,49 @@
|
|||
/*
|
||||
* 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.context.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 the element this alert is associated with
|
||||
* @param alert the alert to add
|
||||
*/
|
||||
public void add(String element, Alert alert);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.context.alert;
|
||||
|
||||
/**
|
||||
* A static factory for conveniently constructing Alerts.
|
||||
* Usage example:
|
||||
* <pre>
|
||||
* import static org.springframework.ui.alert.Alerts.*;
|
||||
*
|
||||
* public void example() {
|
||||
* info("An info alert");
|
||||
* warning("A warning alert");
|
||||
* error("An error alert");
|
||||
* fatal("A fatal alert");
|
||||
* }
|
||||
* </pre>
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class Alerts {
|
||||
|
||||
/**
|
||||
* Creates a new info alert.
|
||||
* @param message the alert message
|
||||
* @return the info alert
|
||||
* @see Severity#INFO
|
||||
*/
|
||||
public static Alert info(String message) {
|
||||
return new GenericAlert(Severity.INFO, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new warning alert.
|
||||
* @param message the alert message
|
||||
* @return the info alert
|
||||
* @see Severity#WARNING
|
||||
*/
|
||||
public static Alert warning(String message) {
|
||||
return new GenericAlert(Severity.WARNING, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new error alert.
|
||||
* @param message the alert message
|
||||
* @return the info alert
|
||||
* @see Severity#ERROR
|
||||
*/
|
||||
public static Alert error(String message) {
|
||||
return new GenericAlert(Severity.ERROR, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new fatal alert.
|
||||
* @param message the alert message
|
||||
* @return the info alert
|
||||
* @see Severity#FATAL
|
||||
*/
|
||||
public static Alert fatal(String message) {
|
||||
return new GenericAlert(Severity.FATAL, message);
|
||||
}
|
||||
|
||||
private static class GenericAlert implements Alert {
|
||||
|
||||
private Severity severity;
|
||||
|
||||
private String message;
|
||||
|
||||
public GenericAlert(Severity severity, String message) {
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getSeverity() + ": " + getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.context.alert;
|
||||
|
||||
/**
|
||||
* The set of alert severities.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see Alert
|
||||
*/
|
||||
public enum Severity {
|
||||
|
||||
/**
|
||||
* The "Informational" severity.
|
||||
* Indicates a successful operation or result.
|
||||
*/
|
||||
INFO,
|
||||
|
||||
/**
|
||||
* The "Warning" severity.
|
||||
* Indicates there is a minor problem, or to inform the user of possible misuse,
|
||||
* or to indicate a problem may arise in the future.
|
||||
*/
|
||||
WARNING,
|
||||
|
||||
/**
|
||||
* The "Error" severity.
|
||||
* Indicates a significant problem like a business rule violation.
|
||||
*/
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
* The "Fatal" severity.
|
||||
* Indicates a fatal problem like a system error or runtime exception.
|
||||
*/
|
||||
FATAL
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* A general-purpose Alerting API to communicate events of interest.
|
||||
*/
|
||||
package org.springframework.context.alert;
|
||||
|
||||
|
|
@ -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.context.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.context.alert.Alert;
|
||||
import org.springframework.context.alert.AlertContext;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
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>> alerts = 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(alerts);
|
||||
}
|
||||
|
||||
public List<Alert> getAlerts(String element) {
|
||||
List<Alert> messages = alerts.get(element);
|
||||
if (messages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(messages);
|
||||
}
|
||||
|
||||
public void add(String element, Alert alert) {
|
||||
List<Alert> alerts = this.alerts.get(element);
|
||||
alerts.add(alert);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("alerts", alerts).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* AlertContext implementation suitable for use in most environments.
|
||||
*/
|
||||
package org.springframework.context.alert.support;
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
/**
|
||||
* A factory for a default message to return if no message could be resolved.
|
||||
* Allows the message String to be created lazily, only when it is needed.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see MessageBuilder
|
||||
*/
|
||||
public interface DefaultMessageFactory {
|
||||
|
||||
/**
|
||||
* Create the default message.
|
||||
*/
|
||||
String createDefaultMessage();
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
final class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable {
|
||||
|
||||
private String[] codes;
|
||||
|
||||
private Map<String, Object> args;
|
||||
|
||||
private DefaultMessageFactory defaultMessageFactory;
|
||||
|
||||
private ExpressionParser expressionParser;
|
||||
|
||||
public DefaultMessageResolver(String[] codes, Map<String, Object> args,
|
||||
DefaultMessageFactory defaultMessageFactory, ExpressionParser expressionParser) {
|
||||
this.codes = codes;
|
||||
this.args = args;
|
||||
this.defaultMessageFactory = defaultMessageFactory;
|
||||
this.expressionParser = expressionParser;
|
||||
}
|
||||
|
||||
// implementing MessageResolver
|
||||
|
||||
public String resolveMessage(MessageSource messageSource, Locale locale) {
|
||||
if (messageSource == null) {
|
||||
if (defaultMessageFactory != null) {
|
||||
return defaultMessageFactory.createDefaultMessage();
|
||||
} else {
|
||||
throw new MessageResolutionException(
|
||||
"Unable to resolve message; MessagSource argument is null and no defaultMessage is configured");
|
||||
}
|
||||
}
|
||||
String messageString;
|
||||
try {
|
||||
messageString = messageSource.getMessage(this, locale);
|
||||
} catch (NoSuchMessageException e) {
|
||||
throw new MessageResolutionException("Unable to resolve message in" + messageSource, e);
|
||||
}
|
||||
Expression message;
|
||||
try {
|
||||
message = expressionParser.parseExpression(messageString, ParserContext.TEMPLATE_EXPRESSION);
|
||||
} catch (ParseException e) {
|
||||
throw new MessageResolutionException("Failed to parse message expression", e);
|
||||
}
|
||||
try {
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setRootObject(args);
|
||||
context.addPropertyAccessor(new MessageArgumentAccessor(messageSource, locale));
|
||||
return (String) message.getValue(context);
|
||||
} catch (EvaluationException e) {
|
||||
throw new MessageResolutionException("Failed to evaluate message expression '"
|
||||
+ message.getExpressionString() + "' to generate final message text", e);
|
||||
}
|
||||
}
|
||||
|
||||
// implementing MessageSourceResolver
|
||||
|
||||
public String[] getCodes() {
|
||||
return codes;
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getDefaultMessage() {
|
||||
return defaultMessageFactory.createDefaultMessage();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("codes", codes).append("args", args).append("defaultMessageFactory",
|
||||
defaultMessageFactory).toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static class MessageArgumentAccessor implements PropertyAccessor {
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
private Locale locale;
|
||||
|
||||
public MessageArgumentAccessor(MessageSource messageSource, Locale locale) {
|
||||
this.messageSource = messageSource;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
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);
|
||||
} else {
|
||||
return new TypedValue(o);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue)
|
||||
throws AccessException {
|
||||
throw new UnsupportedOperationException("Should not be called");
|
||||
}
|
||||
|
||||
public Class[] getSpecificTargetClasses() {
|
||||
return new Class[] { Map.class };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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.context.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 messageResolverBuilder = 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) {
|
||||
messageResolverBuilder.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) {
|
||||
messageResolverBuilder.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) {
|
||||
messageResolverBuilder.defaultMessage(message);
|
||||
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(DefaultMessageFactory defaultMessageFactory) {
|
||||
messageResolverBuilder.defaultMessage(defaultMessageFactory);
|
||||
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 messageResolverBuilder.build().resolveMessage(messageSource, getLocale());
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private Locale getLocale() {
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
} else {
|
||||
return LocaleContextHolder.getLocale();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
/**
|
||||
* Runtime exception thrown by a {@link MessageResolver} if a message resolution fails.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
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.
|
||||
* @param message a messaging describing the failure
|
||||
* @param cause the cause of the failure
|
||||
*/
|
||||
public MessageResolutionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
|
||||
/**
|
||||
* A factory for a localized message resolved from a MessageSource.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see MessageSource
|
||||
*/
|
||||
public interface MessageResolver {
|
||||
|
||||
/**
|
||||
* Resolve the message from the message source for the locale.
|
||||
* @param messageSource the message source, an abstraction for a resource bundle
|
||||
* @param locale the locale of this request
|
||||
* @return the resolved message
|
||||
* @throws MessageResolutionException if a resolution failure occurs
|
||||
*/
|
||||
public String resolveMessage(MessageSource messageSource, Locale locale);
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* MessageResolver resolver = new MessageResolverBuilder().
|
||||
* code("invalidFormat").
|
||||
* arg("label", new ResolvableArgument("mathForm.decimalField")).
|
||||
* arg("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>
|
||||
* 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)
|
||||
*/
|
||||
public class MessageResolverBuilder {
|
||||
|
||||
private Set<String> codes = new LinkedHashSet<String>();
|
||||
|
||||
private Map<String, Object> args = new LinkedHashMap<String, Object>();
|
||||
|
||||
private DefaultMessageFactory defaultMessageFactory;
|
||||
|
||||
private ExpressionParser expressionParser = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* 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 MessageResolverBuilder code(String code) {
|
||||
codes.add(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 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 MessageResolverBuilder arg(String name, Object value) {
|
||||
args.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MessageResolverBuilder defaultMessage(String message) {
|
||||
return defaultMessage(new StaticDefaultMessageFactory(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MessageResolverBuilder defaultMessage(DefaultMessageFactory defaultMessageFactory) {
|
||||
this.defaultMessageFactory = defaultMessageFactory;
|
||||
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 MessageResolver build() {
|
||||
if (codes == null && defaultMessageFactory == 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(codesArray, args, defaultMessageFactory, expressionParser);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.context.message;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* A message argument value that is resolved from a MessageSource.
|
||||
* Allows the value to be localized.
|
||||
* @see MessageSource
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class ResolvableArgument implements MessageSourceResolvable {
|
||||
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* Creates a resolvable argument.
|
||||
* @param code the code that will be used to lookup the argument value from the message source
|
||||
*/
|
||||
public ResolvableArgument(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String[] getCodes() {
|
||||
return new String[] { code.toString() };
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getDefaultMessage() {
|
||||
return String.valueOf(code);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("code", code).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.context.message;
|
||||
|
||||
class StaticDefaultMessageFactory implements DefaultMessageFactory {
|
||||
|
||||
private String defaultMessage;
|
||||
|
||||
public StaticDefaultMessageFactory(String defaultMessage) {
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
public String createDefaultMessage() {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* An API for creating localized messages.
|
||||
*/
|
||||
package org.springframework.context.message;
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.model.binder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bind to fields of a model object.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @param <M> The type of model this binder binds to
|
||||
*/
|
||||
public interface Binder<M> {
|
||||
|
||||
/**
|
||||
* Bind submitted field values.
|
||||
* @param fieldValues the field values to bind
|
||||
* @param model the model to bind to
|
||||
* @return the results of the binding operation
|
||||
* @throws MissingFieldException when the fieldValues Map is missing required fields
|
||||
*/
|
||||
BindingResults bind(Map<String, ? extends Object> fieldValues, M model);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.model.binder;
|
||||
|
||||
import org.springframework.context.alert.Alert;
|
||||
|
||||
/**
|
||||
* The result of a bind operation.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see Binder#bind(java.util.Map, Object)
|
||||
*/
|
||||
public interface BindingResult {
|
||||
|
||||
/**
|
||||
* The name of the field this binding result is for.
|
||||
* @see Binder#getNested(String)
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
/**
|
||||
* The raw submitted value for which binding was attempted.
|
||||
* If not a failure, this value was successfully bound to the model.
|
||||
* @see #isFailure()
|
||||
*/
|
||||
Object getSubmittedValue();
|
||||
|
||||
/**
|
||||
* 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,68 @@
|
|||
/*
|
||||
* 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.model.binder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.alert.Severity;
|
||||
|
||||
/**
|
||||
* The results of a bind operation.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see Binder#bind(java.util.Map, Object)
|
||||
*/
|
||||
public interface BindingResults extends Iterable<BindingResult> {
|
||||
|
||||
/**
|
||||
* The subset of BindingResults that were successful.
|
||||
*/
|
||||
List<BindingResult> successes();
|
||||
|
||||
/**
|
||||
* The subset of BindingResults that failed.
|
||||
*/
|
||||
List<BindingResult> failures();
|
||||
|
||||
/**
|
||||
* If there is at least one failure with a Severity equal to or greater than {@link Severity#ERROR}.
|
||||
* @see BindingResults#failures()
|
||||
*/
|
||||
boolean hasErrors();
|
||||
|
||||
/**
|
||||
* The subset of BindingResults that failed with {@link Severity#ERROR} or greater.
|
||||
*/
|
||||
List<BindingResult> errors();
|
||||
|
||||
/**
|
||||
* The total number of results.
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* The BindingResult at the specified index.
|
||||
* @throws IndexOutOfBoundsException if the index is out of bounds
|
||||
*/
|
||||
BindingResult get(int index);
|
||||
|
||||
/**
|
||||
* The BindingResult for the specified field.
|
||||
* Returns null if no result exists for the fieldName specified.
|
||||
*/
|
||||
BindingResult get(String fieldName);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.model.binder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Exception thrown by a Binder when a required source value is missing unexpectedly from the sourceValues map.
|
||||
* Indicates a client configuration error.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see Binder#bind(Map, Object)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MissingFieldException extends RuntimeException {
|
||||
|
||||
private List<String> missing;
|
||||
|
||||
/**
|
||||
* Creates a new missing field exceptions.
|
||||
* @param missing
|
||||
* @param fieldValues
|
||||
*/
|
||||
public MissingFieldException(List<String> missing, Map<String, ? extends Object> fieldValues) {
|
||||
super(getMessage(missing, fieldValues));
|
||||
this.missing = missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* The names of the fields that are missing.
|
||||
*/
|
||||
public List<String> getMissing() {
|
||||
return missing;
|
||||
}
|
||||
|
||||
private static String getMessage(List<String> missingRequired, Map<String, ? extends Object> sourceValues) {
|
||||
if (missingRequired.size() == 1) {
|
||||
return "Missing a field [" + missingRequired.get(0) + "]; fieldValues map contained " + sourceValues.keySet();
|
||||
} else {
|
||||
return "Missing fields " + missingRequired + "; fieldValues map contained " + sourceValues.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* API for binding submitted field values in a single batch operation.
|
||||
*/
|
||||
package org.springframework.model.binder;
|
||||
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.model.binder.Binder;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
import org.springframework.model.binder.BindingResults;
|
||||
import org.springframework.model.binder.MissingFieldException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base Binder implementation that defines common structural elements.
|
||||
* Subclasses should be parameterized & implement {@link #bind(Map, Object)}.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see #setRequiredFields(String[])
|
||||
* @see #setMessageSource(MessageSource)
|
||||
* @see #createFieldBinder()
|
||||
* @see #bind(Map, Object)
|
||||
*/
|
||||
public abstract class AbstractBinder<M> implements Binder<M> {
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
private String[] requiredFields;
|
||||
|
||||
/**
|
||||
* Configure the fields for which values must be present in each bind attempt.
|
||||
* @param fieldNames the required field names
|
||||
* @see MissingFieldException
|
||||
*/
|
||||
public void setRequiredFields(String[] fieldNames) {
|
||||
this.requiredFields = fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the MessageSource that resolves localized {@link BindingResult} alert messages.
|
||||
* @param messageSource the message source
|
||||
*/
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
Assert.notNull(messageSource, "The MessageSource is required");
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The configured MessageSource that resolves binding result alert messages.
|
||||
*/
|
||||
protected MessageSource getMessageSource() {
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
// Binder implementation
|
||||
|
||||
public final BindingResults bind(Map<String, ? extends Object> fieldValues, M model) {
|
||||
fieldValues = filter(fieldValues, model);
|
||||
checkRequired(fieldValues);
|
||||
FieldBinder fieldBinder = createFieldBinder(model);
|
||||
ArrayListBindingResults results = new ArrayListBindingResults(fieldValues.size());
|
||||
for (Map.Entry<String, ? extends Object> fieldValue : fieldValues.entrySet()) {
|
||||
results.add(fieldBinder.bind(fieldValue.getKey(), fieldValue.getValue()));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// subclass hooks
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method to create the {@link FieldBinder}
|
||||
* instance for the given model.
|
||||
*/
|
||||
protected abstract FieldBinder createFieldBinder(M model);
|
||||
|
||||
/**
|
||||
* Filter the fields to bind.
|
||||
* Allows for pre-processing the fieldValues Map before any binding occurs.
|
||||
* For example, you might insert empty or default values for fields that are not present.
|
||||
* As another example, you might collapse multiple fields into a single field.
|
||||
* Default implementation simply returns the fieldValues Map unchanged.
|
||||
* @param fieldValues the original fieldValues Map provided by the caller
|
||||
* @return the filtered fieldValues Map that will be used to bind
|
||||
*/
|
||||
protected Map<String, ? extends Object> filter(Map<String, ? extends Object> fieldValues, M model) {
|
||||
return fieldValues;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private void checkRequired(Map<String, ? extends Object> fieldValues) {
|
||||
if (requiredFields == null) {
|
||||
return;
|
||||
}
|
||||
List<String> missingRequired = new ArrayList<String>();
|
||||
for (String required : requiredFields) {
|
||||
boolean found = false;
|
||||
for (String property : fieldValues.keySet()) {
|
||||
if (property.equals(required)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
missingRequired.add(required);
|
||||
}
|
||||
}
|
||||
if (!missingRequired.isEmpty()) {
|
||||
throw new MissingFieldException(missingRequired, fieldValues);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.model.binder.support;
|
||||
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
|
||||
public class AlertBindingResult implements BindingResult {
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private Object submittedValue;
|
||||
|
||||
private Alert alert;
|
||||
|
||||
public AlertBindingResult(String fieldName, Object sourceValue, Alert alert) {
|
||||
this.fieldName = fieldName;
|
||||
this.submittedValue = sourceValue;
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public Object getSubmittedValue() {
|
||||
return submittedValue;
|
||||
}
|
||||
|
||||
public boolean isFailure() {
|
||||
return alert.getSeverity().compareTo(Severity.INFO) > 1;
|
||||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return alert;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getAlert().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
import org.springframework.model.binder.BindingResults;
|
||||
|
||||
class ArrayListBindingResults implements BindingResults {
|
||||
|
||||
private List<BindingResult> results;
|
||||
|
||||
public ArrayListBindingResults() {
|
||||
results = new ArrayList<BindingResult>();
|
||||
}
|
||||
|
||||
public ArrayListBindingResults(int size) {
|
||||
results = new ArrayList<BindingResult>(size);
|
||||
}
|
||||
|
||||
public void add(BindingResult result) {
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
// implementing Iterable
|
||||
|
||||
public Iterator<BindingResult> iterator() {
|
||||
return results.iterator();
|
||||
}
|
||||
|
||||
// implementing BindingResults
|
||||
|
||||
public List<BindingResult> successes() {
|
||||
List<BindingResult> results = new ArrayList<BindingResult>();
|
||||
for (BindingResult result : this) {
|
||||
if (!result.isFailure()) {
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<BindingResult> failures() {
|
||||
List<BindingResult> results = new ArrayList<BindingResult>();
|
||||
for (BindingResult result : this) {
|
||||
if (result.isFailure()) {
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return errors().size() > 0;
|
||||
}
|
||||
|
||||
public List<BindingResult> errors() {
|
||||
List<BindingResult> results = new ArrayList<BindingResult>();
|
||||
for (BindingResult result : this) {
|
||||
if (result.isFailure() && result.getAlert().getSeverity().compareTo(Severity.ERROR) >= 0) {
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public BindingResult get(int index) {
|
||||
return results.get(index);
|
||||
}
|
||||
|
||||
public BindingResult get(String fieldName) {
|
||||
for (BindingResult result : results) {
|
||||
if (result.getFieldName().equals(fieldName)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return results.size();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[BindingResults = " + results.toString() + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
|
||||
/**
|
||||
* Binder callback interface for binding a single field value.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see AbstractBinder#createFieldBinder(Object)
|
||||
*/
|
||||
public interface FieldBinder {
|
||||
|
||||
/**
|
||||
* Bind a single field.
|
||||
* @param fieldName the field name
|
||||
* @param value the field value
|
||||
* @return the binding result
|
||||
*/
|
||||
BindingResult bind(String fieldName, Object value);
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.context.message.DefaultMessageFactory;
|
||||
import org.springframework.context.message.MessageBuilder;
|
||||
import org.springframework.context.message.ResolvableArgument;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
|
||||
/**
|
||||
* Indicates a field failed to bind because it was not editable/writeable.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FieldNotEditableResult implements BindingResult {
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private Object submittedValue;
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
public FieldNotEditableResult(String fieldName, Object submittedValue, MessageSource messageSource) {
|
||||
this.fieldName = fieldName;
|
||||
this.submittedValue = submittedValue;
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public Object getSubmittedValue() {
|
||||
return submittedValue;
|
||||
}
|
||||
|
||||
public boolean isFailure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return new Alert() {
|
||||
public String getCode() {
|
||||
return "fieldNotEditable";
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.WARNING;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||
builder.code(getCode());
|
||||
builder.arg("label", new ResolvableArgument(fieldName));
|
||||
builder.arg("value", submittedValue);
|
||||
builder.defaultMessage(new DefaultMessageFactory() {
|
||||
public String createDefaultMessage() {
|
||||
return "Failed to bind submitted value " + submittedValue + "; field '" + fieldName + "' is not editable";
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getAlert().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.context.message.DefaultMessageFactory;
|
||||
import org.springframework.context.message.MessageBuilder;
|
||||
import org.springframework.context.message.ResolvableArgument;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
|
||||
/**
|
||||
* Indicates field failed to bind because it was not found.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FieldNotFoundResult implements BindingResult {
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private Object submittedValue;
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
public FieldNotFoundResult(String fieldName, Object submittedValue, MessageSource messageSource) {
|
||||
this.fieldName = fieldName;
|
||||
this.submittedValue = submittedValue;
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public Object getSubmittedValue() {
|
||||
return submittedValue;
|
||||
}
|
||||
|
||||
public boolean isFailure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Alert getAlert() {
|
||||
return new Alert() {
|
||||
public String getCode() {
|
||||
return "fieldNotFound";
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.WARNING;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||
builder.code(getCode());
|
||||
builder.arg("label", new ResolvableArgument(fieldName));
|
||||
builder.arg("value", submittedValue);
|
||||
builder.defaultMessage(new DefaultMessageFactory() {
|
||||
public String createDefaultMessage() {
|
||||
return "Failed to bind submitted value " + submittedValue + "; no field '" + fieldName + "' found";
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getAlert().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.context.message.DefaultMessageFactory;
|
||||
import org.springframework.context.message.MessageBuilder;
|
||||
import org.springframework.context.message.ResolvableArgument;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.model.binder.Binder;
|
||||
import org.springframework.model.binder.BindingResult;
|
||||
|
||||
/**
|
||||
* A {@link Binder} implementation that accepts any target object and uses
|
||||
* Spring's Expression Language (SpEL) to evaluate the keys in the field value Map.
|
||||
* @author Mark Fisher
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public class GenericBinder extends AbstractBinder<Object> {
|
||||
|
||||
private final ExpressionParser expressionParser = new SpelExpressionParser(
|
||||
SpelExpressionParserConfiguration.CreateObjectIfAttemptToReferenceNull
|
||||
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize);
|
||||
|
||||
@Override
|
||||
protected FieldBinder createFieldBinder(Object model) {
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.addPropertyAccessor(new MapAccessor());
|
||||
context.setRootObject(model);
|
||||
return new ExpressionFieldBinder(getMessageSource(), expressionParser, context);
|
||||
}
|
||||
|
||||
private static class ExpressionFieldBinder implements FieldBinder {
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
|
||||
private final EvaluationContext evaluationContext;
|
||||
|
||||
private ExpressionFieldBinder(MessageSource messageSource, ExpressionParser expressionParser,
|
||||
EvaluationContext evaluationContext) {
|
||||
this.messageSource = messageSource;
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
public BindingResult bind(String fieldName, Object value) {
|
||||
Alert alert = null;
|
||||
try {
|
||||
Expression exp = expressionParser.parseExpression(fieldName);
|
||||
if (!exp.isWritable(evaluationContext)) {
|
||||
return new FieldNotEditableResult(fieldName, value, messageSource);
|
||||
}
|
||||
exp.setValue(evaluationContext, value);
|
||||
alert = new BindSuccessAlert(fieldName, value, messageSource);
|
||||
} catch (ParseException e) {
|
||||
alert = new InternalErrorAlert(e);
|
||||
} catch (EvaluationException e) {
|
||||
SpelEvaluationException spelException = (SpelEvaluationException) e;
|
||||
if (spelException.getMessageCode() == SpelMessage.EXCEPTION_DURING_PROPERTY_WRITE) {
|
||||
ConversionFailedException conversionFailure = findConversionFailureCause(spelException);
|
||||
if (conversionFailure != null) {
|
||||
alert = new TypeMismatchAlert(fieldName, value, conversionFailure, messageSource);
|
||||
}
|
||||
}
|
||||
if (alert == null) {
|
||||
alert = new InternalErrorAlert(e);
|
||||
}
|
||||
}
|
||||
return new AlertBindingResult(fieldName, value, alert);
|
||||
}
|
||||
|
||||
private ConversionFailedException findConversionFailureCause(Exception e) {
|
||||
Throwable cause = e.getCause();
|
||||
while (cause != null) {
|
||||
if (cause instanceof ConversionFailedException) {
|
||||
return (ConversionFailedException) cause;
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BindSuccessAlert implements Alert {
|
||||
|
||||
private final String fieldName;
|
||||
|
||||
private final Object value;
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
public BindSuccessAlert(String fieldName, Object value, MessageSource messageSource) {
|
||||
this.fieldName = fieldName;
|
||||
this.value = value;
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return "bindSuccess";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||
builder.code(getCode());
|
||||
builder.arg("label", new ResolvableArgument(fieldName));
|
||||
builder.arg("value", value);
|
||||
builder.defaultMessage(new DefaultMessageFactory() {
|
||||
public String createDefaultMessage() {
|
||||
return "Successfully bound submitted value " + value + " to field '" + fieldName + "'";
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.INFO;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TypeMismatchAlert implements Alert {
|
||||
|
||||
private final String fieldName;
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final ConversionFailedException cause;
|
||||
|
||||
private MessageSource messageSource;
|
||||
|
||||
public TypeMismatchAlert(String fieldName, Object value, ConversionFailedException cause,
|
||||
MessageSource messageSource) {
|
||||
this.fieldName = fieldName;
|
||||
this.value = value;
|
||||
this.cause = cause;
|
||||
this.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return "typeMismatch";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
MessageBuilder builder = new MessageBuilder(messageSource);
|
||||
builder.code(getCode());
|
||||
builder.arg("label", new ResolvableArgument(fieldName));
|
||||
builder.arg("value", value);
|
||||
builder.defaultMessage(new DefaultMessageFactory() {
|
||||
public String createDefaultMessage() {
|
||||
return "Failed to bind submitted value " + value + " to field '" + fieldName
|
||||
+ "'; value could not be converted to type [" + cause.getTargetType().getName() + "]";
|
||||
}
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InternalErrorAlert implements Alert {
|
||||
|
||||
private final Exception cause;
|
||||
|
||||
public InternalErrorAlert(Exception cause) {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return "internalError";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return "An internal error occurred; message = [" + cause.getMessage() + "]";
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.FATAL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Binder API implementation support.
|
||||
*/
|
||||
package org.springframework.model.binder.support;
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package org.springframework.context.alert;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.context.alert.Alerts.error;
|
||||
import static org.springframework.context.alert.Alerts.fatal;
|
||||
import static org.springframework.context.alert.Alerts.info;
|
||||
import static org.springframework.context.alert.Alerts.warning;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
|
||||
public class AlertsTests {
|
||||
|
||||
@Test
|
||||
public void testFactoryMethods() {
|
||||
Alert a1 = info("alert 1");
|
||||
assertEquals(Severity.INFO, a1.getSeverity());
|
||||
assertEquals("alert 1", a1.getMessage());
|
||||
|
||||
Alert a2 = warning("alert 2");
|
||||
assertEquals(Severity.WARNING, a2.getSeverity());
|
||||
assertEquals("alert 2", a2.getMessage());
|
||||
|
||||
Alert a3 = error("alert 3");
|
||||
assertEquals(Severity.ERROR, a3.getSeverity());
|
||||
assertEquals("alert 3", a3.getMessage());
|
||||
|
||||
Alert a4 = fatal("alert 4");
|
||||
assertEquals(Severity.FATAL, a4.getSeverity());
|
||||
assertEquals("alert 4", a4.getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.springframework.context.alert.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.alert.Alert;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.context.alert.support.DefaultAlertContext;
|
||||
|
||||
public class DefaultAlertContextTests {
|
||||
|
||||
private DefaultAlertContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = new DefaultAlertContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAlert() {
|
||||
Alert alert = new Alert() {
|
||||
public String getCode() {
|
||||
return "invalidFormat";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return "Please enter a value in format yyyy-dd-mm";
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return Severity.ERROR;
|
||||
}
|
||||
};
|
||||
context.add("form.property", alert);
|
||||
assertEquals(1, context.getAlerts().size());
|
||||
assertEquals("invalidFormat", context.getAlerts("form.property").get(0).getCode());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.springframework.context.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.message.MessageBuilder;
|
||||
import org.springframework.context.message.ResolvableArgument;
|
||||
|
||||
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", "#,###.##").locale(Locale.US).defaultMessage("Field must be in format #,###.##").build();
|
||||
assertEquals("Decimal Field must be in format #,###.##", message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package org.springframework.context.message;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.message.MessageResolver;
|
||||
import org.springframework.context.message.MessageResolverBuilder;
|
||||
import org.springframework.context.message.ResolvableArgument;
|
||||
|
||||
public class MessageResolverBuilderTests {
|
||||
|
||||
private MessageResolverBuilder builder = new MessageResolverBuilder();
|
||||
|
||||
@Test
|
||||
public void buildMessage() {
|
||||
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");
|
||||
String message = resolver.resolveMessage(messageSource, Locale.US);
|
||||
assertEquals("Decimal Field must be in format #,###.##", message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package org.springframework.context.message;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.support.AbstractMessageSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class MockMessageSource extends AbstractMessageSource {
|
||||
|
||||
/** Map from 'code + locale' keys to message Strings */
|
||||
private final Map<String, String> messages = new HashMap<String, String>();
|
||||
|
||||
@Override
|
||||
protected MessageFormat resolveCode(String code, Locale locale) {
|
||||
throw new IllegalStateException("Should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveCodeWithoutArguments(String code, Locale locale) {
|
||||
return this.messages.get(code + "_" + locale.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given message with the given code.
|
||||
* @param code the lookup code
|
||||
* * @param locale the locale that the message should be found within
|
||||
* @param msg the message associated with this lookup code
|
||||
*/
|
||||
public void addMessage(String code, Locale locale, String msg) {
|
||||
Assert.notNull(code, "Code must not be null");
|
||||
Assert.notNull(locale, "Locale must not be null");
|
||||
Assert.notNull(msg, "Message must not be null");
|
||||
this.messages.put(code + "_" + locale.toString(), msg);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Added message [" + msg + "] for code [" + code + "] and Locale [" + locale + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + this.messages;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* 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.model.binder.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.alert.Severity;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.message.MockMessageSource;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.model.binder.Binder;
|
||||
import org.springframework.model.binder.BindingResults;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @since 3.0
|
||||
*/
|
||||
public class GenericBinderTests {
|
||||
|
||||
@Test
|
||||
public void simpleValues() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
map.put("name", "John Doe");
|
||||
map.put("age", 42);
|
||||
map.put("male", true);
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
|
||||
BindingResults results = binder.bind(map, person);
|
||||
assertEquals(3, results.size());
|
||||
assertEquals(3, results.successes().size());
|
||||
assertEquals(0, results.failures().size());
|
||||
assertEquals(0, results.errors().size());
|
||||
assertEquals("name", results.get(0).getFieldName());
|
||||
assertEquals("John Doe", results.get(0).getSubmittedValue());
|
||||
assertEquals(false, results.get(0).isFailure());
|
||||
assertEquals(Severity.INFO, results.get(0).getAlert().getSeverity());
|
||||
assertEquals("bindSuccess", results.get(0).getAlert().getCode());
|
||||
assertEquals("Successfully bound submitted value John Doe to field 'name'", results.get(0).getAlert().getMessage());
|
||||
assertEquals("name", results.get("name").getFieldName());
|
||||
assertEquals("John Doe", results.get("name").getSubmittedValue());
|
||||
|
||||
assertEquals("John Doe", person.name);
|
||||
assertEquals(42, person.age);
|
||||
assertTrue(person.male);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedValues() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("pob.city", "Rome");
|
||||
map.put("pob.country", "Italy");
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
binder.bind(map, person);
|
||||
assertNotNull(person.pob);
|
||||
assertEquals("Rome", person.pob.city);
|
||||
assertEquals("Italy", person.pob.country);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapValues() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("jobHistory['0']", "Clerk");
|
||||
map.put("jobHistory['1']", "Plumber");
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
binder.bind(map, person);
|
||||
assertEquals("Clerk", person.jobHistory.get(0));
|
||||
assertEquals("Plumber", person.jobHistory.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMismatch() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("male", "bogus");
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
BindingResults results = binder.bind(map, person);
|
||||
assertEquals(1, results.size());
|
||||
assertEquals(0, results.successes().size());
|
||||
assertEquals(1, results.failures().size());
|
||||
assertEquals(1, results.errors().size());
|
||||
assertEquals("bogus", results.get(0).getSubmittedValue());
|
||||
assertEquals("typeMismatch", results.get(0).getAlert().getCode());
|
||||
assertEquals(Severity.ERROR, results.get(0).getAlert().getSeverity());
|
||||
assertEquals("Failed to bind submitted value bogus to field 'male'; value could not be converted to type [boolean]", results.get(0).getAlert().getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void internalError() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("bogus", "bogus");
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
BindingResults results = binder.bind(map, person);
|
||||
assertEquals(1, results.size());
|
||||
assertEquals(0, results.successes().size());
|
||||
assertEquals(1, results.failures().size());
|
||||
assertEquals(1, results.errors().size());
|
||||
assertEquals("bogus", results.get(0).getSubmittedValue());
|
||||
assertEquals("internalError", results.get(0).getAlert().getCode());
|
||||
assertEquals(Severity.FATAL, results.get(0).getAlert().getSeverity());
|
||||
assertEquals("An internal error occurred; message = [EL1034E:(pos 0): A problem occurred whilst attempting to set the property 'bogus': Unable to access property 'bogus' through setter]", results.get(0).getAlert().getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldNotEditable() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("readOnly", "whatever");
|
||||
Binder<Object> binder = new GenericBinder();
|
||||
BindingResults results = binder.bind(map, person);
|
||||
assertEquals(1, results.size());
|
||||
assertEquals(0, results.successes().size());
|
||||
assertEquals(1, results.failures().size());
|
||||
assertEquals(0, results.errors().size());
|
||||
assertEquals("whatever", results.get(0).getSubmittedValue());
|
||||
assertEquals("fieldNotEditable", results.get(0).getAlert().getCode());
|
||||
assertEquals(Severity.WARNING, results.get(0).getAlert().getSeverity());
|
||||
assertEquals("Failed to bind submitted value whatever; field 'readOnly' is not editable", results.get(0).getAlert().getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageSource() {
|
||||
Person person = new Person();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("male", "bogus");
|
||||
GenericBinder binder = new GenericBinder();
|
||||
MockMessageSource messageSource = new MockMessageSource();
|
||||
messageSource.addMessage("typeMismatch", Locale.US, "Please enter true or false for the value of the #{label} field; you entered #{value}");
|
||||
binder.setMessageSource(messageSource);
|
||||
LocaleContextHolder.setLocale(Locale.US);
|
||||
BindingResults results = binder.bind(map, person);
|
||||
assertEquals("Please enter true or false for the value of the male field; you entered bogus", results.get(0).getAlert().getMessage());
|
||||
LocaleContextHolder.setLocale(null);
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
private boolean male;
|
||||
|
||||
private PlaceOfBirth pob;
|
||||
|
||||
private Map<Integer, String> jobHistory;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public boolean isMale() {
|
||||
return male;
|
||||
}
|
||||
|
||||
public void setMale(boolean male) {
|
||||
this.male = male;
|
||||
}
|
||||
|
||||
public PlaceOfBirth getPob() {
|
||||
return pob;
|
||||
}
|
||||
|
||||
public void setPob(PlaceOfBirth pob) {
|
||||
this.pob = pob;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getJobHistory() {
|
||||
return jobHistory;
|
||||
}
|
||||
|
||||
public void setJobHistory(Map<Integer, String> jobHistory) {
|
||||
this.jobHistory = jobHistory;
|
||||
}
|
||||
|
||||
public void setBogus(String bogus) {
|
||||
throw new RuntimeException("internal error");
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("name", name)
|
||||
.append("age", age)
|
||||
.append("male", male)
|
||||
.append("pob", pob)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PlaceOfBirth {
|
||||
|
||||
private String city;
|
||||
|
||||
private String country;
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("city", city)
|
||||
.append("country", country)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue