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());