view-scope polishing
This commit is contained in:
@@ -117,14 +117,4 @@ public interface RequestControlContext extends RequestContext {
|
||||
*/
|
||||
public boolean getAlwaysRedirectOnPause();
|
||||
|
||||
/**
|
||||
* Initialize view scope. Called by view states when they enter.
|
||||
*/
|
||||
public void initViewScope();
|
||||
|
||||
/**
|
||||
* Destroy view-scope. Called by view-states when they exit.
|
||||
*/
|
||||
public void destroyViewScope();
|
||||
|
||||
}
|
||||
@@ -162,7 +162,6 @@ public class ViewState extends TransitionableState {
|
||||
}
|
||||
|
||||
protected void doPreEntryActions(RequestControlContext context) throws FlowExecutionException {
|
||||
context.initViewScope();
|
||||
createVariables(context);
|
||||
}
|
||||
|
||||
@@ -203,7 +202,6 @@ public class ViewState extends TransitionableState {
|
||||
|
||||
public void exit(RequestControlContext context) {
|
||||
destroyVariables(context);
|
||||
context.destroyViewScope();
|
||||
super.exit(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +331,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
|
||||
void start(Flow flow, MutableAttributeMap input, RequestControlContext context) {
|
||||
listeners.fireSessionCreating(context, flow);
|
||||
FlowSession session = activateSession(flow);
|
||||
if (input == null) {
|
||||
input = new LocalAttributeMap();
|
||||
}
|
||||
listeners.fireSessionStarting(context, session, input);
|
||||
flow.start(context, input);
|
||||
listeners.fireSessionStarted(context, session);
|
||||
|
||||
@@ -40,6 +40,8 @@ import org.springframework.webflow.execution.FlowSession;
|
||||
*/
|
||||
class FlowSessionImpl implements FlowSession, Externalizable {
|
||||
|
||||
private static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap";
|
||||
|
||||
/**
|
||||
* The flow definition (a singleton).
|
||||
* <p>
|
||||
@@ -104,6 +106,18 @@ class FlowSessionImpl implements FlowSession, Externalizable {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public MutableAttributeMap getViewScope() throws IllegalStateException {
|
||||
if (state == null) {
|
||||
throw new IllegalStateException("The current state of this flow '" + flow.getId()
|
||||
+ "' is [null] - cannot access view scope");
|
||||
}
|
||||
if (!state.isViewState()) {
|
||||
throw new IllegalStateException("The current state '" + state.getId() + "' of this flow '" + flow.getId()
|
||||
+ "' is not a view state - view scope not accessible");
|
||||
}
|
||||
return (MutableAttributeMap) scope.get(FLOW_VIEW_MAP_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public FlowSession getParent() {
|
||||
return parent;
|
||||
}
|
||||
@@ -158,8 +172,14 @@ class FlowSessionImpl implements FlowSession, Externalizable {
|
||||
Assert.notNull(state, "The state is required");
|
||||
Assert.isTrue(flow == state.getOwner(),
|
||||
"The state does not belong to the flow associated with this flow session");
|
||||
if (this.state != null && this.state.isViewState()) {
|
||||
destroyViewScope();
|
||||
}
|
||||
this.state = state;
|
||||
this.stateId = state.getId();
|
||||
if (this.state.isViewState()) {
|
||||
initViewScope();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,6 +196,16 @@ class FlowSessionImpl implements FlowSession, Externalizable {
|
||||
return stateId;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private void initViewScope() {
|
||||
scope.put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap());
|
||||
}
|
||||
|
||||
private void destroyViewScope() {
|
||||
scope.remove(FLOW_VIEW_MAP_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("flow", flowId).append("state", stateId).append("scope", scope)
|
||||
.toString();
|
||||
|
||||
@@ -48,8 +48,6 @@ import org.springframework.webflow.execution.FlowSession;
|
||||
*/
|
||||
class RequestControlContextImpl implements RequestControlContext {
|
||||
|
||||
private static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap";
|
||||
|
||||
/**
|
||||
* The owning flow execution carrying out this request.
|
||||
*/
|
||||
@@ -123,14 +121,7 @@ class RequestControlContextImpl implements RequestControlContext {
|
||||
}
|
||||
|
||||
public MutableAttributeMap getViewScope() throws IllegalStateException {
|
||||
if (!flowExecution.isActive()) {
|
||||
throw new IllegalStateException("This flow is not active");
|
||||
}
|
||||
if (!getCurrentState().isViewState()) {
|
||||
throw new IllegalStateException("The current state '" + getCurrentState().getId() + "' of this flow '"
|
||||
+ getActiveFlow().getId() + "' is not a view state - view scope not accessible");
|
||||
}
|
||||
return (MutableAttributeMap) getFlowScope().get(FLOW_VIEW_MAP_ATTRIBUTE);
|
||||
return flowExecution.getActiveSession().getViewScope();
|
||||
}
|
||||
|
||||
public MutableAttributeMap getFlowScope() {
|
||||
@@ -213,14 +204,6 @@ class RequestControlContextImpl implements RequestControlContext {
|
||||
flowExecution.start(flow, input, this);
|
||||
}
|
||||
|
||||
public void initViewScope() {
|
||||
getFlowScope().put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap());
|
||||
}
|
||||
|
||||
public void destroyViewScope() {
|
||||
getFlowScope().remove(FLOW_VIEW_MAP_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public boolean handleEvent(Event event) throws FlowExecutionException {
|
||||
this.lastEvent = event;
|
||||
return flowExecution.handleEvent(event, this);
|
||||
|
||||
@@ -100,8 +100,8 @@ public interface FlowExecutionContext {
|
||||
public FlowSession getActiveSession() throws IllegalStateException;
|
||||
|
||||
/**
|
||||
* Returns a mutable map for data held in "flash scope". Attributes in this map are cleared out on the next event
|
||||
* signaled against this flow execution. Flash attributes survive flow execution refresh operations.
|
||||
* Returns a mutable map for data held in "flash scope". Attributes in this map are cleared out on the next view
|
||||
* rendering. Flash attributes survive flow execution refresh operations.
|
||||
* @return flash scope
|
||||
*/
|
||||
public MutableAttributeMap getFlashScope();
|
||||
|
||||
@@ -54,6 +54,14 @@ public interface FlowSession {
|
||||
*/
|
||||
public MutableAttributeMap getScope();
|
||||
|
||||
/**
|
||||
* Returns a mutable map for data held in "view scope". Attributes in this map are cleared out when the current view
|
||||
* state exits.
|
||||
* @return view scope
|
||||
* @throws IllegalStateException if this flow session is not currently in a view state
|
||||
*/
|
||||
public MutableAttributeMap getViewScope() throws IllegalStateException;
|
||||
|
||||
/**
|
||||
* Returns the parent flow session in the current flow execution, or <code>null</code> if there is no parent flow
|
||||
* session.
|
||||
|
||||
@@ -36,14 +36,14 @@ import org.springframework.webflow.execution.FlowSession;
|
||||
*/
|
||||
public class MockFlowSession implements FlowSession {
|
||||
|
||||
private static final String FLOW_VIEW_MAP_ATTRIBUTE = "flowViewMap";
|
||||
|
||||
private Flow definition;
|
||||
|
||||
private State state;
|
||||
|
||||
private MutableAttributeMap scope = new LocalAttributeMap();
|
||||
|
||||
private MutableAttributeMap flashMap = new LocalAttributeMap();
|
||||
|
||||
private FlowSession parent;
|
||||
|
||||
/**
|
||||
@@ -90,8 +90,16 @@ public class MockFlowSession implements FlowSession {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public MutableAttributeMap getFlashMap() {
|
||||
return flashMap;
|
||||
public MutableAttributeMap getViewScope() throws IllegalStateException {
|
||||
if (state == null) {
|
||||
throw new IllegalStateException("The current state of this flow '" + definition.getId()
|
||||
+ "' is [null] - cannot access view scope");
|
||||
}
|
||||
if (!state.isViewState()) {
|
||||
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);
|
||||
}
|
||||
|
||||
public FlowSession getParent() {
|
||||
@@ -115,7 +123,13 @@ public class MockFlowSession implements FlowSession {
|
||||
* Set the currently active state.
|
||||
*/
|
||||
public void setState(State state) {
|
||||
if (this.state != null && this.state.isViewState()) {
|
||||
destroyViewScope();
|
||||
}
|
||||
this.state = state;
|
||||
if (this.state != null && this.state.isViewState()) {
|
||||
initViewScope();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +147,7 @@ public class MockFlowSession implements FlowSession {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
// conveniece accessors
|
||||
// convenience accessors
|
||||
|
||||
/**
|
||||
* Returns the flow definition of this session.
|
||||
@@ -148,4 +162,14 @@ public class MockFlowSession implements FlowSession {
|
||||
public State getStateInternal() {
|
||||
return state;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private void initViewScope() {
|
||||
scope.put(FLOW_VIEW_MAP_ATTRIBUTE, new LocalAttributeMap());
|
||||
}
|
||||
|
||||
private void destroyViewScope() {
|
||||
scope.remove(FLOW_VIEW_MAP_ATTRIBUTE);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import junit.framework.TestCase;
|
||||
|
||||
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.FlowExecutionImplFactory;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
@@ -33,9 +34,8 @@ import org.springframework.webflow.test.MockExternalContext;
|
||||
* <p>
|
||||
* More specifically, a typical flow execution test case will test:
|
||||
* <ul>
|
||||
* <li>That the flow execution starts as expected given a request from an external context containing potential input
|
||||
* attributes (see the {@link #startFlow(ExternalContext)} variants).
|
||||
* <li>That given the set of supported state transition criteria a state executes the appropriate transition when a
|
||||
* <li>That the flow execution starts as expected (see {@link #startFlow(MutableAttributeMap, ExternalContext)}).
|
||||
* <li>That given the set of supported state transition criteria, a state executes the appropriate transition when a
|
||||
* matching event is signaled (with potential input request parameters, see the {@link #resumeFlow(ExternalContext)}
|
||||
* variants). A test case should be coded for each logical event that can occur, where an event drives a possible path
|
||||
* through the flow. The goal should be to exercise all possible paths of the flow. Use a test coverage tool like Clover
|
||||
@@ -76,7 +76,6 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
|
||||
/**
|
||||
* Constructs a flow execution test with given name.
|
||||
* @param name the name of the test
|
||||
* @since 1.0.2
|
||||
*/
|
||||
public AbstractFlowExecutionTests(String name) {
|
||||
super(name);
|
||||
@@ -101,9 +100,9 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
|
||||
* execution during the start operation
|
||||
* @throws FlowExecutionException if an exception was thrown while starting the flow execution
|
||||
*/
|
||||
protected void startFlow(ExternalContext context) throws FlowExecutionException {
|
||||
protected void startFlow(MutableAttributeMap input, ExternalContext context) throws FlowExecutionException {
|
||||
flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());
|
||||
flowExecution.start(null, context);
|
||||
flowExecution.start(input, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +113,7 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
|
||||
*/
|
||||
protected void resumeFlow(ExternalContext context) throws FlowExecutionException {
|
||||
Assert.state(flowExecution != null, "The flow execution to test is [null]; "
|
||||
+ "you must start the flow execution before you can signal an event against it!");
|
||||
+ "you must start the flow execution before you can resume it!");
|
||||
flowExecution.resume(context);
|
||||
}
|
||||
|
||||
@@ -127,40 +126,71 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
|
||||
*/
|
||||
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 query it!");
|
||||
"The flow execution to test is [null]; you must start the flow execution before you can access it!");
|
||||
return flowExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute in conversation scope. Conversation-scoped attributes are shared by all flow sessions.
|
||||
* 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
|
||||
*/
|
||||
protected Object getConversationAttribute(String attributeName) {
|
||||
return getFlowExecution().getConversationScope().get(attributeName);
|
||||
protected Object getFlashAttribute(String attributeName) {
|
||||
return getFlowExecution().getFlashScope().get(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in conversation scope; asserts the attribute is present. Conversation-scoped
|
||||
* attributes are shared by all flow sessions.
|
||||
* 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
|
||||
*/
|
||||
protected Object getRequiredConversationAttribute(String attributeName) throws IllegalStateException {
|
||||
return getFlowExecution().getConversationScope().getRequired(attributeName);
|
||||
protected Object getRequiredFlashAttribute(String attributeName) throws IllegalStateException {
|
||||
return getFlowExecution().getFlashScope().getRequired(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in conversation scope; asserts the attribute is present and of the required type.
|
||||
* Conversation-scoped attributes are shared by all flow sessions.
|
||||
* 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 not of the required type
|
||||
* @throws IllegalStateException if the attribute was not present or was of the wrong type
|
||||
*/
|
||||
protected Object getRequiredConversationAttribute(String attributeName, Class requiredType)
|
||||
throws IllegalStateException {
|
||||
return getFlowExecution().getConversationScope().getRequired(attributeName, requiredType);
|
||||
protected Object getRequiredFlashAttribute(String attributeName, Class requiredType) throws IllegalStateException {
|
||||
return getFlowExecution().getFlashScope().getRequired(attributeName, requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute in view scope. View-scoped attributes are local to the current view state and are cleared
|
||||
* when the view state exits.
|
||||
* @param attributeName the name of the attribute
|
||||
* @return the attribute value
|
||||
*/
|
||||
protected Object getViewAttribute(String attributeName) {
|
||||
return getFlowExecution().getActiveSession().getViewScope().get(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in view scope; asserts the attribute is present. View-scoped attributes are local
|
||||
* to the current view state and are cleared when the view state exits.
|
||||
* @param attributeName the name of the attribute
|
||||
* @return the attribute value
|
||||
* @throws IllegalStateException if the attribute was not present
|
||||
*/
|
||||
protected Object getRequiredViewAttribute(String attributeName) throws IllegalStateException {
|
||||
return getFlowExecution().getActiveSession().getViewScope().getRequired(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in view scope; asserts the attribute is present and of the correct type.
|
||||
* View-scoped attributes are local to the current view state and are cleared when the view state exits.
|
||||
* @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
|
||||
*/
|
||||
protected Object getRequiredViewAttribute(String attributeName, Class requiredType) throws IllegalStateException {
|
||||
return getFlowExecution().getActiveSession().getViewScope().getRequired(attributeName, requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,35 +225,35 @@ public abstract class AbstractFlowExecutionTests extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute in flash scope. Flash-scoped attributes are local to the active flow session and cleared on
|
||||
* the next user event.
|
||||
* Returns the attribute in conversation scope. Conversation-scoped attributes are shared by all flow sessions.
|
||||
* @param attributeName the name of the attribute
|
||||
* @return the attribute value
|
||||
*/
|
||||
protected Object getFlashAttribute(String attributeName) {
|
||||
return getFlowExecution().getFlashScope().get(attributeName);
|
||||
protected Object getConversationAttribute(String attributeName) {
|
||||
return getFlowExecution().getConversationScope().get(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in flash scope; asserts the attribute is present. Flash-scoped attributes are
|
||||
* local to the active flow session and cleared on the next user event.
|
||||
* Returns the required attribute in conversation scope; asserts the attribute is present. Conversation-scoped
|
||||
* attributes are shared by all flow sessions.
|
||||
* @param attributeName the name of the attribute
|
||||
* @return the attribute value
|
||||
* @throws IllegalStateException if the attribute was not present
|
||||
*/
|
||||
protected Object getRequiredFlashAttribute(String attributeName) throws IllegalStateException {
|
||||
return getFlowExecution().getFlashScope().getRequired(attributeName);
|
||||
protected Object getRequiredConversationAttribute(String attributeName) throws IllegalStateException {
|
||||
return getFlowExecution().getConversationScope().getRequired(attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required attribute in flash scope; asserts the attribute is present and of the correct type.
|
||||
* Flash-scoped attributes are local to the active flow session and cleared on the next user event.
|
||||
* Returns the required attribute in conversation scope; asserts the attribute is present and of the required type.
|
||||
* Conversation-scoped attributes are shared by all flow sessions.
|
||||
* @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
|
||||
* @throws IllegalStateException if the attribute was not present or not of the required type
|
||||
*/
|
||||
protected Object getRequiredFlashAttribute(String attributeName, Class requiredType) throws IllegalStateException {
|
||||
return getFlowExecution().getFlashScope().getRequired(attributeName, requiredType);
|
||||
protected Object getRequiredConversationAttribute(String attributeName, Class requiredType)
|
||||
throws IllegalStateException {
|
||||
return getFlowExecution().getConversationScope().getRequired(attributeName, requiredType);
|
||||
}
|
||||
|
||||
// assert helpers
|
||||
|
||||
@@ -99,6 +99,29 @@ public class FlowExecutionImplTests extends TestCase {
|
||||
assertEquals(1, mockListener.getPausedCount());
|
||||
}
|
||||
|
||||
public void testStartWithNullInputMap() {
|
||||
Flow flow = new Flow("flow");
|
||||
new State(flow, "state") {
|
||||
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
|
||||
// no op
|
||||
}
|
||||
};
|
||||
MockFlowExecutionListener mockListener = new MockFlowExecutionListener() {
|
||||
public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap input) {
|
||||
super.sessionStarting(context, session, input);
|
||||
assertNotNull(input);
|
||||
}
|
||||
};
|
||||
FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener };
|
||||
FlowExecutionImpl execution = new FlowExecutionImpl(flow);
|
||||
execution.setListeners(listeners);
|
||||
execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource()));
|
||||
MockExternalContext context = new MockExternalContext();
|
||||
execution.start(null, context);
|
||||
assertTrue(execution.isActive());
|
||||
assertEquals(1, mockListener.getPausedCount());
|
||||
}
|
||||
|
||||
public void testStartExceptionThrownBeforeFirstSessionCreated() {
|
||||
Flow flow = new Flow("flow");
|
||||
new EndState(flow, "end");
|
||||
|
||||
@@ -39,12 +39,12 @@ public class SearchFlowExecutionTests extends AbstractXmlFlowExecutionTests {
|
||||
|
||||
public void testStartFlow() {
|
||||
ExternalContext context = new MockExternalContext();
|
||||
startFlow(context);
|
||||
startFlow(null, context);
|
||||
assertCurrentStateEquals("enterCriteria");
|
||||
}
|
||||
|
||||
public void testCriteriaSubmitSuccess() {
|
||||
startFlow(new MockExternalContext());
|
||||
startFlow(null, new MockExternalContext());
|
||||
MockExternalContext context = new MockExternalContext();
|
||||
context.putRequestParameter("firstName", "Keith");
|
||||
context.putRequestParameter("lastName", "Donald");
|
||||
@@ -55,7 +55,7 @@ public class SearchFlowExecutionTests extends AbstractXmlFlowExecutionTests {
|
||||
}
|
||||
|
||||
public void testNewSearch() {
|
||||
startFlow(new MockExternalContext());
|
||||
startFlow(null, new MockExternalContext());
|
||||
MockExternalContext context = new MockExternalContext();
|
||||
context.putRequestParameter("firstName", "Keith");
|
||||
context.putRequestParameter("lastName", "Donald");
|
||||
@@ -70,7 +70,7 @@ public class SearchFlowExecutionTests extends AbstractXmlFlowExecutionTests {
|
||||
}
|
||||
|
||||
public void testSelectValidResult() {
|
||||
startFlow(new MockExternalContext());
|
||||
startFlow(null, new MockExternalContext());
|
||||
MockExternalContext context = new MockExternalContext();
|
||||
context.putRequestParameter("firstName", "Keith");
|
||||
context.putRequestParameter("lastName", "Donald");
|
||||
|
||||
Reference in New Issue
Block a user