UI message system initial commit; support for adding UI messages of different severities INFO, WARNING, ERROR, FATAL

This commit is contained in:
Keith Donald 2009-06-06 19:13:27 +00:00
parent 8c65ed9e0b
commit 534871e6f6
13 changed files with 682 additions and 18 deletions

View File

@ -1,4 +1,4 @@
#Thu Dec 18 06:34:20 PST 2008
#Sat Jun 06 14:43:50 EDT 2009
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
@ -55,17 +55,17 @@ org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_block_comments=false
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_html=false
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
org.eclipse.jdt.core.formatter.comment.format_line_comments=false
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=120
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
@ -256,7 +256,7 @@ org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.lineSplit=120
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0

View File

@ -1,4 +1,4 @@
#Thu Dec 18 06:34:20 PST 2008
#Sat Jun 06 14:43:50 EDT 2009
eclipse.preferences.version=1
formatter_profile=org.eclipse.jdt.ui.default.eclipse_profile
formatter_profile=_Spring
formatter_settings_version=11

View File

@ -3,13 +3,38 @@ package org.springframework.ui.binding;
import java.util.Map;
public interface BindingFailure {
/**
* The code identifying the type of failure.
* This code can be used to resolve the failure message if no explicit {@link #getMessage() message} is configured.
*/
String getCode();
String getSeverity();
// TODO - where does arg formatting occur
Map<String, Object> getArgs();
/**
* The severity of the failure, which measures the impact of the failure on the user.
*/
Severity getSeverity();
/**
* An map of arguments that can be used as named parameters in resolvable messages associated with this failure.
* Each constraint defines a set of arguments that are specific to it. For example, a length
* constraint might define arguments of "min" and "max" of Integer values. In the message bundle, you then might see
* "length=The ${label} field value must be between ${min} and ${max}". Returns an empty map if no arguments are present.
*/
Map<String, Object> getArguments();
/**
* The message summarizing this failure. May be a literal string or a resolvable message code. Can be null.
* If null, the failure message will be resolved using the failure code.
*/
String getDefaultMessage();
/**
* A map of details providing additional information about this failure. Each entry in this map is a failure detail
* item that has a name and value. The name uniquely identifies the failure detail and describes its purpose;
* for example, a "cause" or "recommendedAction". The value is the failure detail message, either a literal string or
* resolvable code. If resolvable, the detail code is resolved relative to this failure.
* Returns an empty map if no details are present.
*/
Map<String, String> getDetails();
}

View File

@ -0,0 +1,87 @@
/*
* 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.MessageSourceResolvable;
import org.springframework.core.style.ToStringCreator;
class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable {
private Severity severity;
private String[] codes;
private Object[] args;
private String defaultText;
public DefaultMessageResolver(Severity severity, String[] codes, Object[] args, String defaultText) {
this.severity = severity;
this.codes = codes;
this.args = args;
this.defaultText = defaultText;
}
// implementing MessageResolver
public Message resolveMessage(MessageSource messageSource, Locale locale) {
String text = messageSource.getMessage(this, locale);
return new TextMessage(severity, text);
}
// implementing MessageSourceResolver
public String[] getCodes() {
return codes;
}
public Object[] getArguments() {
return args;
}
public String getDefaultMessage() {
return defaultText;
}
public String toString() {
return new ToStringCreator(this).append("severity", severity).append("codes", codes).append("args", args).append("defaultText", defaultText).toString();
}
static class TextMessage implements Message {
private Severity severity;
private String text;
public TextMessage(Severity severity, String text) {
this.severity = severity;
this.text = text;
}
public Severity getSeverity() {
return severity;
}
public String getText() {
return text;
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2004-2009 the original author oimport java.io.Serializable;
import org.springframework.core.style.ToStringCreator;
ou may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.message;
/**
* Communicates information about an event to the user.
* For example, a validation message may inform a web application user a business rule was violated.
* A message is attached to a receiving element, has text providing the basis for communication,
* and has severity indicating the priority or intensity of the message for its receiver.
*
* @author Keith Donald
*/
public interface Message {
/**
* The severity of this message.
* The severity indicates the intensity or priority of the communication.
* @return the message severity
*/
public Severity getSeverity();
/**
* The message text.
* The text is the message's communication payload.
* @return the message text
*/
public String getText();
}

View File

