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
This commit is contained in:
Rossen Stoyanchev
2012-02-28 17:59:57 -05:00
parent df649084ab
commit d616e6bed8
10 changed files with 60 additions and 30 deletions

View File

@@ -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)
-----------------------------------------------

View File

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

View File

@@ -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;

View File

@@ -32,8 +32,8 @@ import org.springframework.webflow.execution.FlowSession;
/**
* Implementation of the FlowSession interfaced used internally by the <code>FlowExecutionImpl</code>. This class is
* closely coupled with <code>FlowExecutionImpl</code> and <code>RequestControlContextImpl</code>. The three
* classes work together to form a complete flow execution implementation.
* closely coupled with <code>FlowExecutionImpl</code> and <code>RequestControlContextImpl</code>. 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).
* <p>
@@ -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 + "']";
}

View File

@@ -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() {

View File

@@ -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.
* <p>
* 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.
* <p>
* Note that a flow <i>session</i> 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 <code>null</code> if there is no parent flow
* session.

View File

@@ -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);
}
}

View File

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

View File

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

View File

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