diff --git a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java index f76aa320..f770eee8 100644 --- a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java +++ b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java @@ -26,6 +26,7 @@ import java.util.Map; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; import org.springframework.util.CachingMapDecorator; /** @@ -47,12 +48,36 @@ class DefaultMessageContext implements StateManageableMessageContext { this.messageSource = messageSource; } - public Serializable createMessagesMemento() { - return new HashMap(objectMessages); + // implementing message context + + public Message[] getAllMessages() { + List messages = new ArrayList(); + Iterator it = objectMessages.keySet().iterator(); + while (it.hasNext()) { + messages.addAll((List) objectMessages.get(it.next())); + } + return (Message[]) messages.toArray(new Message[messages.size()]); } - public void restoreMessages(Serializable messagesMemento) { - this.objectMessages.putAll((Map) messagesMemento); + public Message[] getMessagesBySource(Object source) { + List messages = (List) objectMessages.get(source); + return (Message[]) messages.toArray(new Message[messages.size()]); + } + + public Message[] getMessagesByCriteria(MessageCriteria criteria) { + Assert.notNull(criteria, "The message criteria is required"); + List messages = new ArrayList(); + Iterator it = objectMessages.values().iterator(); + while (it.hasNext()) { + List sourceMessages = (List) it.next(); + for (Iterator it2 = sourceMessages.iterator(); it2.hasNext();) { + Message message = (Message) it2.next(); + if (criteria.test(message)) { + messages.add(message); + } + } + } + return (Message[]) messages.toArray(new Message[messages.size()]); } public void addMessage(MessageResolver messageResolver) { @@ -62,24 +87,20 @@ class DefaultMessageContext implements StateManageableMessageContext { messages.add(message); } - public Message[] getMessages() { - List messages = new ArrayList(); - Iterator i = objectMessages.keySet().iterator(); - while (i.hasNext()) { - messages.addAll((List) objectMessages.get(i.next())); - } - return (Message[]) messages.toArray(new Message[messages.size()]); - } - - public Message[] getMessages(Object source) { - List messages = (List) objectMessages.get(source); - return (Message[]) messages.toArray(new Message[messages.size()]); - } - public void clearMessages() { objectMessages.clear(); } + // implementing state manageable message context + + public Serializable createMessagesMemento() { + return new HashMap(objectMessages); + } + + public void restoreMessages(Serializable messagesMemento) { + this.objectMessages.putAll((Map) messagesMemento); + } + public String toString() { return new ToStringCreator(this).append("objectMessages", objectMessages).toString(); } diff --git a/spring-binding/src/main/java/org/springframework/binding/message/MessageContext.java b/spring-binding/src/main/java/org/springframework/binding/message/MessageContext.java index 7440999c..8dee7754 100644 --- a/spring-binding/src/main/java/org/springframework/binding/message/MessageContext.java +++ b/spring-binding/src/main/java/org/springframework/binding/message/MessageContext.java @@ -9,14 +9,20 @@ public interface MessageContext { * Get all messages in this context. The messages returned should be suitable for display as-is. * @return the messages */ - public Message[] getMessages(); + public Message[] getAllMessages(); /** - * Get all messages in this context from the source provided. - * @param source the source that recorded the message + * Get all messages in this context for the source provided. + * @param source the source associated with messages, or null for global messages * @return the source's messages */ - public Message[] getMessages(Object source); + public Message[] getMessagesBySource(Object source); + + /** + * Get all messages that meet the given result criteria. + * @param criteria the message criteria + */ + public Message[] getMessagesByCriteria(MessageCriteria criteria); /** * Add a new message to this context. diff --git a/spring-binding/src/test/java/org/springframework/binding/message/DefaultMessageContextFactoryTests.java b/spring-binding/src/test/java/org/springframework/binding/message/DefaultMessageContextFactoryTests.java index d769a549..abf6de0c 100644 --- a/spring-binding/src/test/java/org/springframework/binding/message/DefaultMessageContextFactoryTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/message/DefaultMessageContextFactoryTests.java @@ -20,7 +20,7 @@ public class DefaultMessageContextFactoryTests extends TestCase { public void testCreateMessageContext() { MessageContext context = factory.createMessageContext(); context.addMessage(new MessageBuilder().defaultText("Hello world!").build()); - Message[] messages = context.getMessages(); + Message[] messages = context.getAllMessages(); assertEquals(1, messages.length); assertEquals("Hello world!", messages[0].getText()); assertEquals(Severity.INFO, messages[0].getSeverity()); @@ -30,7 +30,7 @@ public class DefaultMessageContextFactoryTests extends TestCase { public void testResolveMessage() { MessageContext context = factory.createMessageContext(); context.addMessage(new MessageBuilder().warning().source(this).code("message").build()); - Message[] messages = context.getMessages(this); + Message[] messages = context.getMessagesBySource(this); assertEquals(1, messages.length); assertEquals("Hello world resolved!", messages[0].getText()); assertEquals(Severity.WARNING, messages[0].getSeverity()); @@ -40,7 +40,7 @@ public class DefaultMessageContextFactoryTests extends TestCase { public void testResolveMessageDefaultText() { MessageContext context = factory.createMessageContext(); context.addMessage(new MessageBuilder().error().code("bogus").defaultText("Hello world fallback!").build()); - Message[] messages = context.getMessages(null); + Message[] messages = context.getAllMessages(); assertEquals(1, messages.length); assertEquals("Hello world fallback!", messages[0].getText()); assertEquals(Severity.ERROR, messages[0].getSeverity()); @@ -52,7 +52,7 @@ public class DefaultMessageContextFactoryTests extends TestCase { MessageContext context = factory.createMessageContext(); context.addMessage(new MessageBuilder().error().source(this).code("argmessage").arg("Keith").defaultText( "Hello world fallback!").build()); - Message[] messages = context.getMessages(this); + Message[] messages = context.getAllMessages(); assertEquals(1, messages.length); assertEquals("Hello world Keith!", messages[0].getText()); assertEquals(Severity.ERROR, messages[0].getSeverity()); @@ -64,7 +64,7 @@ public class DefaultMessageContextFactoryTests extends TestCase { MessageContext context = factory.createMessageContext(); context.addMessage(new MessageBuilder().error().source(this).codes(new String[] { "bogus", "argmessage" }) .args(new Object[] { "Keith" }).defaultText("Hello world fallback!").build()); - Message[] messages = context.getMessages(this); + Message[] messages = context.getMessagesBySource(this); assertEquals(1, messages.length); assertEquals("Hello world Keith!", messages[0].getText()); assertEquals(Severity.ERROR, messages[0].getSeverity()); @@ -77,16 +77,16 @@ public class DefaultMessageContextFactoryTests extends TestCase { context.addMessage(new MessageBuilder().defaultText("Info").build()); context.addMessage(new MessageBuilder().error().defaultText("Error").build()); context.addMessage(new MessageBuilder().warning().source(this).code("message").build()); - assertEquals(2, context.getMessages(null).length); - assertEquals(1, context.getMessages(this).length); + assertEquals(2, context.getMessagesBySource(null).length); + assertEquals(1, context.getMessagesBySource(this).length); assertTrue(context instanceof StateManageableMessageContext); StateManageableMessageContext manageable = (StateManageableMessageContext) context; Serializable messages = manageable.createMessagesMemento(); context = factory.createMessageContext(); - assertEquals(0, context.getMessages().length); + assertEquals(0, context.getAllMessages().length); manageable = (StateManageableMessageContext) context; manageable.restoreMessages(messages); - assertEquals(2, context.getMessages(null).length); - assertEquals(1, context.getMessages(this).length); + assertEquals(2, context.getMessagesBySource(null).length); + assertEquals(1, context.getMessagesBySource(this).length); } } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java index 066ed476..f2ba165f 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowFacesContext.java @@ -119,7 +119,7 @@ public class FlowFacesContext extends FacesContext { * associated with any specific UIComponent. If no such messages have been queued, return null. */ public FacesMessage.Severity getMaximumSeverity() { - if (context.getMessageContext().getMessages().length == 0) { + if (context.getMessageContext().getAllMessages().length == 0) { return null; } FacesMessage.Severity max = FacesMessage.SEVERITY_INFO; @@ -241,11 +241,11 @@ public class FlowFacesContext extends FacesContext { private int currentIndex = -1; protected FacesMessageIterator() { - this.messages = context.getMessageContext().getMessages(); + this.messages = context.getMessageContext().getAllMessages(); } protected FacesMessageIterator(String clientId) { - this.messages = context.getMessageContext().getMessages(clientId); + this.messages = context.getMessageContext().getMessagesBySource(clientId); } public boolean hasNext() { @@ -282,7 +282,7 @@ public class FlowFacesContext extends FacesContext { int currentIndex = -1; protected ClientIdIterator() { - this.messages = context.getMessageContext().getMessages(); + this.messages = context.getMessageContext().getAllMessages(); } public boolean hasNext() { diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java index bcc7dbc6..f5ede8a5 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/FlowFacesContextTests.java @@ -1,7 +1,5 @@ package org.springframework.faces.webflow; -import org.easymock.EasyMock; - import java.util.Iterator; import javax.faces.application.FacesMessage; @@ -9,8 +7,10 @@ import javax.faces.context.FacesContext; import junit.framework.TestCase; +import org.easymock.EasyMock; import org.springframework.binding.message.Message; import org.springframework.binding.message.MessageContext; +import org.springframework.binding.message.MessageCriteria; import org.springframework.binding.message.MessageResolver; import org.springframework.binding.message.Severity; import org.springframework.webflow.execution.RequestContext; @@ -106,17 +106,19 @@ public class FlowFacesContextTests extends TestCase { messageCount++; } - public Message[] getMessages() { + public Message[] getAllMessages() { return null; } - public Message[] getMessages(Object source) { - // TODO Auto-generated method stub + public Message[] getMessagesBySource(Object source) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Message[] getMessagesByCriteria(MessageCriteria criteria) { throw new UnsupportedOperationException("Auto-generated method stub"); } public void clearMessages() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Auto-generated method stub"); } } @@ -136,16 +138,19 @@ public class FlowFacesContextTests extends TestCase { } - public Message[] getMessages() { + public Message[] getAllMessages() { return messages; } - public Message[] getMessages(Object source) { + public Message[] getMessagesBySource(Object source) { return new Message[] { messages[1] }; } + public Message[] getMessagesByCriteria(MessageCriteria criteria) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + public void clearMessages() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Auto-generated method stub"); } diff --git a/spring-webflow-samples/booking-faces/.settings/org.eclipse.wst.common.component b/spring-webflow-samples/booking-faces/.settings/org.eclipse.wst.common.component index 1c05f365..cbbbabaf 100755 --- a/spring-webflow-samples/booking-faces/.settings/org.eclipse.wst.common.component +++ b/spring-webflow-samples/booking-faces/.settings/org.eclipse.wst.common.component @@ -4,7 +4,6 @@ - diff --git a/spring-webflow-samples/booking-mvc/.settings/org.eclipse.wst.common.component b/spring-webflow-samples/booking-mvc/.settings/org.eclipse.wst.common.component index 911151b0..297ca111 100755 --- a/spring-webflow-samples/booking-mvc/.settings/org.eclipse.wst.common.component +++ b/spring-webflow-samples/booking-mvc/.settings/org.eclipse.wst.common.component @@ -2,11 +2,8 @@ - - + - - diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java index e44bc8ab..5e42e88e 100755 --- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java +++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java @@ -63,7 +63,11 @@ public class Booking implements Serializable { @Transient public int getNights() { - return (int) (checkoutDate.getTime() - checkinDate.getTime()) / 1000 / 60 / 60 / 24; + if (checkinDate == null || checkoutDate == null) { + return 0; + } else { + return (int) (checkoutDate.getTime() - checkinDate.getTime()) / 1000 / 60 / 60 / 24; + } } @Id diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java index 6030a491..4afb9015 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java @@ -1,5 +1,6 @@ package org.springframework.webflow.mvc.view; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -11,7 +12,12 @@ import org.springframework.binding.format.FormatterRegistry; import org.springframework.binding.mapping.MappingResult; import org.springframework.binding.mapping.MappingResults; import org.springframework.binding.mapping.MappingResultsCriteria; +import org.springframework.binding.message.Message; import org.springframework.binding.message.MessageContext; +import org.springframework.binding.message.MessageCriteria; +import org.springframework.binding.message.Severity; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; public class BindingModel extends ViewRenderingErrors { @@ -23,11 +29,14 @@ public class BindingModel extends ViewRenderingErrors { private MappingResults mappingResults; + private MessageContext messageContext; + public BindingModel(Object boundObject, ExpressionParser expressionParser, FormatterRegistry formatterRegistry, MessageContext messageContext) { this.boundObject = boundObject; this.expressionParser = expressionParser; this.formatterRegistry = formatterRegistry; + this.messageContext = messageContext; } public void setMappingResults(MappingResults results) { @@ -35,18 +44,15 @@ public class BindingModel extends ViewRenderingErrors { } public List getAllErrors() { - // TODO - return Collections.EMPTY_LIST; + return toErrors(messageContext.getMessagesByCriteria(ERRORS_ANY_SOURCE)); } public List getGlobalErrors() { - // TODO - return Collections.EMPTY_LIST; + return toErrors(messageContext.getMessagesByCriteria(ERRORS_NULL_SOURCE)); } public List getFieldErrors(String field) { - // TODO - return Collections.EMPTY_LIST; + return toErrors(messageContext.getMessagesByCriteria(new FieldErrorMessage(field))); } public Class getFieldType(String field) { @@ -61,15 +67,14 @@ public class BindingModel extends ViewRenderingErrors { return fieldError.getResult().getOriginalValue(); } } - return getFormattedValue(field); + return getFormattedValue(parseFieldExpression(field)); } private Expression parseFieldExpression(String field) { return expressionParser.parseExpression(field, new FluentParserContext().evaluate(boundObject.getClass())); } - private Object getFormattedValue(String field) { - Expression fieldExpression = parseFieldExpression(field); + private Object getFormattedValue(Expression fieldExpression) { Formatter formatter = getFormatter(fieldExpression); if (formatter != null) { return formatter.format(fieldExpression.getValue(boundObject)); @@ -87,6 +92,23 @@ public class BindingModel extends ViewRenderingErrors { } } + private List toErrors(Message[] messages) { + if (messages == null || messages.length == 0) { + return Collections.EMPTY_LIST; + } + ArrayList errors = new ArrayList(messages.length); + for (int i = 0; i < messages.length; i++) { + Message message = messages[i]; + if (message.getSource() == null) { + errors.add(new ObjectError("boundObject", null, null, message.getText())); + } else { + errors.add(new FieldError("boundObject", (String) message.getSource(), null, false, null, null, message + .getText())); + } + } + return errors; + } + private static class FieldErrorResult implements MappingResultsCriteria { private String field; @@ -104,4 +126,28 @@ public class BindingModel extends ViewRenderingErrors { } } + private static final MessageCriteria ERRORS_ANY_SOURCE = new MessageCriteria() { + public boolean test(Message message) { + return message.getSeverity() == Severity.ERROR; + } + }; + + private static final MessageCriteria ERRORS_NULL_SOURCE = new MessageCriteria() { + public boolean test(Message message) { + return message.getSource() == null && message.getSeverity() == Severity.ERROR; + } + }; + + private static class FieldErrorMessage implements MessageCriteria { + private String field; + + public FieldErrorMessage(String field) { + this.field = field; + } + + public boolean test(Message message) { + return field.equals(message.getSource()) && message.getSeverity() == Severity.ERROR; + } + } + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcView.java index 7cdbb604..445d3184 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcView.java @@ -18,6 +18,7 @@ package org.springframework.webflow.mvc.view; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -39,7 +40,8 @@ import org.springframework.binding.mapping.MappingResultsCriteria; import org.springframework.binding.mapping.impl.DefaultMapper; import org.springframework.binding.mapping.impl.DefaultMapping; import org.springframework.binding.mapping.impl.DefaultMappingContext; -import org.springframework.binding.mapping.results.TargetAccessError; +import org.springframework.binding.message.MessageBuilder; +import org.springframework.binding.message.MessageResolver; import org.springframework.validation.BindingResult; import org.springframework.webflow.core.collection.ParameterMap; import org.springframework.webflow.definition.TransitionDefinition; @@ -51,7 +53,9 @@ import org.springframework.webflow.expression.DefaultExpressionParserFactory; class MvcView implements View { - private static final MappingResultsCriteria PROPERTY_NOT_FOUND_ERRORS = new PropertyNotFoundErrors(); + private static final MappingResultsCriteria PROPERTY_NOT_FOUND_ERROR = new PropertyNotFoundError(); + + private static final MappingResultsCriteria MAPPING_ERROR = new MappingError(); private org.springframework.web.servlet.View view; @@ -113,6 +117,7 @@ class MvcView implements View { mappingResults = bind(model); if (mappingResults.hasErrorResults() && !onlyPropertyNotFoundErrorsPresent(mappingResults)) { viewErrors = true; + addErrorMessages(mappingResults); } } } @@ -187,7 +192,22 @@ class MvcView implements View { } private boolean onlyPropertyNotFoundErrorsPresent(MappingResults results) { - return results.getResults(PROPERTY_NOT_FOUND_ERRORS).size() == mappingResults.getErrorResults().size(); + return results.getResults(PROPERTY_NOT_FOUND_ERROR).size() == mappingResults.getErrorResults().size(); + } + + private void addErrorMessages(MappingResults results) { + List errors = results.getResults(MAPPING_ERROR); + for (Iterator it = errors.iterator(); it.hasNext();) { + MappingResult error = (MappingResult) it.next(); + context.getMessageContext().addMessage(message(error)); + } + } + + private MessageResolver message(MappingResult error) { + String field = error.getMapping().getTargetExpression().getExpressionString(); + String errorCode = error.getResult().getErrorCode(); + return new MessageBuilder().error().source(field).code(errorCode).arg(field).defaultText( + errorCode + " on " + field).build(); } private void determineEventId(RequestContext context) { @@ -237,10 +257,15 @@ class MvcView implements View { return null; } - private static class PropertyNotFoundErrors implements MappingResultsCriteria { + private static class PropertyNotFoundError implements MappingResultsCriteria { public boolean test(MappingResult result) { - return result.getResult() instanceof TargetAccessError - && result.getResult().getErrorCode().equals("propertyNotFound"); + return result.getResult().isError() && "propertyNotFound".equals(result.getResult().getErrorCode()); + } + } + + private static class MappingError implements MappingResultsCriteria { + public boolean test(MappingResult result) { + return result.getResult().isError() && !PROPERTY_NOT_FOUND_ERROR.test(result); } }