@ -0,0 +1,158 @@
/*
* 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.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.style.ToStringCreator;
/**
* A convenient builder for building {@link MessageResolver} objects programmatically.
* Often used by model code such as validation logic to conveniently record validation messages.
* Supports the production of message resolvers that hard-code their message text,
* as well as message resolvers that retrieve their text from a {@link MessageSource}.
*
* Usage example:
* <p>
* <pre>
* new MessageBuilder().
* severity(Severity.ERROR).
* code(&quot;invalidFormat&quot;).
* arg("mathForm.decimalField").
* arg("#,###.##").
* defaultText(&quot;The decimal field must be in format #,####.##&quot;).
* build();
* </pre>
* </p>
* @author Keith Donald
*/
public class MessageBuilder {
private Set<String> codes = new LinkedHashSet<String>();
private Severity severity;
private List<Object> args = new ArrayList<Object>();
private String defaultText;
/**
* Records the severity of the message.
* @return this, for fluent API usage
*/
public MessageBuilder severity(Severity severity) {
this.severity = severity;
return this;
}
/**
* Records that the message being built should try and resolve its text using the code provided.
* Adds the code to the codes list. Successive calls to this method add additional codes.
* Codes are applied in the order they are added.
* @param code the message code
* @return this, for fluent API usage
*/
public MessageBuilder code(String code) {
codes.add(code);
return this;
}
/**
* Records that the message being built has a variable argument.
* Adds the arg to the args list. Successive calls to this method add additional args.
* Args are applied in the order they are added.
* @param arg the message argument value
* @return this, for fluent API usage
*/
public MessageBuilder arg(Object arg) {
args.add(arg);
return this;
}
/**
* Records that the message being built has a variable argument, whose display value is also {@link MessageSourceResolvable}.
* Adds the arg to the args list. Successive calls to this method add additional resolvable args.
* Args are applied in the order they are added.
* @param arg the resolvable message argument
* @return this, for fluent API usage
*/
public MessageBuilder resolvableArg(Object arg) {
args.add(new ResolvableArgument(arg));
return this;
}
/**
* Records the fallback text of the message being built.
* If the message has no codes, this will always be used as the text.
* If the message has codes but none can be resolved, this will always be used as the text.
* @param text the default text
* @return this, for fluent API usage
*/
public MessageBuilder defaultText(String text) {
defaultText = text;
return this;
}
/**
* Builds the message that will be resolved.
* Call after recording builder instructions.
* @return the built message resolver
*/
public MessageResolver build() {
if (severity == null) {
severity = Severity.INFO;
}
if (codes == null && defaultText == null) {
throw new IllegalArgumentException(
"A message code or the message text is required to build this message resolver");
}
String[] codesArray = (String[]) codes.toArray(new String[codes.size()]);
Object[] argsArray = args.toArray(new Object[args.size()]);
return new DefaultMessageResolver(severity, codesArray, argsArray, defaultText);
}
private static class ResolvableArgument implements MessageSourceResolvable {
private Object arg;
public ResolvableArgument(Object arg) {
this.arg = arg;
}
public Object[] getArguments() {
return null;
}
public String[] getCodes() {
return new String[] { arg.toString() };
}
public String getDefaultMessage() {
return arg.toString();
}
public String toString() {
return new ToStringCreator(this).append("arg", arg).toString();
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.message;
import java.util.List;
import java.util.Map;
/**
* A context for recording and retrieving messages for display in a user interface.
*/
public interface MessageContext {
/**
* Return all messages in this context indexed by the UI element they are associated with.
* @return the message map
*/
public Map<String, List<Message>> getMessages();
/**
* Get all messages on the UI element provided.
* Returns an empty list if no messages have been recorded against the element.
* Messages are returned in the order they were added.
* @param element the id of the element to lookup messages against
*/
public List<Message> getMessages(String element);
/**
* Add a new message to this context.
* @param element the id of the UI element the message should be associated with
* @param messageResolver the resolver that will resolve the message to be added
*/
public void addMessage(String element, MessageResolver messageResolver);
}

View File

@ -0,0 +1,39 @@
/*
* 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;
/**
* A factory for a Message. Allows a Message to be internationalized and to be resolved from a
* {@link MessageSource message resource bundle}.
*
* @author Keith Donald
* @see Message
* @see MessageSource
*/
public interface MessageResolver {
/**
* Resolve the message from the message source using the current locale.
* @param messageSource the message source, an abstraction for a resource bundle
* @param locale the current locale of this request
* @return the resolved message
*/
public Message resolveMessage(MessageSource messageSource, Locale locale);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.message;
/**
* Enum exposing supported message severities.
*
* @author Keith Donald
* @author Jeremy Grelle
* @see Message
*/
public enum Severity {
/**
* The "Informational" severity. Used to indicate a successful operation or result.
*/
INFO,
/**
* The "Warning" severity. Used to indicate there is a minor problem, or to inform the message receiver of possible
* misuse, or to indicate a problem may arise in the future.
*/
WARNING,
/**
* The "Error" severity. Used to indicate a significant problem like a business rule violation.
*/
ERROR,
/**
* The "Fatal" severity. Used to indicate a fatal problem like a system error.
*/
FATAL
}

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
A system for creating and managing localized messages to display in a UI.
</p>
</body>
</html>

View File

@ -0,0 +1,147 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.message.support;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.core.style.ToStringCreator;
import org.springframework.ui.message.Message;
import org.springframework.ui.message.MessageContext;
import org.springframework.ui.message.MessageResolver;
import org.springframework.ui.message.Severity;
import org.springframework.util.CachingMapDecorator;
import edu.emory.mathcs.backport.java.util.Collections;
/**
* The default message context implementation.
* Uses a {@link MessageSource} to resolve messages that are added by callers.
*
* @author Keith Donald
*/
public class DefaultMessageContext implements MessageContext {
private MessageSource messageSource;
@SuppressWarnings("serial")
private Map<String, ArrayList<Message>> messagesByElement = new CachingMapDecorator<String, ArrayList<Message>>(new LinkedHashMap<String, ArrayList<Message>>()) {
protected ArrayList<Message> create(String element) {
return new ArrayList<Message>();
}
};
/**
* Creates a new default message context.
* Defaults to a message source that simply resolves default text and cannot resolve localized message codes.
*/
public DefaultMessageContext() {
init(null);
}
/**
* Creates a new default message context.
* @param messageSource the message source to resolve messages added to this context
*/
public DefaultMessageContext(MessageSource messageSource) {
init(messageSource);
}
/**
* The message source configured to resolve message text.
* @return the message source
*/
public MessageSource getMessageSource() {
return messageSource;
}
// implementing message context
@SuppressWarnings("unchecked")
public Map<String, List<Message>> getMessages() {
return Collections.unmodifiableMap(messagesByElement);
}
@SuppressWarnings("unchecked")
public List<Message> getMessages(String element) {
List<Message> messages = messagesByElement.get(element);
if (messages.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableList(messages);
}
public void addMessage(String element, MessageResolver messageResolver) {
List<Message> messages = messagesByElement.get(element);
messages.add(new ResolvableMessage(messageResolver));
}
public String toString() {
return new ToStringCreator(this).append("messagesByElement", messagesByElement).toString();
}
// internal helpers
private void init(MessageSource messageSource) {
setMessageSource(messageSource);
}
private void setMessageSource(MessageSource messageSource) {
if (messageSource == null) {
messageSource = new DefaultTextFallbackMessageSource();
}
this.messageSource = messageSource;
}
private static class DefaultTextFallbackMessageSource extends AbstractMessageSource {
protected MessageFormat resolveCode(String code, Locale locale) {
return null;
}
}
class ResolvableMessage implements Message {
private MessageResolver resolver;
private Message resolvedMessage;
public ResolvableMessage(MessageResolver resolver) {
this.resolver = resolver;
}
public Severity getSeverity() {
return getMessage().getSeverity();
}
public String getText() {
return getMessage().getText();
}
public Message getMessage() {
if (resolvedMessage == null) {
resolvedMessage = resolver.resolveMessage(messageSource, LocaleContextHolder.getLocale());
}
return resolvedMessage;
}
}
}

View File

@ -0,0 +1,24 @@
package org.springframework.ui.message;
import static org.junit.Assert.assertEquals;
import java.util.Locale;
import org.junit.Test;
import org.springframework.context.support.StaticMessageSource;
public class MessageBuilderTests {
private MessageBuilder builder = new MessageBuilder();
@Test
public void buildMessage() {
MessageResolver resolver = builder.severity(Severity.ERROR).code("invalidFormat").resolvableArg("mathForm.decimalField")
.arg("#,###.##").defaultText("Field must be in format #,###.##").build();
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("invalidFormat", Locale.US, "{0} must be in format {1}");
messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field");
Message message = resolver.resolveMessage(messageSource, Locale.US);
assertEquals(Severity.ERROR, message.getSeverity());
assertEquals("Decimal Field must be in format #,###.##", message.getText());
}
}

View File

@ -0,0 +1,40 @@
package org.springframework.ui.message.support;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.ui.message.Message;
import org.springframework.ui.message.MessageBuilder;
import org.springframework.ui.message.MessageResolver;
import org.springframework.ui.message.Severity;
public class DefaultMessageContextTests {
private DefaultMessageContext context;
@Before
public void setUp() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("invalidFormat", Locale.US, "{0} must be in format {1}");
messageSource.addMessage("mathForm.decimalField", Locale.US, "Decimal Field");
context = new DefaultMessageContext(messageSource);
}
@Test
public void addMessage() {
MessageBuilder builder = new MessageBuilder();
MessageResolver message = builder.severity(Severity.ERROR).code("invalidFormat").resolvableArg(
"mathForm.decimalField").arg("#,###.##").defaultText("Field must be in format #,###.##").build();
context.addMessage("mathForm.decimalField", message);
Map<String, List<Message>> messages = context.getMessages();
assertEquals(1, messages.size());
assertEquals("Decimal Field must be in format #,###.##", messages.get("mathForm.decimalField").get(0).getText());
assertEquals("Decimal Field must be in format #,###.##", context.getMessages("mathForm.decimalField").get(0).getText());
}
}