From d616e6bed88e765ed70e4f30207487e436b12e4b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 28 Feb 2012 17:59:57 -0500 Subject: [PATCH] Fix "embedded" mode to be flow session local. The "embedded" mode flag, which is used to suppress redirects so that a flow can be embedded on a surrounding page, was previously stored as a flow execution attribute. The idea was that a flow can be launched in embedded mode simply by passing a flow input rather than having that decision fixed through a static flow definition. However flow execution attributes are not a suitable place to store the embedded mode flag since they are globally shared. Instead this information is now stored a flow session attribute, which means that each individual session (including subflows) can have be in embedded mode or not without affecting others --- build-spring-webflow/resources/changelog.txt | 1 + .../engine/impl/FlowExecutionImpl.java | 8 +++---- .../engine/impl/FlowExecutionImplFactory.java | 2 +- .../webflow/engine/impl/FlowSessionImpl.java | 22 +++++++++++++++---- .../impl/RequestControlContextImpl.java | 3 +-- .../webflow/execution/FlowSession.java | 12 +++++++--- .../webflow/test/MockFlowSession.java | 22 +++++++++++++++---- .../test/MockRequestControlContext.java | 11 +++------- .../webflow/engine/ViewStateTests.java | 4 ++-- .../impl/FlowExecutionImplFactoryTests.java | 5 +++-- 10 files changed, 60 insertions(+), 30 deletions(-) diff --git a/build-spring-webflow/resources/changelog.txt b/build-spring-webflow/resources/changelog.txt index ed7ee50a..195ae8f1 100644 --- a/build-spring-webflow/resources/changelog.txt +++ b/build-spring-webflow/resources/changelog.txt @@ -7,6 +7,7 @@ Changes in version 2.3.1.RELEASE Upgrade JSF Mojarra version to 2.1.7 Modify Jsf2FlowFacesContext.isValidationFailed() to check Web Flow's MessageContext for errors Recognize class-level bean validation messages in BindingResult.getGlobalErrors() +Fix "embedded" mode to be flow session local, i.e. specific to a specific flow or subflow. Changes in version 2.3.0.RELEASE (Feb 28, 2011) ----------------------------------------------- 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 524a4185..161a1def 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 @@ -214,9 +214,6 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { if (logger.isDebugEnabled()) { logger.debug("Starting in " + externalContext + " with input " + input); } - if (hasEmbeddedModeAttribute(input)) { - attributes.asMap().put("embeddedMode", Boolean.TRUE); - } MessageContext messageContext = createMessageContext(null); RequestControlContext requestContext = createRequestContext(externalContext, messageContext); RequestContextHolder.setRequestContext(requestContext); @@ -353,13 +350,16 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { void start(Flow flow, MutableAttributeMap input, RequestControlContext context) { listeners.fireSessionCreating(context, flow); - FlowSession session = activateSession(flow); + FlowSessionImpl session = activateSession(flow); if (session.isRoot()) { status = FlowExecutionStatus.ACTIVE; } if (input == null) { input = new LocalAttributeMap(); } + if (hasEmbeddedModeAttribute(input)) { + session.setEmbeddedMode(); + } StateManageableMessageContext messageContext = (StateManageableMessageContext) context.getMessageContext(); messageContext.setMessageSource(flow.getApplicationContext()); listeners.fireSessionStarting(context, session, input); 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 acc704c6..478388d7 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 @@ -78,7 +78,7 @@ public class FlowExecutionImplFactory implements FlowExecutionFactory { logger.debug("Creating new execution of '" + flowDefinition.getId() + "'"); } FlowExecutionImpl execution = new FlowExecutionImpl((Flow) flowDefinition); - execution.setAttributes(new LocalAttributeMap(executionAttributes.asMap())); + execution.setAttributes(executionAttributes); execution.setListeners(executionListenerLoader.getListeners(execution.getDefinition())); execution.setKeyFactory(executionKeyFactory); return execution; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java index c30fd1e0..85e70122 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java @@ -32,8 +32,8 @@ import org.springframework.webflow.execution.FlowSession; /** * Implementation of the FlowSession interfaced used internally by the FlowExecutionImpl. This class is - * closely coupled with FlowExecutionImpl and RequestControlContextImpl. The three - * classes work together to form a complete flow execution implementation. + * closely coupled with FlowExecutionImpl and RequestControlContextImpl. The three classes + * work together to form a complete flow execution implementation. * * @author Keith Donald * @author Erwin Vervaet @@ -42,6 +42,8 @@ class FlowSessionImpl implements FlowSession, Externalizable { private static final String VIEW_SCOPE_ATTRIBUTE = "viewScope"; + private static final String EMBEDDED_MODE_ATTRIBUTE = "embeddedMode"; + /** * The flow definition (a singleton). *

