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 b74306e0..923fa006 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 @@ -23,6 +23,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.style.ToStringCreator; @@ -36,6 +38,8 @@ import org.springframework.util.CachingMapDecorator; */ class DefaultMessageContext implements StateManageableMessageContext { + private static final Log logger = LogFactory.getLog(DefaultMessageContext.class); + private MessageSource messageSource; private Map objectMessages = new CachingMapDecorator() { @@ -95,8 +99,14 @@ class DefaultMessageContext implements StateManageableMessageContext { public void addMessage(MessageResolver messageResolver) { Locale currentLocale = LocaleContextHolder.getLocale(); + if (logger.isDebugEnabled()) { + logger.debug("Resolving message using " + messageResolver); + } Message message = messageResolver.resolveMessage(messageSource, currentLocale); List messages = (List) objectMessages.get(message.getSource()); + if (logger.isDebugEnabled()) { + logger.debug("Adding resolved message " + message); + } messages.add(message); } diff --git a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContextFactory.java index 2ec9d3f0..feed725d 100644 --- a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContextFactory.java +++ b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContextFactory.java @@ -1,8 +1,10 @@ package org.springframework.binding.message; +import java.text.MessageFormat; +import java.util.Locale; import org.springframework.context.MessageSource; -import org.springframework.util.Assert; +import org.springframework.context.support.AbstractMessageSource; /** * Default message context factory that simply stores messages indexed in a map by their source. Suitable for use in @@ -20,11 +22,19 @@ public class DefaultMessageContextFactory implements MessageContextFactory { * @param messageSource */ public DefaultMessageContextFactory(MessageSource messageSource) { - Assert.notNull(messageSource, "The message source is required"); + if (messageSource == null) { + messageSource = new DefaultTextFallbackMessageSource(); + } this.messageSource = messageSource; } public StateManageableMessageContext createMessageContext() { return new DefaultMessageContext(messageSource); } + + private class DefaultTextFallbackMessageSource extends AbstractMessageSource { + protected MessageFormat resolveCode(String code, Locale locale) { + return null; + } + } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageResolver.java b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageResolver.java new file mode 100644 index 00000000..103cfa72 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageResolver.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2007 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.binding.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 Object source; + private String[] codes; + private Severity severity; + private Object[] args; + private String defaultText; + + public DefaultMessageResolver(Object source, String[] codes, Severity severity, Object[] args, + String defaultText) { + this.source = source; + this.codes = codes; + this.severity = severity; + this.args = args; + this.defaultText = defaultText; + } + + public Message resolveMessage(MessageSource messageSource, Locale locale) { + return new Message(source, messageSource.getMessage(this, locale), severity); + } + + // 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("source", source).append("severity", severity).append("codes", + codes).append("args", args).append("defaultText", defaultText).toString(); + } +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/message/MessageBuilder.java b/spring-binding/src/main/java/org/springframework/binding/message/MessageBuilder.java index bc6596e8..e768a3d2 100644 --- a/spring-binding/src/main/java/org/springframework/binding/message/MessageBuilder.java +++ b/spring-binding/src/main/java/org/springframework/binding/message/MessageBuilder.java @@ -4,11 +4,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; 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 @@ -170,41 +170,7 @@ public class MessageBuilder { } String[] codesArray = (String[]) codes.toArray(new String[codes.size()]); Object[] argsArray = args.toArray(new Object[args.size()]); - return new BuiltMessageResolver(source, codesArray, severity, argsArray, defaultText); - } - - private static class BuiltMessageResolver implements MessageResolver, MessageSourceResolvable { - private Object source; - private String[] codes; - private Severity severity; - private Object[] args; - private String defaultText; - - public BuiltMessageResolver(Object source, String[] codes, Severity severity, Object[] args, String defaultText) { - this.source = source; - this.codes = codes; - this.severity = severity; - this.args = args; - this.defaultText = defaultText; - } - - public Message resolveMessage(MessageSource messageSource, Locale locale) { - return new Message(source, messageSource.getMessage(this, locale), severity); - } - - // implementing MessageSourceResolver - - public String[] getCodes() { - return codes; - } - - public Object[] getArguments() { - return args; - } - - public String getDefaultMessage() { - return defaultText; - } + return new DefaultMessageResolver(source, codesArray, severity, argsArray, defaultText); } private static class ResolvableArgument implements MessageSourceResolvable { @@ -227,6 +193,10 @@ public class MessageBuilder { return arg.toString(); } + public String toString() { + return new ToStringCreator(this).append("arg", arg).toString(); + } + } } diff --git a/spring-binding/src/test/resources/log4j.xml b/spring-binding/src/test/resources/log4j.xml new file mode 100644 index 00000000..767b96d6 --- /dev/null +++ b/spring-binding/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-faces/src/main/java/org/springframework/faces/model/ManySelectionTrackingListDataModel.java b/spring-faces/src/main/java/org/springframework/faces/model/ManySelectionTrackingListDataModel.java index af38353f..70494509 100644 --- a/spring-faces/src/main/java/org/springframework/faces/model/ManySelectionTrackingListDataModel.java +++ b/spring-faces/src/main/java/org/springframework/faces/model/ManySelectionTrackingListDataModel.java @@ -32,6 +32,14 @@ public class ManySelectionTrackingListDataModel extends SerializableListDataMode private List selections = new ArrayList(); + public ManySelectionTrackingListDataModel() { + super(); + } + + public ManySelectionTrackingListDataModel(List list) { + super(list); + } + public List getSelections() { return selections; } diff --git a/spring-faces/src/main/java/org/springframework/faces/model/OneSelectionTrackingListDataModel.java b/spring-faces/src/main/java/org/springframework/faces/model/OneSelectionTrackingListDataModel.java index 757d66c9..788bb44c 100644 --- a/spring-faces/src/main/java/org/springframework/faces/model/OneSelectionTrackingListDataModel.java +++ b/spring-faces/src/main/java/org/springframework/faces/model/OneSelectionTrackingListDataModel.java @@ -31,6 +31,14 @@ public class OneSelectionTrackingListDataModel extends SerializableListDataModel private List selections = new ArrayList(); + public OneSelectionTrackingListDataModel() { + super(); + } + + public OneSelectionTrackingListDataModel(List list) { + super(list); + } + public List getSelections() { return selections; } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java index 306570a2..c8ae6fdf 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java @@ -31,8 +31,9 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.binding.expression.Expression; +import org.springframework.context.ApplicationContext; import org.springframework.core.io.ContextResource; -import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.Resource; import org.springframework.faces.ui.AjaxViewRoot; import org.springframework.webflow.context.ExternalContext; import org.springframework.webflow.execution.RequestContext; @@ -56,13 +57,10 @@ public class JsfViewFactory implements ViewFactory { private final Expression viewIdExpression; - private final ResourceLoader resourceLoader; - private final Lifecycle lifecycle; - public JsfViewFactory(Expression viewIdExpression, ResourceLoader resourceLoader, Lifecycle lifecycle) { + public JsfViewFactory(Expression viewIdExpression, Lifecycle lifecycle) { this.viewIdExpression = viewIdExpression; - this.resourceLoader = resourceLoader; this.lifecycle = lifecycle; } @@ -141,8 +139,16 @@ public class JsfViewFactory implements ViewFactory { if (viewId.startsWith("/")) { return viewId; } else { - ContextResource viewResource = (ContextResource) resourceLoader.getResource(viewId); - return viewResource.getPathWithinContext(); + ApplicationContext flowContext = context.getActiveFlow().getApplicationContext(); + if (flowContext == null) { + throw new IllegalStateException("A Flow ApplicationContext is required to resolve Flow View Resources"); + } + Resource viewResource = flowContext.getResource(viewId); + if (!(viewResource instanceof ContextResource)) { + throw new IllegalStateException( + "A ContextResource is required to get relative view paths within this context"); + } + return ((ContextResource) viewResource).getPathWithinContext(); } } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java index 56725ffc..060ac6b4 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactoryCreator.java @@ -20,7 +20,6 @@ import javax.faces.lifecycle.Lifecycle; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; -import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.engine.builder.ViewFactoryCreator; import org.springframework.webflow.execution.ViewFactory; @@ -36,8 +35,8 @@ public class JsfViewFactoryCreator implements ViewFactoryCreator { private Lifecycle lifecycle; public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ResourceLoader resourceLoader) { - return new JsfViewFactory(viewIdExpression, resourceLoader, getLifecycle()); + FormatterRegistry formatterRegistry) { + return new JsfViewFactory(viewIdExpression, getLifecycle()); } public String getViewIdByConvention(String viewStateId) { diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfFinalResponseActionTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfFinalResponseActionTests.java index 690f7270..adf32a69 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfFinalResponseActionTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfFinalResponseActionTests.java @@ -70,8 +70,8 @@ public class JsfFinalResponseActionTests extends TestCase { jsfMock.facesContext().setViewRoot(null); jsfMock.facesContext().getApplication().setViewHandler(viewHandler); lifecycle = new TestLifecycle(jsfMock.lifecycle()); - factory = new JsfViewFactory(parser.parseExpression("#{'" + VIEW_ID + "'}", new FluentParserContext().template() - .evaluate(RequestContext.class).expectResult(String.class)), null, lifecycle); + factory = new JsfViewFactory(parser.parseExpression("#{'" + VIEW_ID + "'}", new FluentParserContext() + .template().evaluate(RequestContext.class).expectResult(String.class)), lifecycle); RequestContextHolder.setRequestContext(context); MockExternalContext ext = new MockExternalContext(); ext.setNativeContext(new MockServletContext()); diff --git a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java index 5345abec..d5989bfc 100644 --- a/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/webflow/JsfViewFactoryTests.java @@ -94,7 +94,7 @@ public class JsfViewFactoryTests extends TestCase { lifecycle = new NoExecutionLifecycle(jsfMock.lifecycle()); factory = new JsfViewFactory(parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate( - RequestContext.class).expectResult(String.class)), null, lifecycle); + RequestContext.class).expectResult(String.class)), lifecycle); UIViewRoot newRoot = new UIViewRoot(); newRoot.setViewId(VIEW_ID); @@ -117,7 +117,7 @@ public class JsfViewFactoryTests extends TestCase { lifecycle = new NoExecutionLifecycle(jsfMock.lifecycle()); factory = new JsfViewFactory(parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate( - RequestContext.class).expectResult(String.class)), null, lifecycle); + RequestContext.class).expectResult(String.class)), lifecycle); UIViewRoot existingRoot = new UIViewRoot(); existingRoot.setViewId(VIEW_ID); @@ -141,7 +141,7 @@ public class JsfViewFactoryTests extends TestCase { lifecycle = new NoExecutionLifecycle(jsfMock.lifecycle()); factory = new JsfViewFactory(parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate( - RequestContext.class).expectResult(String.class)), null, lifecycle); + RequestContext.class).expectResult(String.class)), lifecycle); UIViewRoot existingRoot = new UIViewRoot(); existingRoot.setViewId(VIEW_ID); @@ -169,7 +169,7 @@ public class JsfViewFactoryTests extends TestCase { lifecycle = new NoExecutionLifecycle(jsfMock.lifecycle()); factory = new JsfViewFactory(parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate( - RequestContext.class).expectResult(String.class)), null, lifecycle); + RequestContext.class).expectResult(String.class)), lifecycle); UIViewRoot newRoot = new UIViewRoot(); newRoot.setViewId(VIEW_ID); diff --git a/spring-webflow-samples/booking-faces/.classpath b/spring-webflow-samples/booking-faces/.classpath index 5abb4eb7..35e84375 100755 --- a/spring-webflow-samples/booking-faces/.classpath +++ b/spring-webflow-samples/booking-faces/.classpath @@ -7,5 +7,6 @@ + diff --git a/spring-webflow-samples/booking-faces/src/test/java/org/springframework/webflow/samples/booking/MainFlowExecutionTests.java b/spring-webflow-samples/booking-faces/src/test/java/org/springframework/webflow/samples/booking/MainFlowExecutionTests.java index c8a4392a..463e7f86 100644 --- a/spring-webflow-samples/booking-faces/src/test/java/org/springframework/webflow/samples/booking/MainFlowExecutionTests.java +++ b/spring-webflow-samples/booking-faces/src/test/java/org/springframework/webflow/samples/booking/MainFlowExecutionTests.java @@ -6,9 +6,15 @@ import java.util.List; import javax.faces.model.DataModel; import org.easymock.EasyMock; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; +import org.springframework.faces.model.OneSelectionTrackingListDataModel; import org.springframework.faces.model.converter.FacesConversionService; import org.springframework.webflow.config.FlowDefinitionResource; import org.springframework.webflow.config.FlowDefinitionResourceFactory; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.engine.EndState; +import org.springframework.webflow.engine.Flow; import org.springframework.webflow.test.MockExternalContext; import org.springframework.webflow.test.MockFlowBuilderContext; import org.springframework.webflow.test.execution.AbstractXmlFlowExecutionTests; @@ -32,7 +38,7 @@ public class MainFlowExecutionTests extends AbstractXmlFlowExecutionTests { builderContext.getFlowBuilderServices().setConversionService(new FacesConversionService()); } - public void testStart() { + public void testStartMainFlow() { List bookings = new ArrayList(); bookings.add(new Booking(new Hotel(), new User("keith", "password", "Keith Donald"))); EasyMock.expect(bookingService.findBookings("keith")).andReturn(bookings); @@ -42,31 +48,80 @@ public class MainFlowExecutionTests extends AbstractXmlFlowExecutionTests { context.setCurrentUser("keith"); startFlow(context); assertCurrentStateEquals("enterSearchCriteria"); + assertResponseWrittenEquals("enterSearchCriteria", context); assertTrue(getRequiredFlowAttribute("searchCriteria") instanceof SearchCriteria); assertTrue(getRequiredViewAttribute("bookings") instanceof DataModel); EasyMock.verify(bookingService); } - // public void testSearch() { - // setCurrentState("enterSearchCriteria"); - // SearchCriteria criteria = new SearchCriteria(); - // criteria.setSearchString("Jameson"); - // getFlowScope().put("searchCriteria", criteria); - // - // List hotels = new ArrayList(); - // hotels.add(new Hotel()); - // EasyMock.expect(bookingService.findHotels(criteria)).andReturn(hotels); - // EasyMock.replay(bookingService); - // - // MockExternalContext context = new MockExternalContext(); - // context.setEventId("search"); - // resumeFlow(context); - // - // EasyMock.verify(bookingService); - // - // assertCurrentStateEquals("reviewHotels"); - // assertTrue(getRequiredViewAttribute("hotels") instanceof DataModel); - // } + public void testSearchHotels() { + setCurrentState("enterSearchCriteria"); + SearchCriteria criteria = new SearchCriteria(); + criteria.setSearchString("Jameson"); + getFlowScope().put("searchCriteria", criteria); + + List hotels = new ArrayList(); + hotels.add(new Hotel()); + EasyMock.expect(bookingService.findHotels(criteria)).andReturn(hotels); + EasyMock.replay(bookingService); + + MockExternalContext context = new MockExternalContext(); + context.setEventId("search"); + resumeFlow(context); + + EasyMock.verify(bookingService); + + assertCurrentStateEquals("reviewHotels"); + assertResponseWrittenEquals("reviewHotels", context); + assertTrue(getRequiredViewAttribute("hotels") instanceof DataModel); + } + + public void testSelectHotel() { + setCurrentState("reviewHotels"); + + List hotels = new ArrayList(); + Hotel hotel = new Hotel(); + hotel.setId(1L); + hotel.setName("Jameson Inn"); + hotels.add(hotel); + OneSelectionTrackingListDataModel dataModel = new OneSelectionTrackingListDataModel(hotels); + dataModel.select(hotel); + getViewScope().put("hotels", dataModel); + + MockExternalContext context = new MockExternalContext(); + context.setEventId("select"); + resumeFlow(context); + + assertCurrentStateEquals("reviewHotel"); + assertNull(getFlowAttribute("hotels")); + assertSame(hotel, getFlowAttribute("hotel")); + } + + public void testBookHotel() { + setCurrentState("reviewHotel"); + + Hotel hotel = new Hotel(); + hotel.setId(1L); + hotel.setName("Jameson Inn"); + getFlowScope().put("hotel", hotel); + + Flow mockBookingFlow = new Flow("booking"); + mockBookingFlow.setInputMapper(new Mapper() { + public MappingResults map(Object source, Object target) { + assertEquals(new Long(1), ((AttributeMap) source).get("hotelId")); + return null; + } + }); + new EndState(mockBookingFlow, "bookingConfirmed"); + getFlowDefinitionRegistry().registerFlowDefinition(mockBookingFlow); + + MockExternalContext context = new MockExternalContext(); + context.setEventId("book"); + resumeFlow(context); + + assertFlowExecutionEnded(); + assertFlowExecutionOutcomeEquals("finish"); + } } diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp index 1521340b..cc6dfc05 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/enterBookingDetails.jsp @@ -45,7 +45,6 @@
-
@@ -54,7 +53,6 @@
-
@@ -84,7 +82,6 @@
-
@@ -93,7 +90,6 @@
-
diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/messages.properties b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/messages.properties new file mode 100644 index 00000000..692328a7 --- /dev/null +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/messages.properties @@ -0,0 +1,2 @@ +booking.checkinDate.typeMismatch=The Check In Date must be in the format dd/mm/yy +booking.checkoutDate.typeMismatch=The Check Out Date must be in the format dd/mm/yy \ No newline at end of file diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml index 4a664641..f7b12c5f 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/web-application-config.xml @@ -1,7 +1,7 @@ - - - - - + + + + + + - - - - + + + + diff --git a/spring-webflow-samples/booking-mvc/src/test/resources/log4j.properties b/spring-webflow-samples/booking-mvc/src/test/resources/log4j.properties deleted file mode 100755 index 5ba9222c..00000000 --- a/spring-webflow-samples/booking-mvc/src/test/resources/log4j.properties +++ /dev/null @@ -1,9 +0,0 @@ -log4j.rootCategory=WARN, stdout - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n - -# Enable web flow logging -log4j.category.org.springframework.webflow=DEBUG -log4j.category.org.springframework.binding=DEBUG \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutionRepositoryType.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutionRepositoryType.java index da7fc663..988b78ed 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutionRepositoryType.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutionRepositoryType.java @@ -36,15 +36,10 @@ public class FlowExecutionRepositoryType extends StaticLabeledEnum { */ public static final FlowExecutionRepositoryType CONTINUATION = new FlowExecutionRepositoryType(1, "Continuation"); - /** - * The 'client' (continuation) flow execution repository type. - */ - public static final FlowExecutionRepositoryType CLIENT = new FlowExecutionRepositoryType(2, "Client"); - /** * The 'singleKey' flow execution repository type. */ - public static final FlowExecutionRepositoryType SINGLEKEY = new FlowExecutionRepositoryType(3, "Single Key"); + public static final FlowExecutionRepositoryType SINGLEKEY = new FlowExecutionRepositoryType(2, "Single Key"); /** * Private constructor because this is a typesafe enum! diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java index efc2f6a3..6f6e1a2b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java @@ -261,36 +261,29 @@ class FlowExecutorFactoryBean implements FactoryBean, InitializingBean { */ protected FlowExecutionRepository createFlowExecutionRepository(FlowExecutionRepositoryType repositoryType, FlowExecutionStateRestorer executionStateRestorer, ConversationManager conversationManager) { - if (repositoryType == FlowExecutionRepositoryType.CLIENT) { - throw new UnsupportedOperationException( - "The 'client' flow execution repository is not supported in this 2.0 Milestone; support is planned for a future release"); + if (conversationManager == null) { + conversationManager = createDefaultConversationManager(); + } + if (repositoryType == FlowExecutionRepositoryType.CONTINUATION) { + DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(conversationManager, + executionStateRestorer); + if (maxContinuations != null) { + repository.setMaxContinuations(maxContinuations.intValue()); + } + return repository; + } else if (repositoryType == FlowExecutionRepositoryType.SIMPLE) { + DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(conversationManager, + executionStateRestorer); + repository.setMaxContinuations(1); + return repository; + } else if (repositoryType == FlowExecutionRepositoryType.SINGLEKEY) { + DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(conversationManager, + executionStateRestorer); + repository.setAlwaysGenerateNewNextKey(false); + return repository; } else { - // determine the conversation manager to use - ConversationManager conversationManagerToUse = conversationManager; - if (conversationManagerToUse == null) { - conversationManagerToUse = createDefaultConversationManager(); - } - if (repositoryType == FlowExecutionRepositoryType.SIMPLE) { - DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository( - conversationManagerToUse, executionStateRestorer); - repository.setMaxContinuations(1); - return repository; - } else if (repositoryType == FlowExecutionRepositoryType.CONTINUATION) { - DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository( - conversationManagerToUse, executionStateRestorer); - if (maxContinuations != null) { - repository.setMaxContinuations(maxContinuations.intValue()); - } - return repository; - } else if (repositoryType == FlowExecutionRepositoryType.SINGLEKEY) { - DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository( - conversationManagerToUse, executionStateRestorer); - repository.setAlwaysGenerateNewNextKey(false); - return repository; - } else { - throw new IllegalStateException("Cannot create execution repository - unsupported repository type " - + repositoryType); - } + throw new IllegalStateException("Cannot create execution repository - unsupported repository type " + + repositoryType); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd index 6661f713..189b7b64 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/spring-webflow-config-2.0.xsd @@ -313,16 +313,6 @@ of the browser back button. This is the default if not specified. - - - - - - - diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java index 2889f724..bf9877b5 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java @@ -15,6 +15,8 @@ */ package org.springframework.webflow.definition.registry; +import org.springframework.webflow.definition.FlowDefinition; + /** * A container of flow definitions. Extends {@link FlowDefinitionLocator} for accessing registered Flow definitions for * execution at runtime. @@ -41,4 +43,10 @@ public interface FlowDefinitionRegistry extends FlowDefinitionLocator { */ public void registerFlowDefinition(FlowDefinitionHolder definitionHolder); + /** + * Register a flow definition in this registry. + * @param definition the actual flow definition + */ + public void registerFlowDefinition(FlowDefinition definition); + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java index 59f98b54..a7af2eb2 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowVariable.java @@ -33,12 +33,6 @@ public class FlowVariable extends AnnotatedObject { */ private String name; - /** - * Is this flow variable local or global? Local variables go into flow scope. Global variables go into conversation - * scope. - */ - private Boolean local; - /** * The value factory that provides this variable's value. */ @@ -47,14 +41,12 @@ public class FlowVariable extends AnnotatedObject { /** * Creates a new flow variable. * @param name the variable name - * @param local the local variable */ - public FlowVariable(String name, VariableValueFactory valueFactory, boolean local) { + public FlowVariable(String name, VariableValueFactory valueFactory) { Assert.hasText(name, "The variable name is required"); Assert.notNull(valueFactory, "The variable value factory is required"); this.name = name; this.valueFactory = valueFactory; - this.local = Boolean.valueOf(local); } /** @@ -64,13 +56,6 @@ public class FlowVariable extends AnnotatedObject { return name; } - /** - * Is this a local flow variable or a conversation-scoped flow variable? - */ - public boolean isLocal() { - return local.booleanValue(); - } - // name and scope based equality public boolean equals(Object o) { @@ -78,11 +63,11 @@ public class FlowVariable extends AnnotatedObject { return false; } FlowVariable other = (FlowVariable) o; - return name.equals(other.name) && valueFactory.equals(other.valueFactory) && local.equals(other.local); + return name.equals(other.name) && valueFactory.equals(other.valueFactory); } public int hashCode() { - return name.hashCode() + valueFactory.hashCode() + local.hashCode(); + return name.hashCode() + valueFactory.hashCode(); } /** @@ -91,11 +76,7 @@ public class FlowVariable extends AnnotatedObject { */ public void create(RequestContext context) { Object value = valueFactory.createInitialValue(context); - if (local == Boolean.TRUE) { - context.getFlowScope().put(name, value); - } else { - context.getConversationScope().put(name, value); - } + context.getFlowScope().put(name, value); } /** @@ -104,12 +85,7 @@ public class FlowVariable extends AnnotatedObject { * @param context the executing flow */ public void restore(RequestContext context) { - Object value; - if (local == Boolean.TRUE) { - value = context.getFlowScope().get(name); - } else { - value = context.getConversationScope().get(name); - } + Object value = context.getFlowScope().get(name); valueFactory.restoreReferences(value, context); } @@ -118,15 +94,10 @@ public class FlowVariable extends AnnotatedObject { * @param context the executing flow */ public Object destroy(RequestContext context) { - if (local == Boolean.TRUE) { - return context.getFlowScope().remove(name); - } else { - return context.getConversationScope().remove(name); - } + return context.getFlowScope().remove(name); } public String toString() { - return new ToStringCreator(this).append("name", name).append("valueFactory", valueFactory).append("local", - local).toString(); + return new ToStringCreator(this).append("name", name).append("valueFactory", valueFactory).toString(); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/History.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/History.java new file mode 100644 index 00000000..4eaa71fe --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/History.java @@ -0,0 +1,27 @@ +package org.springframework.webflow.engine; + +import org.springframework.core.enums.StaticLabeledEnum; + +public class History extends StaticLabeledEnum { + + /** + * The history of the view state should be preserved when the view state exits to support back-tracking. + */ + public static final History PRESERVE = new History(0, "preserve"); + + /** + * The history of the view state should be discarded when the view state exits to prevent back-tracking. + */ + public static final History DISCARD = new History(1, "discard"); + + /** + * The history of the view state and all previous view state should be invalidated to completely restrict back + * tracking. + */ + public static final History INVALIDATE = new History(2, "invalidate"); + + public History(int code, String label) { + super(code, label); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java index 31804409..d928b6fb 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/RequestControlContext.java @@ -53,6 +53,47 @@ public interface RequestControlContext extends RequestContext { */ public void setCurrentState(State state); + /** + * Assign the ongoing flow execution its flow execution key. This method will be called before a state is about to + * render a view and pause the flow execution. + */ + public FlowExecutionKey assignFlowExecutionKey(); + + /** + * Update the current flow execution snapshot to save the current state. + */ + public void updateCurrentFlowExecutionSnapshot(); + + /** + * Remove the current flow execution snapshot to invalidate the current state. + */ + public void removeCurrentFlowExecutionSnapshot(); + + /** + * Remove all flow execution snapshots associated with the ongoing conversation. Invalidates previous states. + */ + public void removeAllFlowExecutionSnapshots(); + + /** + * Signals the occurrence of an event in the current state of this flow execution request context. This method + * should be called by clients that report internal event occurrences, such as action states. The + * onEvent() method of the flow involved in the flow execution will be called. + * @param event the event that occurred + * @return a boolean indicating if handling this event caused the current state to exit and a new state to enter + * @throws FlowExecutionException if an exception was thrown within a state of the flow during execution of this + * signalEvent operation + * @see Flow#handleEvent(RequestControlContext) + */ + public boolean handleEvent(Event event) throws FlowExecutionException; + + /** + * Execute this transition out of the current source state. Allows for privileged execution of an arbitrary + * transition. + * @param transition the transition + * @see Transition#execute(State, RequestControlContext) + */ + public boolean execute(Transition transition); + /** * Record the transition executing in the flow. This method will be called as part of executing a transition from * one state to another. @@ -61,12 +102,6 @@ public interface RequestControlContext extends RequestContext { */ public void setCurrentTransition(Transition transition); - /** - * Assign the ongoing flow execution its flow execution key. This method will be called before a state is about to - * render a view and pause the flow execution. - */ - public FlowExecutionKey assignFlowExecutionKey(); - /** * Spawn a new flow session and activate it in the currently executing flow. Also transitions the spawned flow to * its start state. This method should be called by clients that wish to spawn new flows, such as subflow states. @@ -80,18 +115,6 @@ public interface RequestControlContext extends RequestContext { */ public void start(Flow flow, MutableAttributeMap input) throws FlowExecutionException; - /** - * Signals the occurrence of an event in the current state of this flow execution request context. This method - * should be called by clients that report internal event occurrences, such as action states. The - * onEvent() method of the flow involved in the flow execution will be called. - * @param event the event that occurred - * @return a boolean indicating if handling this event caused the current state to exit and a new state to enter - * @throws FlowExecutionException if an exception was thrown within a state of the flow during execution of this - * signalEvent operation - * @see Flow#handleEvent(RequestControlContext) - */ - public boolean handleEvent(Event event) throws FlowExecutionException; - /** * End the active flow session of the current flow execution. This method should be called by clients that terminate * flows, such as end states. The end() method of the flow involved in the flow execution will be @@ -103,14 +126,6 @@ public interface RequestControlContext extends RequestContext { */ public FlowSession endActiveFlowSession(MutableAttributeMap output) throws IllegalStateException; - /** - * Execute this transition out of the current source state. Allows for privileged execution of an arbitrary - * transition. - * @param transition the transition - * @see Transition#execute(State, RequestControlContext) - */ - public boolean execute(Transition transition); - /** * Returns true if the 'always redirect pause' flow execution attribute is set to true, false otherwise. * @return true or false diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java index 462bbfaf..1d9c1ba2 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java @@ -40,7 +40,7 @@ import org.springframework.webflow.execution.ViewFactory; public class ViewState extends TransitionableState { /** - * The list of actions to be executed when this state is entered. + * The list of actions to be executed before the view is rendered. */ private ActionList renderActionList = new ActionList(); @@ -59,6 +59,12 @@ public class ViewState extends TransitionableState { */ private Boolean redirect; + /** + * An enum indicating the history behavior of this view-state. Used to configure back-tracking policies. Default is + * {@link History#PRESERVE}. + */ + private History history = History.PRESERVE; + /** * Whether or not the view should render as a popup. */ @@ -146,6 +152,24 @@ public class ViewState extends TransitionableState { this.popup = popup; } + /** + * Returns the history behavior of this view-state. Used to configure back-tracking policies. Default is + * {@link History#PRESERVE}. + * @return the history + */ + public History getHistory() { + return history; + } + + /** + * Sets the history behavior of this view state. Used to configure back-tracking policies. Default is + * {@link History#PRESERVE}. + * @param history the history + */ + public void setHistory(History history) { + this.history = history; + } + /** * Returns the view factory. */ @@ -201,8 +225,15 @@ public class ViewState extends TransitionableState { } public void exit(RequestControlContext context) { - destroyVariables(context); super.exit(context); + destroyVariables(context); + if (history == History.PRESERVE) { + context.updateCurrentFlowExecutionSnapshot(); + } else if (history == History.DISCARD) { + context.removeCurrentFlowExecutionSnapshot(); + } else if (history == History.INVALIDATE) { + context.removeAllFlowExecutionSnapshots(); + } } // internal helpers @@ -269,7 +300,7 @@ public class ViewState extends TransitionableState { protected void appendToString(ToStringCreator creator) { super.appendToString(creator); creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append( - "popup", popup); + "popup", popup).append("history", history); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java index 5b28218e..119345d0 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java @@ -23,6 +23,7 @@ import org.springframework.webflow.engine.DecisionState; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.FlowExecutionExceptionHandler; +import org.springframework.webflow.engine.History; import org.springframework.webflow.engine.State; import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.engine.SubflowState; @@ -81,12 +82,14 @@ public class FlowArtifactFactory { * @return the fully initialized view state instance */ public State createViewState(String id, Flow flow, ViewVariable[] variables, Action[] entryActions, - ViewFactory viewFactory, Boolean redirect, boolean popup, Action[] renderActions, Transition[] transitions, - FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) { + ViewFactory viewFactory, Boolean redirect, boolean popup, History history, Action[] renderActions, + Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, + AttributeMap attributes) { ViewState viewState = new ViewState(flow, id, viewFactory); viewState.addVariables(variables); viewState.setRedirect(redirect); viewState.setPopup(popup); + viewState.setHistory(history); viewState.getRenderActionList().addAll(renderActions); configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes); return viewState; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java index 6165de5f..6772440d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/ViewFactoryCreator.java @@ -18,7 +18,6 @@ package org.springframework.webflow.engine.builder; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; -import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.execution.View; import org.springframework.webflow.execution.ViewFactory; @@ -37,7 +36,7 @@ public interface ViewFactoryCreator { * @return the view factory */ public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ResourceLoader resourceLoader); + FormatterRegistry formatterRegistry); /** * Get the default id of the view to render in the provided view state by convention. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java index b22c2c08..3fcd99a4 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java @@ -7,6 +7,7 @@ import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionExecutor; @@ -21,7 +22,9 @@ import org.springframework.binding.mapping.impl.DefaultMapper; import org.springframework.binding.mapping.impl.DefaultMapping; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.core.JdkVersion; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -44,9 +47,11 @@ import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.engine.AnnotatedAction; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.FlowExecutionExceptionHandler; import org.springframework.webflow.engine.FlowVariable; +import org.springframework.webflow.engine.History; import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.engine.TargetStateResolver; import org.springframework.webflow.engine.Transition; @@ -87,7 +92,6 @@ import org.springframework.webflow.engine.support.TransitionCriteriaChain; import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.ScopeType; import org.springframework.webflow.execution.ViewFactory; import org.springframework.webflow.security.SecurityRule; @@ -104,6 +108,10 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { private LocalFlowBuilderContext localFlowBuilderContext; + /** + * Creates a flow builder that can build a {@link Flow} from a {@link FlowModel}. + * @param flowModelHolder the flow model holder + */ public FlowModelFlowBuilder(FlowModelHolder flowModelHolder) { this.flowModelHolder = flowModelHolder; } @@ -311,10 +319,31 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { } new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); registerFlowBeans(flowContext.getBeanFactory()); + registerMessageSource(flowContext, flowResource); flowContext.refresh(); return flowContext; } + private void registerMessageSource(GenericApplicationContext flowContext, Resource flowResource) { + boolean localMessageSourcePresent = flowContext + .containsLocalBean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME); + if (flowResource != null && !localMessageSourcePresent) { + Resource messageBundle; + try { + messageBundle = flowResource.createRelative("messages.properties"); + } catch (IOException e) { + messageBundle = null; + } + if (messageBundle != null && messageBundle.exists()) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(ReloadableResourceBundleMessageSource.class); + builder.addPropertyValue("basename", "messages"); + flowContext.registerBeanDefinition(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, builder + .getBeanDefinition()); + } + } + } + private Flow parseFlow(FlowModel flow) { String flowId = getLocalContext().getFlowId(); AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes(); @@ -329,8 +358,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { Class clazz = (Class) fromStringTo(Class.class).execute(var.getClassName()); VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, getFlow() .getApplicationContext().getAutowireCapableBeanFactory()); - ScopeType scope = ScopeType.CONVERSATION; - return new FlowVariable(var.getName(), valueFactory, scope == ScopeType.FLOW ? true : false); + return new FlowVariable(var.getName(), valueFactory); } private Mapper parseFlowInputMapper(List inputs) { @@ -476,6 +504,10 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { if (StringUtils.hasText(state.getPopup())) { popup = ((Boolean) fromStringTo(Boolean.class).execute(state.getPopup())).booleanValue(); } + History history = History.PRESERVE; + if (StringUtils.hasText(state.getHistory())) { + history = (History) fromStringTo(History.class).execute(state.getHistory()); + } MutableAttributeMap attributes = parseMetaAttributes(state.getAttributes()); if (state.getModel() != null) { attributes.put("model", getLocalContext().getExpressionParser().parseExpression(state.getModel(), @@ -484,7 +516,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { parseAndPutSecured(state.getSecured(), attributes); getLocalContext().getFlowArtifactFactory().createViewState(state.getId(), flow, parseViewVariables(state.getVars()), parseActions(state.getOnEntryActions()), viewFactory, redirect, - popup, parseActions(state.getOnRenderActions()), parseTransitions(state.getTransitions()), + popup, history, parseActions(state.getOnRenderActions()), parseTransitions(state.getTransitions()), parseExceptionHandlers(state.getExceptionHandlers(), state.getTransitions()), parseActions(state.getOnExitActions()), attributes); } @@ -560,8 +592,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { private ViewFactory createViewFactory(Expression viewId) { return getLocalContext().getViewFactoryCreator().createViewFactory(viewId, - getLocalContext().getExpressionParser(), getLocalContext().getFormatterRegistry(), - getLocalContext().getApplicationContext()); + getLocalContext().getExpressionParser(), getLocalContext().getFormatterRegistry()); } private ViewVariable[] parseViewVariables(List vars) { @@ -734,13 +765,21 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { if (actionModels != null && !actionModels.isEmpty()) { List actions = new ArrayList(actionModels.size()); for (Iterator it = actionModels.iterator(); it.hasNext();) { - AbstractActionModel action = (AbstractActionModel) it.next(); - if (action instanceof EvaluateModel) { - actions.add(parseEvaluateAction((EvaluateModel) action)); - } else if (action instanceof RenderModel) { - actions.add(parseRenderAction((RenderModel) action)); - } else if (action instanceof SetModel) { - actions.add(parseSetAction((SetModel) action)); + AbstractActionModel actionModel = (AbstractActionModel) it.next(); + Action action; + if (actionModel instanceof EvaluateModel) { + action = parseEvaluateAction((EvaluateModel) actionModel); + } else if (actionModel instanceof RenderModel) { + action = parseRenderAction((RenderModel) actionModel); + } else if (actionModel instanceof SetModel) { + action = parseSetAction((SetModel) actionModel); + } else { + action = null; + } + if (action != null) { + AnnotatedAction annotatedAction = new AnnotatedAction(action); + annotatedAction.getAttributes().putAll(parseMetaAttributes(actionModel.getAttributes())); + actions.add(annotatedAction); } } return (Action[]) actions.toArray(new Action[actions.size()]); @@ -847,14 +886,6 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { } } - private ScopeType parseScopeType(String scope, ScopeType defaultScope) { - if (StringUtils.hasText(scope)) { - return (ScopeType) fromStringTo(ScopeType.class).execute(scope); - } else { - return defaultScope; - } - } - private ConversionExecutor fromStringTo(Class targetType) throws ConversionException { return getLocalContext().getConversionService().getConversionExecutor(String.class, targetType); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java index a17e10ee..959d84eb 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java @@ -264,6 +264,18 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } } + public void setCurrentState(String stateId) { + State state = flow.getStateInstance(stateId); + FlowSessionImpl session; + if (started) { + session = getActiveSessionInternal(); + } else { + session = activateSession(flow); + started = true; + } + session.setCurrentState(state); + } + public void resume(ExternalContext externalContext) throws FlowExecutionException, IllegalStateException { if (!isActive()) { if (started) { @@ -386,6 +398,18 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { return key; } + void updateCurrentFlowExecutionSnapshot() { + keyFactory.updateFlowExecutionSnapshot(this); + } + + void removeCurrentFlowExecutionSnapshot() { + keyFactory.removeFlowExecutionSnapshot(this); + } + + void removeAllFlowExecutionSnapshots() { + keyFactory.removeAllFlowExecutionSnapshots(this); + } + // package private setters for restoring transient state used by FlowExecutionImplServicesConfigurer FlowExecutionListener[] getListeners() { @@ -516,7 +540,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { * @param flow the flow definition * @return the new flow session */ - private FlowSession activateSession(Flow flow) { + private FlowSessionImpl activateSession(Flow flow) { FlowSessionImpl session; if (!flowSessions.isEmpty()) { FlowSessionImpl parent = getActiveSessionInternal(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java index 3113ba59..24bcc778 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java @@ -69,6 +69,15 @@ public class FlowExecutionImplFactory extends FlowExecutionImplServicesConfigure return new SimpleFlowExecutionKey(idGenerator.generateUid()); } + public void removeAllFlowExecutionSnapshots(FlowExecution execution) { + } + + public void removeFlowExecutionSnapshot(FlowExecution execution) { + } + + public void updateFlowExecutionSnapshot(FlowExecution execution) { + } + private static class SimpleFlowExecutionKey extends FlowExecutionKey { private Serializable value; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java index 23abdb5e..2ace2c88 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java @@ -16,8 +16,6 @@ package org.springframework.webflow.engine.impl; import org.springframework.binding.message.DefaultMessageContextFactory; -import org.springframework.binding.message.MessageContextFactory; -import org.springframework.context.support.StaticMessageSource; import org.springframework.util.Assert; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.CollectionUtils; @@ -37,11 +35,6 @@ abstract class FlowExecutionImplServicesConfigurer { */ private FlowExecutionListenerLoader executionListenerLoader = StaticFlowExecutionListenerLoader.EMPTY_INSTANCE; - /** - * The factory for message contexts for tracking flow execution messages. - */ - private MessageContextFactory messageContextFactory = new DefaultMessageContextFactory(new StaticMessageSource()); - /** * Sets the attributes to apply to flow executions created by this factory. Execution attributes may affect flow * execution behavior. @@ -61,13 +54,6 @@ abstract class FlowExecutionImplServicesConfigurer { this.executionListenerLoader = executionListenerLoader; } - /** - * Sets the strategy for creating message contexts that track flow execution messages. - */ - public void setMessageContextFactory(MessageContextFactory messageContextFactory) { - this.messageContextFactory = messageContextFactory; - } - /** * Called by subclasses to apply the configured set of standard services to the flow execution. * @param execution the flow execution @@ -75,6 +61,7 @@ abstract class FlowExecutionImplServicesConfigurer { protected void configureServices(FlowExecutionImpl execution) { execution.setAttributes(executionAttributes); execution.setListeners(executionListenerLoader.getListeners(execution.getDefinition())); - execution.setMessageContextFactory(messageContextFactory); + execution.setMessageContextFactory(new DefaultMessageContextFactory(execution.getDefinition() + .getApplicationContext())); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java index d685debd..1ce4e502 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/RequestControlContextImpl.java @@ -195,15 +195,23 @@ class RequestControlContextImpl implements RequestControlContext { return flowExecution.assignKey(); } + public void updateCurrentFlowExecutionSnapshot() { + flowExecution.updateCurrentFlowExecutionSnapshot(); + } + + public void removeCurrentFlowExecutionSnapshot() { + flowExecution.removeCurrentFlowExecutionSnapshot(); + } + + public void removeAllFlowExecutionSnapshots() { + flowExecution.removeAllFlowExecutionSnapshots(); + } + public boolean getAlwaysRedirectOnPause() { Boolean redirectOnPause = flowExecution.getAttributes().getBoolean("alwaysRedirectOnPause"); return redirectOnPause != null ? redirectOnPause.booleanValue() : false; } - public void start(Flow flow, MutableAttributeMap input) throws FlowExecutionException { - flowExecution.start(flow, input, this); - } - public boolean handleEvent(Event event) throws FlowExecutionException { this.currentEvent = event; return flowExecution.handleEvent(event, this); @@ -213,6 +221,10 @@ class RequestControlContextImpl implements RequestControlContext { return flowExecution.execute(transition, this); } + public void start(Flow flow, MutableAttributeMap input) throws FlowExecutionException { + flowExecution.start(flow, input, this); + } + public FlowSession endActiveFlowSession(MutableAttributeMap output) throws IllegalStateException { return flowExecution.endActiveFlowSession(output, this); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java index 731a9daf..32925694 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java @@ -16,9 +16,7 @@ package org.springframework.webflow.engine.model.builder.xml; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -57,8 +55,6 @@ import org.springframework.webflow.engine.model.registry.FlowModelLocator; import org.springframework.webflow.engine.model.registry.NoSuchFlowModelException; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** @@ -287,8 +283,7 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { } private LinkedList parseActions(Element element) { - List actionElements = getChildElementsByTagNames(element, Arrays.asList(new String[] { "evaluate", "render", - "set" })); + List actionElements = DomUtils.getChildElementsByTagName(element, new String[] { "evaluate", "render", "set" }); if (actionElements.isEmpty()) { return null; } @@ -300,8 +295,8 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { } private LinkedList parseStates(Element element) { - List stateElements = getChildElementsByTagNames(element, Arrays.asList(new String[] { "view-state", - "action-state", "decision-state", "subflow-state", "end-state" })); + List stateElements = DomUtils.getChildElementsByTagName(element, new String[] { "view-state", "action-state", + "decision-state", "subflow-state", "end-state" }); if (stateElements.isEmpty()) { return null; } @@ -618,25 +613,6 @@ public class XmlFlowModelBuilder implements FlowModelBuilder { return state; } - // TODO: submited to DomUtils in spring-core will be available in 2.5.3 - public static List getChildElementsByTagNames(Element ele, Collection childEleNames) { - Assert.notNull(ele, "Element must not be null"); - Assert.notNull(childEleNames, "Element names collection must not be null"); - NodeList nl = ele.getChildNodes(); - List childEles = new ArrayList(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (node instanceof Element && nodeNameMatch(node, childEleNames)) { - childEles.add(node); - } - } - return childEles; - } - - private static boolean nodeNameMatch(Node node, Collection desiredNames) { - return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName())); - } - public String toString() { return new ToStringCreator(this).append("resource", resource).toString(); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactory.java index 275a2707..bb48a0e7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/BeanFactoryVariableValueFactory.java @@ -53,7 +53,9 @@ public class BeanFactoryVariableValueFactory implements VariableValueFactory { } public void restoreReferences(Object value, RequestContext context) { - beanFactory.autowireBean(value); + if (value != null) { + beanFactory.autowireBean(value); + } } public String toString() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionKeyFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionKeyFactory.java index 7acb90f7..49022ab4 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionKeyFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionKeyFactory.java @@ -30,4 +30,11 @@ public interface FlowExecutionKeyFactory { * @return the key to assign to the flow execution */ public FlowExecutionKey getKey(FlowExecution execution); + + public void updateFlowExecutionSnapshot(FlowExecution execution); + + public void removeFlowExecutionSnapshot(FlowExecution execution); + + public void removeAllFlowExecutionSnapshots(FlowExecution execution); + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java index ceed0ab8..7f441c27 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java @@ -126,6 +126,22 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu putConversationScope(flowExecution); } + // implementing flow execution key factory + + public void removeAllFlowExecutionSnapshots(FlowExecution execution) { + getContinuationGroup(execution.getKey()).removeAllContinuations(); + } + + public void removeFlowExecutionSnapshot(FlowExecution execution) { + FlowExecutionKey key = execution.getKey(); + getContinuationGroup(key).removeContinuation(getContinuationId(key)); + } + + public void updateFlowExecutionSnapshot(FlowExecution execution) { + FlowExecutionKey key = execution.getKey(); + getContinuationGroup(key).updateContinuation(getContinuationId(key), snapshot(execution)); + } + // hooks for subclassing protected FlowExecutionContinuationGroup createFlowExecutionContinuationGroup() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/FlowExecutionContinuationGroup.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/FlowExecutionContinuationGroup.java index 3a29c1c5..f2f0a0d7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/FlowExecutionContinuationGroup.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/FlowExecutionContinuationGroup.java @@ -66,23 +66,20 @@ class FlowExecutionContinuationGroup implements Serializable { /** * Returns the continuation with the provided id, or null if no such continuation * exists with that id. - * @param id the continuation id + * @param continuationId the continuation id * @return the continuation * @throws ContinuationNotFoundException if the id does not match a continuation in this group */ - public FlowExecutionContinuation get(Serializable id) throws ContinuationNotFoundException { - FlowExecutionContinuation continuation = (FlowExecutionContinuation) continuations.get(id); + public FlowExecutionContinuation get(Serializable continuationId) throws ContinuationNotFoundException { + FlowExecutionContinuation continuation = (FlowExecutionContinuation) continuations.get(continuationId); if (continuation == null) { - throw new ContinuationNotFoundException(id); + throw new ContinuationNotFoundException(continuationId); } return continuation; } /** * Add a flow execution continuation with given id to this group. - * - * TODO add listener methods 1. continuationAdded(ConversationId conversationId, FlowExecutionContinuation - * continuation) 2. maxContinuationsReached(ConversationId conversationId) * @param continuationId the continuation id * @param continuation the continuation */ @@ -99,6 +96,37 @@ class FlowExecutionContinuationGroup implements Serializable { } } + /** + * Update the continuation with the given id. + * @param continuationId the continuation id + * @param continuation thew new continuation + * @throws ContinuationNotFoundException if there was no previous continuation to update + */ + public void updateContinuation(Serializable continuationId, FlowExecutionContinuation continuation) + throws ContinuationNotFoundException { + if (!continuations.containsKey(continuationId)) { + throw new ContinuationNotFoundException(continuationId); + } + continuations.put(continuationId, continuation); + } + + /** + * Remove the continuation with the given id. + * @param continuationId the continuation id + */ + public void removeContinuation(Serializable continuationId) { + continuations.remove(continuationId); + continuationIds.remove(continuationId); + } + + /** + * Remove all continuations in this group. + */ + public void removeAllContinuations() { + continuations.clear(); + continuationIds.clear(); + } + /** * Has the maximum number of allowed continuations in this group been exceeded? */ @@ -112,4 +140,5 @@ class FlowExecutionContinuationGroup implements Serializable { private void removeOldestContinuation() { continuations.remove(continuationIds.removeFirst()); } + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutorImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutorImpl.java index e142d0ad..c75fc7d8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutorImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutorImpl.java @@ -105,8 +105,8 @@ public class FlowExecutorImpl implements FlowExecutor { public FlowExecutionResult launchExecution(String flowId, MutableAttributeMap input, ExternalContext context) throws FlowException { - ExternalContextHolder.setExternalContext(context); try { + ExternalContextHolder.setExternalContext(context); FlowDefinition flowDefinition = definitionLocator.getFlowDefinition(flowId); FlowExecution flowExecution = executionFactory.createFlowExecution(flowDefinition); flowExecution.start(input, context); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java index a0b50b04..d538afce 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java @@ -62,8 +62,8 @@ public class PortletMvcView extends MvcView { } request.setAttribute(ViewRendererServlet.VIEW_ATTRIBUTE, view); request.setAttribute(ViewRendererServlet.MODEL_ATTRIBUTE, model); - // request.setAttribute(org.springframework.web.servlet.support.RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, - // context.getActiveFlow().getBeanFactory()); + request.setAttribute(org.springframework.web.servlet.support.RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, + context.getActiveFlow().getApplicationContext()); portletContext.getRequestDispatcher(DispatcherPortlet.DEFAULT_VIEW_RENDERER_URL).include(request, response); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java index cb002a45..8334067a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/servlet/ServletMvcView.java @@ -44,8 +44,8 @@ public class ServletMvcView extends MvcView { ExternalContext externalContext = context.getExternalContext(); HttpServletRequest request = (HttpServletRequest) externalContext.getNativeRequest(); HttpServletResponse response = (HttpServletResponse) externalContext.getNativeResponse(); - // request.setAttribute(org.springframework.web.servlet.support.RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, - // context.getActiveFlow().getBeanFactory()); + request.setAttribute(org.springframework.web.servlet.support.RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, + context.getActiveFlow().getApplicationContext()); getView().render(model, request, response); } } \ No newline at end of file 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 9b720ec2..4c1899ec 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 @@ -16,6 +16,9 @@ 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.util.Assert; +import org.springframework.validation.AbstractErrors; +import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; @@ -29,7 +32,9 @@ import org.springframework.validation.ObjectError; * * @author Keith Donald */ -public class BindingModel extends ViewRenderingErrors { +public class BindingModel extends AbstractErrors { + + private String objectName; private Object boundObject; @@ -43,13 +48,17 @@ public class BindingModel extends ViewRenderingErrors { /** * Creates a new Spring Binding model. + * @param objectName the name of the bound model object * @param boundObject the bound model object * @param expressionParser the expression parser used to access model object properties * @param formatterRegistry the formatter registry used to access formatters for formatting properties * @param messageContext the message context containing flow messages to display */ - public BindingModel(Object boundObject, ExpressionParser expressionParser, FormatterRegistry formatterRegistry, - MessageContext messageContext) { + public BindingModel(String objectName, Object boundObject, ExpressionParser expressionParser, + FormatterRegistry formatterRegistry, MessageContext messageContext) { + Assert.hasText(objectName, "The object name is required"); + Assert.notNull(boundObject, "The bound object instance is required"); + this.objectName = objectName; this.boundObject = boundObject; this.expressionParser = expressionParser; this.formatterRegistry = formatterRegistry; @@ -94,6 +103,12 @@ public class BindingModel extends ViewRenderingErrors { return getFormattedValue(parseFieldExpression(field)); } + // not typically used by mvc views, but implemented to be on the safe side + + public List getFieldErrors() { + return toErrors(messageContext.getMessagesByCriteria(new FieldErrorMessage())); + } + // internal helpers private Expression parseFieldExpression(String field) { @@ -126,10 +141,9 @@ public class BindingModel extends ViewRenderingErrors { 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())); + errors.add(new ObjectError(objectName, message.getText())); } else { - errors.add(new FieldError("boundObject", (String) message.getSource(), null, false, null, null, message - .getText())); + errors.add(new FieldError(objectName, (String) message.getSource(), message.getText())); } } return errors; @@ -167,13 +181,36 @@ public class BindingModel extends ViewRenderingErrors { private static class FieldErrorMessage implements MessageCriteria { private String field; + public FieldErrorMessage() { + } + public FieldErrorMessage(String field) { this.field = field; } public boolean test(Message message) { - return field.equals(message.getSource()) && message.getSeverity() == Severity.ERROR; + if (field != null) { + return field.equals(message.getSource()) && message.getSeverity() == Severity.ERROR; + } else { + return message.getSource() != null && message.getSeverity() == Severity.ERROR; + } } } + public String getObjectName() { + return objectName; + } + + public void addAllErrors(Errors errors) { + throw new UnsupportedOperationException("Should not be called during view rendering"); + } + + public void reject(String errorCode, Object[] errorArgs, String defaultMessage) { + throw new UnsupportedOperationException("Should not be called during view rendering"); + } + + public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { + throw new UnsupportedOperationException("Should not be called during view rendering"); + } + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/InternalFlowResourceMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/InternalFlowResourceMvcViewFactory.java index 8a22abd6..01c2c0c0 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/InternalFlowResourceMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/InternalFlowResourceMvcViewFactory.java @@ -6,7 +6,6 @@ import org.springframework.binding.format.FormatterRegistry; import org.springframework.context.ApplicationContext; import org.springframework.core.io.ContextResource; import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.util.ClassUtils; import org.springframework.web.servlet.view.InternalResourceView; import org.springframework.web.servlet.view.JstlView; @@ -28,45 +27,44 @@ class InternalFlowResourceMvcViewFactory implements ViewFactory { private Expression viewIdExpression; - private ApplicationContext applicationContext; - - private ResourceLoader resourceLoader; - private ExpressionParser expressionParser; private FormatterRegistry formatterRegistry; public InternalFlowResourceMvcViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ApplicationContext context, ResourceLoader resourceLoader) { + FormatterRegistry formatterRegistry) { this.viewIdExpression = viewIdExpression; this.expressionParser = expressionParser; this.formatterRegistry = formatterRegistry; - this.applicationContext = context; - this.resourceLoader = resourceLoader; } public View getView(RequestContext context) { String viewId = (String) viewIdExpression.getValue(context); if (viewId.startsWith("/")) { - return getViewInternal(viewId, context); + return getViewInternal(viewId, context, context.getActiveFlow().getApplicationContext()); } else { - Resource viewResource = resourceLoader.getResource(viewId); + ApplicationContext flowContext = context.getActiveFlow().getApplicationContext(); + if (flowContext == null) { + throw new IllegalStateException("A Flow ApplicationContext is required to resolve Flow View Resources"); + } + Resource viewResource = flowContext.getResource(viewId); if (!(viewResource instanceof ContextResource)) { throw new IllegalStateException( "A ContextResource is required to get relative view paths within this context"); } - return getViewInternal(((ContextResource) viewResource).getPathWithinContext(), context); + return getViewInternal(((ContextResource) viewResource).getPathWithinContext(), context, flowContext); } } - private View getViewInternal(String viewPath, RequestContext context) { + private View getViewInternal(String viewPath, RequestContext context, ApplicationContext flowContext) { if (viewPath.endsWith(".jsp")) { if (JSTL_PRESENT) { JstlView view = new JstlView(viewPath); - view.setApplicationContext(context.getActiveFlow().getApplicationContext()); + view.setApplicationContext(flowContext); return createMvcView(view, context); } else { InternalResourceView view = new InternalResourceView(viewPath); + view.setApplicationContext(flowContext); return createMvcView(view, context); } } else { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MessageContextErrors.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MessageContextErrors.java index 5eb95436..bb3f070a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MessageContextErrors.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MessageContextErrors.java @@ -4,9 +4,8 @@ import java.util.List; import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageContext; +import org.springframework.validation.AbstractErrors; import org.springframework.validation.Errors; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; /** * Adapts a MessageContext object to the Spring Errors interface. Allows Spring Validators to record errors that are @@ -14,7 +13,7 @@ import org.springframework.validation.ObjectError; * * @author Keith Donald */ -public class MessageContextErrors implements Errors { +public class MessageContextErrors extends AbstractErrors { private MessageContext messageContext; @@ -26,122 +25,32 @@ public class MessageContextErrors implements Errors { this.messageContext = messageContext; } - public void reject(String errorCode) { - messageContext.addMessage(new MessageBuilder().error().code(errorCode).build()); - } - - public void reject(String errorCode, String defaultMessage) { - messageContext.addMessage(new MessageBuilder().error().code(errorCode).defaultText(defaultMessage).build()); - } - public void reject(String errorCode, Object[] errorArgs, String defaultMessage) { messageContext.addMessage(new MessageBuilder().error().code(errorCode).defaultText(defaultMessage).build()); } - public void rejectValue(String field, String errorCode) { - messageContext.addMessage(new MessageBuilder().error().source(field).code(errorCode).build()); - } - - public void rejectValue(String field, String errorCode, String defaultMessage) { - messageContext.addMessage(new MessageBuilder().error().source(field).code(errorCode) - .defaultText(defaultMessage).build()); - } - public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { - messageContext.addMessage(new MessageBuilder().error().source(field).code(errorCode).args(errorArgs) - .defaultText(defaultMessage).build()); + messageContext.addMessage(new MessageBuilder().error().code(errorCode).defaultText(defaultMessage).build()); } public void addAllErrors(Errors errors) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public List getAllErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public int getErrorCount() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public FieldError getFieldError() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public FieldError getFieldError(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public int getFieldErrorCount() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public int getFieldErrorCount(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); + throw new UnsupportedOperationException("Auto-generated method stub"); } public List getFieldErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public List getFieldErrors(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public Class getFieldType(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); + throw new UnsupportedOperationException("Auto-generated method stub"); } public Object getFieldValue(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public ObjectError getGlobalError() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public int getGlobalErrorCount() { - throw new UnsupportedOperationException("Should not be called by a validator"); + throw new UnsupportedOperationException("Auto-generated method stub"); } public List getGlobalErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public String getNestedPath() { - throw new UnsupportedOperationException("Should not be called by a validator"); + throw new UnsupportedOperationException("Auto-generated method stub"); } public String getObjectName() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public boolean hasErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public boolean hasFieldErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public boolean hasFieldErrors(String field) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public boolean hasGlobalErrors() { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public void popNestedPath() throws IllegalStateException { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public void pushNestedPath(String subPath) { - throw new UnsupportedOperationException("Should not be called by a validator"); - } - - public void setNestedPath(String nestedPath) { - throw new UnsupportedOperationException("Should not be called by a validator"); + throw new UnsupportedOperationException("Auto-generated method stub"); } } 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 622c3a21..8adcac8e 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 @@ -191,8 +191,8 @@ public abstract class MvcView implements View { private void exposeBindingModel(Map model) { Object modelObject = getModelObject(); if (modelObject != null) { - BindingModel bindingModel = new BindingModel(modelObject, expressionParser, formatterRegistry, - requestContext.getMessageContext()); + BindingModel bindingModel = new BindingModel(getModelExpression().getExpressionString(), modelObject, + expressionParser, formatterRegistry, requestContext.getMessageContext()); bindingModel.setMappingResults(mappingResults); model.put(BindingResult.MODEL_KEY_PREFIX + getModelExpression().getExpressionString(), bindingModel); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcViewFactoryCreator.java index bd99b05f..655854a2 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/MvcViewFactoryCreator.java @@ -20,9 +20,6 @@ import java.util.List; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.engine.builder.ViewFactoryCreator; import org.springframework.webflow.execution.ViewFactory; @@ -38,12 +35,10 @@ import org.springframework.webflow.execution.ViewFactory; * @author Keith Donald * @author Scott Andrews */ -public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationContextAware { +public class MvcViewFactoryCreator implements ViewFactoryCreator { private List viewResolvers; - private ApplicationContext applicationContext; - /** * Sets the view resolvers that will be used to resolve views selected by flows. If multiple resolvers are to be * used, the resolvers should be ordered in the manner they should be applied. @@ -53,18 +48,12 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon this.viewResolvers = viewResolvers; } - public void setApplicationContext(ApplicationContext context) { - this.applicationContext = context; - } - public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ResourceLoader resourceLoader) { + FormatterRegistry formatterRegistry) { if (viewResolvers != null) { - return new ViewResolvingMvcViewFactory(viewIdExpression, expressionParser, formatterRegistry, - viewResolvers, applicationContext); + return new ViewResolvingMvcViewFactory(viewIdExpression, expressionParser, formatterRegistry, viewResolvers); } else { - return new InternalFlowResourceMvcViewFactory(viewIdExpression, expressionParser, formatterRegistry, - applicationContext, resourceLoader); + return new InternalFlowResourceMvcViewFactory(viewIdExpression, expressionParser, formatterRegistry); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ViewResolvingMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ViewResolvingMvcViewFactory.java index 4dcfba06..be3c7e20 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ViewResolvingMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ViewResolvingMvcViewFactory.java @@ -7,7 +7,6 @@ import java.util.Locale; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; -import org.springframework.context.ApplicationContext; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.web.servlet.ViewResolver; import org.springframework.webflow.context.portlet.PortletExternalContext; @@ -32,13 +31,12 @@ class ViewResolvingMvcViewFactory implements ViewFactory { private List viewResolvers; - private ApplicationContext applicationContext; - public ViewResolvingMvcViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, List viewResolvers, ApplicationContext context) { + FormatterRegistry formatterRegistry, List viewResolvers) { this.viewIdExpression = viewIdExpression; + this.expressionParser = expressionParser; + this.formatterRegistry = formatterRegistry; this.viewResolvers = viewResolvers; - this.applicationContext = context; } public View getView(RequestContext context) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKeyFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKeyFactory.java index 1a364d0e..ec4624e6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKeyFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKeyFactory.java @@ -14,4 +14,14 @@ public class MockFlowExecutionKeyFactory implements FlowExecutionKeyFactory { public FlowExecutionKey getKey(FlowExecution execution) { return new GeneratedFlowExecutionKey(); } + + public void removeAllFlowExecutionSnapshots(FlowExecution execution) { + } + + public void removeFlowExecutionSnapshot(FlowExecution execution) { + } + + public void updateFlowExecutionSnapshot(FlowExecution execution) { + } + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java index f012b75e..5c946c09 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestControlContext.java @@ -64,6 +64,33 @@ public class MockRequestControlContext extends MockRequestContext implements Req getMockFlowExecutionContext().getMockActiveSession().setState(state); } + public FlowExecutionKey assignFlowExecutionKey() { + GeneratedFlowExecutionKey key = new GeneratedFlowExecutionKey(); + getMockFlowExecutionContext().setKey(key); + return key; + } + + public void removeAllFlowExecutionSnapshots() { + + } + + public void removeCurrentFlowExecutionSnapshot() { + + } + + public void updateCurrentFlowExecutionSnapshot() { + + } + + public boolean handleEvent(Event event) { + setCurrentEvent(event); + return ((Flow) getActiveFlow()).handleEvent(this); + } + + public boolean execute(Transition transition) { + return transition.execute((TransitionableState) getCurrentState(), this); + } + public void start(Flow flow, MutableAttributeMap input) throws IllegalStateException { MockFlowSession session = new MockFlowSession(flow, input); if (getFlowExecutionContext().isActive()) { @@ -73,11 +100,6 @@ public class MockRequestControlContext extends MockRequestContext implements Req flow.start(this, input); } - public boolean handleEvent(Event event) { - setCurrentEvent(event); - return ((Flow) getActiveFlow()).handleEvent(this); - } - public FlowSession endActiveFlowSession(MutableAttributeMap output) throws IllegalStateException { MockFlowSession endingSession = getMockFlowExecutionContext().getMockActiveSession(); endingSession.getDefinitionInternal().end(this, output); @@ -85,24 +107,16 @@ public class MockRequestControlContext extends MockRequestContext implements Req return endingSession; } - public boolean execute(Transition transition) { - return transition.execute((TransitionableState) getCurrentState(), this); + public boolean getAlwaysRedirectOnPause() { + return alwaysRedirectOnPause; } - public FlowExecutionKey assignFlowExecutionKey() { - GeneratedFlowExecutionKey key = new GeneratedFlowExecutionKey(); - getMockFlowExecutionContext().setKey(key); - return key; - } + // implementation specific accessors for testing public boolean getFlowExecutionRedirectSent() { return this.flowExecutionRedirectSent; } - public boolean getAlwaysRedirectOnPause() { - return alwaysRedirectOnPause; - } - public void setAlwaysRedirectOnPause(boolean alwaysRedirectOnPause) { this.alwaysRedirectOnPause = alwaysRedirectOnPause; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java index c02cf8aa..bd9ec872 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockViewFactoryCreator.java @@ -20,7 +20,6 @@ import java.io.IOException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; -import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.engine.builder.ViewFactoryCreator; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; @@ -36,7 +35,7 @@ import org.springframework.webflow.execution.ViewFactory; class MockViewFactoryCreator implements ViewFactoryCreator { public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ResourceLoader resourceLoader) { + FormatterRegistry formatterRegistry) { return new MockViewFactory(viewIdExpression); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java index 3507a4b1..5fe31932 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java @@ -19,9 +19,11 @@ import org.springframework.webflow.config.FlowDefinitionResource; import org.springframework.webflow.config.FlowDefinitionResourceFactory; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.FlowBuilder; +import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader; @@ -39,7 +41,7 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo /** * The cached flow definition. */ - private static FlowDefinition cachedFlowDefinition; + private static Flow cachedFlowDefinition; /** * The flag indicating if the flow definition built from an externalized resource as part of this test should be @@ -52,6 +54,11 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo */ private FlowDefinitionResourceFactory resourceFactory = new FlowDefinitionResourceFactory(); + /** + * Private flow builder context object. + */ + private MockFlowBuilderContext flowBuilderContext; + /** * Constructs a default externalized flow execution test. * @see #setName(String) @@ -128,9 +135,6 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo return resourceFactory; } - /** - * Returns the flow definition being tested. - */ protected final FlowDefinition getFlowDefinition() { if (isCacheFlowDefinition() && cachedFlowDefinition != null) { return cachedFlowDefinition; @@ -142,6 +146,14 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo return flow; } + /** + * Returns the flow definition being tested as a {@link Flow} implementation. Useful if you need to do specific + * assertions against the configuration of the implementation. + */ + protected final Flow getFlow() { + return (Flow) getFlowDefinition(); + } + /** * Factory method to assemble a flow definition from a resource. Called by {@link #getFlowDefinition()} to create * the "main" flow to test. May also be called by subclasses to create subflow definitions whose executions should @@ -150,22 +162,33 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo */ protected final Flow buildFlow() { FlowDefinitionResource resource = getResource(getResourceFactory()); - MockFlowBuilderContext builderContext = new MockFlowBuilderContext(resource.getId(), resource.getAttributes()); - configureFlowBuilderContext(builderContext); + flowBuilderContext = new MockFlowBuilderContext(resource.getId(), resource.getAttributes()); + configureFlowBuilderContext(flowBuilderContext); FlowBuilder builder = createFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, builderContext); + FlowAssembler assembler = new FlowAssembler(builder, flowBuilderContext); return assembler.assembleFlow(); } /** * Subclasses may override this hook to customize the builder context for the flow being tested. Useful for - * registering mock subflows or other builder services. By default, this method does nothing. + * registering mock subflows or other {@link FlowBuilderServices flow builder services}. By default, this method + * does nothing. * @param builderContext the mock flow builder context to configure */ protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) { } + /** + * Returns a reference to the flow definition registry used by the flow being tested to load subflows. Allows late + * registration of dependent subflows on a per test-case basis. This is an alternative to registering such subflows + * upfront in {@link #configureFlowBuilderContext(MockFlowBuilderContext)}. + * @return the flow definition registry + */ + protected FlowDefinitionRegistry getFlowDefinitionRegistry() { + return (FlowDefinitionRegistry) flowBuilderContext.getFlowDefinitionLocator(); + } + /** * Get the resource defining the flow to be tested. * @param resourceFactory a helper for constructing the resource to be tested diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractFlowExecutionTests.java b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractFlowExecutionTests.java index 1e34e379..f684ed23 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractFlowExecutionTests.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractFlowExecutionTests.java @@ -21,7 +21,9 @@ import org.springframework.util.Assert; import org.springframework.webflow.context.ExternalContext; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.engine.impl.FlowExecutionImpl; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; +import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecution; import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.FlowExecutionFactory; @@ -65,6 +67,11 @@ public abstract class AbstractFlowExecutionTests extends TestCase { */ private FlowExecution flowExecution; + /** + * The outcome that was reached when the flow ends; initially null. + */ + private Event flowExecutionOutcome; + /** * Constructs a default flow execution test. * @see #setName(String) @@ -114,6 +121,9 @@ public abstract class AbstractFlowExecutionTests extends TestCase { protected void startFlow(MutableAttributeMap input, ExternalContext context) throws FlowExecutionException { flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition()); flowExecution.start(input, context); + if (flowExecution.hasEnded()) { + flowExecutionOutcome = flowExecution.getOutcome(); + } } /** @@ -126,6 +136,21 @@ public abstract class AbstractFlowExecutionTests extends TestCase { Assert.state(flowExecution != null, "The flow execution to test is [null]; " + "you must start the flow execution before you can resume it!"); flowExecution.resume(context); + if (flowExecution.hasEnded()) { + flowExecutionOutcome = flowExecution.getOutcome(); + } + } + + /** + * Sets the current state of the flow execution being tested. If the execution has not been started, it will be + * created and activated. + * @param stateId the state id + */ + protected void setCurrentState(String stateId) { + if (flowExecution == null) { + flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition()); + } + ((FlowExecutionImpl) flowExecution).setCurrentState(stateId); } // convenience accessors @@ -136,40 +161,39 @@ public abstract class AbstractFlowExecutionTests extends TestCase { * @throws IllegalStateException the execution has not been started */ protected FlowExecution getFlowExecution() throws IllegalStateException { - Assert.state(flowExecution != null, - "The flow execution to test is [null]; you must start the flow execution before you can access it!"); return flowExecution; } /** - * Returns the attribute in flash scope. Flash-scoped attributes are cleared on the next view rendering. - * @param attributeName the name of the attribute - * @return the attribute value + * Returns the flow execution outcome that was reached. + * @return the flow execution outcome, or null if the flow execution has not ended */ - protected Object getFlashAttribute(String attributeName) { - return getFlowExecution().getFlashScope().get(attributeName); + protected Event getFlowExecutionOutcome() { + return flowExecutionOutcome; } /** - * Returns the required attribute in flash scope; asserts the attribute is present. Flash-scoped attributes are - * cleared on the next view rendering. - * @param attributeName the name of the attribute - * @return the attribute value - * @throws IllegalStateException if the attribute was not present + * Returns view scope. + * @return view scope */ - protected Object getRequiredFlashAttribute(String attributeName) throws IllegalStateException { - return getFlowExecution().getFlashScope().getRequired(attributeName); + protected MutableAttributeMap getViewScope() throws IllegalStateException { + return getFlowExecution().getActiveSession().getViewScope(); } /** - * Returns the required attribute in flash scope; asserts the attribute is present and of the correct type. - * Flash-scoped attributes are cleared on the next view rendering. - * @param attributeName the name of the attribute - * @return the attribute value - * @throws IllegalStateException if the attribute was not present or was of the wrong type + * Returns flow scope. + * @return flow scope */ - protected Object getRequiredFlashAttribute(String attributeName, Class requiredType) throws IllegalStateException { - return getFlowExecution().getFlashScope().getRequired(attributeName, requiredType); + protected MutableAttributeMap getFlowScope() throws IllegalStateException { + return getFlowExecution().getActiveSession().getScope(); + } + + /** + * Returns conversation scope. + * @return conversation scope + */ + protected MutableAttributeMap getConversationScope() throws IllegalStateException { + return getFlowExecution().getConversationScope(); } /** @@ -290,8 +314,15 @@ public abstract class AbstractFlowExecutionTests extends TestCase { * Assert that the entire flow execution has ended; that is, it is no longer active. */ protected void assertFlowExecutionEnded() { - assertTrue("The flow execution is still active but it should have ended", getFlowExecution().hasStarted() - && !getFlowExecution().isActive()); + assertTrue("The flow execution is still active but it should have ended", getFlowExecution().hasEnded()); + } + + /** + * Assert that the entire flow execution has ended; that is, it is no longer active. + */ + protected void assertFlowExecutionOutcomeEquals(String outcome) { + assertNotNull("There has been no flow execution outcome", flowExecutionOutcome); + assertEquals("The flow execution outcome is wrong", flowExecutionOutcome.getId(), outcome); } /** diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java index 4d7f4013..69473c38 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java @@ -10,7 +10,6 @@ import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.format.FormatterRegistry; import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.engine.builder.ViewFactoryCreator; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.execution.ViewFactory; @@ -44,7 +43,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase { public static class TestViewFactoryCreator implements ViewFactoryCreator { public ViewFactory createViewFactory(Expression viewIdExpression, ExpressionParser expressionParser, - FormatterRegistry formatterRegistry, ResourceLoader resourceLoader) { + FormatterRegistry formatterRegistry) { throw new UnsupportedOperationException("Auto-generated method stub"); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java index ee5629ab..258b8fb3 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java @@ -193,7 +193,7 @@ public class FlowTests extends TestCase { public void restoreReferences(Object value, RequestContext context) { } - }, true)); + })); flow.start(context, new LocalAttributeMap()); context.getFlowScope().getRequired("var1", ArrayList.class); } @@ -202,7 +202,8 @@ public class FlowTests extends TestCase { DefaultMapper attributeMapper = new DefaultMapper(); ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); Expression x = parser.parseExpression("attr", new FluentParserContext().evaluate(AttributeMap.class)); - Expression y = parser.parseExpression("flowScope.attr", new FluentParserContext().evaluate(RequestContext.class)); + Expression y = parser.parseExpression("flowScope.attr", new FluentParserContext() + .evaluate(RequestContext.class)); attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setInputMapper(attributeMapper); MockRequestControlContext context = new MockRequestControlContext(flow); @@ -216,7 +217,8 @@ public class FlowTests extends TestCase { DefaultMapper attributeMapper = new DefaultMapper(); ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); Expression x = parser.parseExpression("attr", new FluentParserContext().evaluate(AttributeMap.class)); - Expression y = parser.parseExpression("flowScope.attr", new FluentParserContext().evaluate(RequestContext.class)); + Expression y = parser.parseExpression("flowScope.attr", new FluentParserContext() + .evaluate(RequestContext.class)); attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setInputMapper(attributeMapper); MockRequestControlContext context = new MockRequestControlContext(flow); @@ -304,7 +306,8 @@ public class FlowTests extends TestCase { public void testEndWithOutputMapper() { DefaultMapper attributeMapper = new DefaultMapper(); ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); - Expression x = parser.parseExpression("flowScope.attr", new FluentParserContext().evaluate(RequestContext.class)); + Expression x = parser.parseExpression("flowScope.attr", new FluentParserContext() + .evaluate(RequestContext.class)); Expression y = parser.parseExpression("attr", new FluentParserContext().evaluate(MutableAttributeMap.class)); attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setOutputMapper(attributeMapper); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java index e57a134f..4f8ee855 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowVariableTests.java @@ -17,7 +17,7 @@ public class FlowVariableTests extends TestCase { public void restoreReferences(Object value, RequestContext context) { } - }, true); + }); MockRequestContext context = new MockRequestContext(); var.create(context); assertEquals("bar", context.getFlowScope().get("foo")); @@ -31,7 +31,7 @@ public class FlowVariableTests extends TestCase { public void restoreReferences(Object value, RequestContext context) { } - }, true); + }); MockRequestContext context = new MockRequestContext(); var.create(context); assertEquals("bar", context.getFlowScope().get("foo")); @@ -39,36 +39,6 @@ public class FlowVariableTests extends TestCase { assertFalse(context.getFlowScope().contains("foo")); } - public void testCreateConversationVariable() { - FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { - public Object createInitialValue(RequestContext context) { - return "bar"; - } - - public void restoreReferences(Object value, RequestContext context) { - } - }, false); - MockRequestContext context = new MockRequestContext(); - var.create(context); - assertEquals("bar", context.getConversationScope().get("foo")); - } - - public void testDestroyConversationVariable() { - FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { - public Object createInitialValue(RequestContext context) { - return "bar"; - } - - public void restoreReferences(Object value, RequestContext context) { - } - }, false); - MockRequestContext context = new MockRequestContext(); - var.create(context); - assertEquals("bar", context.getConversationScope().get("foo")); - var.destroy(context); - assertFalse(context.getConversationScope().contains("foo")); - } - public void testRestoreVariable() { FlowVariable var = new FlowVariable("foo", new VariableValueFactory() { public Object createInitialValue(RequestContext context) { @@ -79,11 +49,11 @@ public class FlowVariableTests extends TestCase { restoreCalled = true; assertEquals("bar", value); } - }, false); + }); MockRequestContext context = new MockRequestContext(); var.create(context); var.restore(context); - assertEquals("bar", context.getConversationScope().get("foo")); + assertEquals("bar", context.getFlowScope().get("foo")); assertTrue(restoreCalled); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java index 32822523..c691223e 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java @@ -262,14 +262,9 @@ public class FlowModelFlowBuilderTests extends TestCase { public void testFlowVariable() { model.addVar(new VarModel("flow-foo", "org.springframework.webflow.TestBean")); - VarModel var = new VarModel("conversation-foo", "org.springframework.webflow.TestBean"); - model.addVar(var); model.addEndState(new EndStateModel("end")); Flow flow = getFlow(model); assertEquals("flow-foo", flow.getVariable("flow-foo").getName()); - assertEquals(false, flow.getVariable("flow-foo").isLocal()); - assertEquals("conversation-foo", flow.getVariables()[1].getName()); - assertEquals(false, flow.getVariables()[1].isLocal()); } public void testViewStateVariable() { @@ -325,6 +320,13 @@ public class FlowModelFlowBuilderTests extends TestCase { assertEquals("end", flow.getStartState().getId()); } + public void testResourceBackedFlowBuilderWithMessages() { + ClassPathResource resource = new ClassPathResource("resources/flow.xml", XmlFlowModelBuilderTests.class); + Flow flow = getFlow(resource); + assertNotNull(flow.getApplicationContext()); + assertEquals("bar", flow.getApplicationContext().getMessage("foo", null, null)); + } + public void testAbstractFlow() { model.setAbstract("true"); try { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/flow.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/flow.xml new file mode 100644 index 00000000..00011745 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/flow.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/messages.properties b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/messages.properties new file mode 100644 index 00000000..74d0a43f --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/resources/messages.properties @@ -0,0 +1 @@ +foo=bar diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java index 373d9504..76fb5f1a 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java @@ -47,6 +47,12 @@ public class FlowExecutionImplFactoryTests extends TestCase { private boolean getKeyCalled; + private boolean updateSnapshotCalled; + + private boolean removeSnapshotCalled; + + private boolean removeAllSnapshotsCalled; + public void setUp() { flowDefinition = new Flow("flow"); new EndState(flowDefinition, "end"); @@ -93,6 +99,9 @@ public class FlowExecutionImplFactoryTests extends TestCase { State state = new State(flowDefinition, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { context.assignFlowExecutionKey(); + context.updateCurrentFlowExecutionSnapshot(); + context.removeCurrentFlowExecutionSnapshot(); + context.removeAllFlowExecutionSnapshots(); } }; flowDefinition.setStartState(state); @@ -101,10 +110,26 @@ public class FlowExecutionImplFactoryTests extends TestCase { getKeyCalled = true; return null; } + + public void removeAllFlowExecutionSnapshots(FlowExecution execution) { + removeAllSnapshotsCalled = true; + } + + public void removeFlowExecutionSnapshot(FlowExecution execution) { + removeSnapshotCalled = true; + } + + public void updateFlowExecutionSnapshot(FlowExecution execution) { + updateSnapshotCalled = true; + } + }); FlowExecution execution = factory.createFlowExecution(flowDefinition); execution.start(null, new MockExternalContext()); assertTrue(getKeyCalled); + assertTrue(removeAllSnapshotsCalled); + assertTrue(removeSnapshotCalled); + assertTrue(updateSnapshotCalled); assertNull(execution.getKey()); } } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java index 4fda6f3d..76dfb570 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java @@ -30,6 +30,15 @@ public class FlowExecutionStateRestorerImplTests extends TestCase { public FlowExecutionKey getKey(FlowExecution execution) { return newKey; } + + public void removeAllFlowExecutionSnapshots(FlowExecution execution) { + } + + public void removeFlowExecutionSnapshot(FlowExecution execution) { + } + + public void updateFlowExecutionSnapshot(FlowExecution execution) { + } }; protected void setUp() { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/flow.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/flow.xml new file mode 100644 index 00000000..00011745 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/flow.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/messages.properties b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/messages.properties new file mode 100644 index 00000000..74d0a43f --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/resources/messages.properties @@ -0,0 +1 @@ +foo=bar diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewFactoryTests.java index 65c63824..63e6a88a 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewFactoryTests.java @@ -25,22 +25,40 @@ import org.springframework.web.servlet.ViewResolver; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.View; import org.springframework.webflow.execution.ViewFactory; -import org.springframework.webflow.mvc.view.MvcViewFactoryCreator; import org.springframework.webflow.test.GeneratedFlowExecutionKey; import org.springframework.webflow.test.MockExternalContext; import org.springframework.webflow.test.MockRequestContext; public class MvcViewFactoryTests extends TestCase { private MvcViewFactoryCreator creator; - private StaticApplicationContext context; + private StaticApplicationContext applicationContext; protected void setUp() { creator = new MvcViewFactoryCreator(); - context = new StaticApplicationContext(); + applicationContext = new StaticApplicationContext(); + applicationContext.refresh(); + } + + public void testGetViewNoFlowApplicationContext() { + Expression viewId = new StaticExpression("flowrelativeview.jsp"); + InternalFlowResourceMvcViewFactory factory = new InternalFlowResourceMvcViewFactory(viewId, null, null); + MockRequestContext context = new MockRequestContext(); + try { + factory.getView(context); + fail("Expected illegal state"); + } catch (IllegalStateException e) { + // expected; + } + } + + public void testGetViewNoFlowApplicationContextAbsolutePath() { + Expression viewId = new StaticExpression("/absoluteview.jsp"); + InternalFlowResourceMvcViewFactory factory = new InternalFlowResourceMvcViewFactory(viewId, null, null); + MockRequestContext context = new MockRequestContext(); + assertNotNull(factory.getView(context)); } public void testNoResolversGetResource() throws Exception { - creator.setApplicationContext(context); ResourceLoader viewResourceLoader = new ResourceLoader() { public ClassLoader getClassLoader() { return ClassUtils.getDefaultClassLoader(); @@ -50,9 +68,11 @@ public class MvcViewFactoryTests extends TestCase { return new TestContextResource("/parent/" + name); } }; + applicationContext.setResourceLoader(viewResourceLoader); Expression viewId = new StaticExpression("myview.jsp"); - ViewFactory viewFactory = creator.createViewFactory(viewId, null, null, viewResourceLoader); + ViewFactory viewFactory = creator.createViewFactory(viewId, null, null); MockRequestContext context = new MockRequestContext(); + context.getRootFlow().setApplicationContext(applicationContext); MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -68,10 +88,9 @@ public class MvcViewFactoryTests extends TestCase { public void testViewResolversGetResource() throws Exception { MockViewResolver viewResolver = new MockViewResolver("myview"); - creator.setApplicationContext(context); creator.setViewResolvers(Collections.singletonList(viewResolver)); Expression viewId = new StaticExpression("myview"); - ViewFactory viewFactory = creator.createViewFactory(viewId, null, null, null); + ViewFactory viewFactory = creator.createViewFactory(viewId, null, null); MockRequestContext context = new MockRequestContext(); MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); @@ -87,7 +106,6 @@ public class MvcViewFactoryTests extends TestCase { } public void testRestoreView() throws Exception { - creator.setApplicationContext(context); ResourceLoader viewResourceLoader = new ResourceLoader() { public ClassLoader getClassLoader() { return ClassUtils.getDefaultClassLoader(); @@ -97,9 +115,11 @@ public class MvcViewFactoryTests extends TestCase { return new TestContextResource("/parent/" + name); } }; + applicationContext.setResourceLoader(viewResourceLoader); Expression viewId = new StaticExpression("myview.jsp"); - ViewFactory viewFactory = creator.createViewFactory(viewId, null, null, viewResourceLoader); + ViewFactory viewFactory = creator.createViewFactory(viewId, null, null); MockRequestContext context = new MockRequestContext(); + context.getRootFlow().setApplicationContext(applicationContext); MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -119,7 +139,6 @@ public class MvcViewFactoryTests extends TestCase { } public void testRestoreViewButtonEventIdFormat() throws Exception { - creator.setApplicationContext(context); ResourceLoader viewResourceLoader = new ResourceLoader() { public ClassLoader getClassLoader() { return ClassUtils.getDefaultClassLoader(); @@ -129,9 +148,11 @@ public class MvcViewFactoryTests extends TestCase { return new TestContextResource("/parent/" + name); } }; + applicationContext.setResourceLoader(viewResourceLoader); Expression viewId = new StaticExpression("myview.jsp"); - ViewFactory viewFactory = creator.createViewFactory(viewId, null, null, viewResourceLoader); + ViewFactory viewFactory = creator.createViewFactory(viewId, null, null); MockRequestContext context = new MockRequestContext(); + context.getRootFlow().setApplicationContext(applicationContext); MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java index ecf534aa..13abc353 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java @@ -85,7 +85,7 @@ public class MvcViewTests extends TestCase { assertNotNull(bm); assertEquals(null, bm.getFieldValue("stringProperty")); assertEquals("3", bm.getFieldValue("integerProperty")); - assertEquals("Jan 1, 2008", bm.getFieldValue("dateProperty")); + assertEquals("1/1/08", bm.getFieldValue("dateProperty")); } public void testResumeNoEvent() throws Exception { @@ -121,7 +121,7 @@ public class MvcViewTests extends TestCase { context.putRequestParameter("_eventId", "submit"); context.putRequestParameter("stringProperty", "foo"); context.putRequestParameter("integerProperty", "5"); - context.putRequestParameter("dateProperty", "Jan 1, 2007"); + context.putRequestParameter("dateProperty", "1/1/2007"); BindBean bindBean = new BindBean(); StaticExpression modelObject = new StaticExpression(bindBean); modelObject.setExpressionString("bindBean");