@@ -119,6 +121,10 @@ class FlowSessionImpl implements FlowSession, Externalizable { return (MutableAttributeMap) scope.get(VIEW_SCOPE_ATTRIBUTE); } + public boolean isEmbeddedMode() { + return (Boolean) scope.get(EMBEDDED_MODE_ATTRIBUTE, Boolean.FALSE); + } + public FlowSession getParent() { return parent; } @@ -224,6 +230,14 @@ class FlowSessionImpl implements FlowSession, Externalizable { this.stateId = stateId; } + /** + * Set a flow session attribute to indicate the current session should execute in embedded mode. + * @see FlowSession#isEmbeddedMode() + */ + void setEmbeddedMode() { + this.scope.put(EMBEDDED_MODE_ATTRIBUTE, true); + } + // internal helpers /** @@ -242,8 +256,8 @@ class FlowSessionImpl implements FlowSession, Externalizable { public String toString() { if (flow != null) { - return new ToStringCreator(this).append("flow", getFlowId()).append("state", getStateId()).append("scope", - scope).toString(); + return new ToStringCreator(this).append("flow", getFlowId()).append("state", getStateId()) + .append("scope", scope).toString(); } else { return "[Unhydrated session '" + flowId + "' in state '" + stateId + "']"; } 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 17f93595..5b4a515d 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 @@ -255,8 +255,7 @@ class RequestControlContextImpl implements RequestControlContext { } public boolean getEmbeddedMode() { - Boolean embedded = flowExecution.getAttributes().getBoolean("embeddedMode"); - return (embedded != null) ? embedded.booleanValue() : false; + return flowExecution.getActiveSession().isEmbeddedMode(); } public String toString() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowSession.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowSession.java index 2ef8d3ef..021a6c2a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowSession.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowSession.java @@ -23,9 +23,9 @@ import org.springframework.webflow.definition.StateDefinition; * A single, local instantiation of a {@link FlowDefinition flow definition} launched within an overall flow execution. *

* This object maintains all instance state including session status within exactly one governing FlowExecution, as well - * as the current flow state. This object also acts as the local "flow scope" data model. Data in - * {@link #getScope() flow scope} lives for the life of this object and is cleaned up automatically when this object is - * destroyed. Destruction happens when this session enters an end state. + * as the current flow state. This object also acts as the local "flow scope" data model. Data in {@link #getScope() + * flow scope} lives for the life of this object and is cleaned up automatically when this object is destroyed. + * Destruction happens when this session enters an end state. *

* Note that a flow session is in no way linked to an HTTP session. It just uses the familiar "session" naming * convention to denote a stateful object. @@ -62,6 +62,12 @@ public interface FlowSession { */ public MutableAttributeMap getViewScope() throws IllegalStateException; + /** + * Returns true if the flow session was started in embedded page mode. An embedded flow can make different + * assumptions with regards to whether redirect after post is necessary. + */ + public boolean isEmbeddedMode(); + /** * Returns the parent flow session in the current flow execution, or null if there is no parent flow * session. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowSession.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowSession.java index 7fd3ee9b..1b76134e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowSession.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowSession.java @@ -36,7 +36,9 @@ import org.springframework.webflow.execution.FlowSession; */ public class MockFlowSession implements FlowSession { - private static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap"; + private static final String VIEW_MAP_ATTRIBUTE = "flowViewMap"; + + private static final String EMBEDDED_MODE_ATTRIBUTE = "embeddedMode"; private Flow definition; @@ -99,7 +101,11 @@ public class MockFlowSession implements FlowSession { throw new IllegalStateException("The current state '" + state.getId() + "' of this flow '" + definition.getId() + "' is not a view state - view scope not accessible"); } - return (MutableAttributeMap) scope.get(FLOW_VIEW_MAP_ATTRIBUTE); + return (MutableAttributeMap) scope.get(VIEW_MAP_ATTRIBUTE); + } + + public boolean isEmbeddedMode() { + return (Boolean) scope.get(EMBEDDED_MODE_ATTRIBUTE, Boolean.FALSE); } public FlowSession getParent() { @@ -163,13 +169,21 @@ public class MockFlowSession implements FlowSession { return state; } + /** + * Set a flow session attribute to indicate the current session should execute in embedded mode. + * @see FlowSession#isEmbeddedMode() + */ + void setEmbeddedMode() { + this.scope.put(EMBEDDED_MODE_ATTRIBUTE, true); + } + // internal helpers private void initViewScope() { - scope.put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap()); + scope.put(VIEW_MAP_ATTRIBUTE, new LocalAttributeMap()); } private void destroyViewScope() { - scope.remove(FLOW_VIEW_MAP_ATTRIBUTE); + scope.remove(VIEW_MAP_ATTRIBUTE); } } \ No newline at end of file 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 ec0b959b..f65a0eb6 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 @@ -138,11 +138,7 @@ public class MockRequestControlContext extends MockRequestContext implements Req } public boolean getEmbeddedMode() { - Boolean embedded = getMockFlowExecutionContext().getAttributes().getBoolean("embeddedMode"); - if (embedded != null) { - return embedded; - } - return false; + return getMockFlowExecutionContext().getMockActiveSession().isEmbeddedMode(); } // implementation specific accessors for testing @@ -157,8 +153,7 @@ public class MockRequestControlContext extends MockRequestContext implements Req .put("redirectInSameState", Boolean.valueOf(redirectInSameState)); } - public void setEmbeddedMode(boolean embedded) { - getMockFlowExecutionContext().getAttributeMap().put("embeddedMode", Boolean.valueOf(embedded)); + public void setEmbeddedMode() { + getMockFlowExecutionContext().getMockActiveSession().setEmbeddedMode(); } - } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java index 8e937b7c..23bcf30b 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/ViewStateTests.java @@ -451,7 +451,7 @@ public class ViewStateTests extends TestCase { state.getTransitionSet().add(t); MockRequestControlContext context = new MockRequestControlContext(flow); context.getMockExternalContext().setAjaxRequest(true); - context.setEmbeddedMode(true); + context.setEmbeddedMode(); context.setAlwaysRedirectOnPause(true); context.setRedirectInSameState(true); state.enter(context); @@ -468,7 +468,7 @@ public class ViewStateTests extends TestCase { MockRequestControlContext context = new MockRequestControlContext(flow); state.enter(context); context.getMockExternalContext().setAjaxRequest(true); - context.setEmbeddedMode(true); + context.setEmbeddedMode(); context.setAlwaysRedirectOnPause(true); context.setRedirectInSameState(true); assertFalse(context.getMockExternalContext().getFlowExecutionRedirectRequested()); 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 88a4d711..23a5adc1 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 @@ -85,7 +85,7 @@ public class FlowExecutionImplFactoryTests extends TestCase { factory.setExecutionAttributes(attributes); FlowExecution execution = factory.createFlowExecution(flowDefinition); assertEquals(attributes, execution.getAttributes()); - assertNotSame(attributes, execution.getAttributes()); + assertSame("Flow execution attributes are global", attributes.asMap(), execution.getAttributes().asMap()); } public void testCreateWithExecutionListener() { @@ -142,7 +142,8 @@ public class FlowExecutionImplFactoryTests extends TestCase { flowExecution.getFlowSessions().add(session1); flowExecution.getFlowSessions().add(session2); factory.restoreFlowExecution(flowExecution, flowDefinition, flowExecutionKey, conversationScope, locator); - assertSame(executionAttributes, flowExecution.getAttributes()); + assertSame("Flow execution attributes are global", flowExecution.getAttributes().asMap(), + executionAttributes.asMap()); assertEquals(1, flowExecution.getListeners().length); assertSame(listener, flowExecution.getListeners()[0]); assertSame(flowExecutionKey, flowExecution.getKey());