diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/resource/FlowResourceHelper.java b/spring-faces/src/main/java/org/springframework/faces/ui/resource/FlowResourceHelper.java index 8ef8203c..a481a9ba 100644 --- a/spring-faces/src/main/java/org/springframework/faces/ui/resource/FlowResourceHelper.java +++ b/spring-faces/src/main/java/org/springframework/faces/ui/resource/FlowResourceHelper.java @@ -10,8 +10,6 @@ import java.util.Set; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.RequestPath; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; @@ -64,9 +62,7 @@ public class FlowResourceHelper { writer.writeAttribute(key, attributes.get(key), null); } - FlowDefinitionRequestInfo requestInfo = new FlowDefinitionRequestInfo("resources", new RequestPath(scriptPath), - null, null); - String src = requestContext.getExternalContext().buildFlowDefinitionUrl(requestInfo); + String src = requestContext.getExternalContext().getContextPath() + "/resources" + scriptPath; writer.writeAttribute("src", src, null); @@ -96,9 +92,7 @@ public class FlowResourceHelper { writer.writeAttribute("type", "text/css", null); writer.writeAttribute("rel", "stylesheet", null); - FlowDefinitionRequestInfo requestInfo = new FlowDefinitionRequestInfo("resources", new RequestPath(cssPath), - null, null); - String src = requestContext.getExternalContext().buildFlowDefinitionUrl(requestInfo); + String src = requestContext.getExternalContext().getContextPath() + "/resources" + cssPath; writer.writeAttribute("href", src, null); diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java index f4fac490..d6ef8af6 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java @@ -95,8 +95,8 @@ public class JsfView implements View { FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); FacesContext defaultFacesContext = facesContextFactory.getFacesContext(context.getExternalContext() - .getContext(), context.getExternalContext().getRequest(), context.getExternalContext().getResponse(), - facesLifecycle); + .getNativeContext(), context.getExternalContext().getNativeRequest(), context.getExternalContext() + .getNativeResponse(), facesLifecycle); return new FlowFacesContext(context, defaultFacesContext); } 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 be56881f..65cbef97 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 @@ -129,8 +129,8 @@ public class JsfViewFactory implements ViewFactory { FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); FacesContext defaultFacesContext = facesContextFactory.getFacesContext(context.getExternalContext() - .getContext(), context.getExternalContext().getRequest(), context.getExternalContext().getResponse(), - lifecycle); + .getNativeContext(), context.getExternalContext().getNativeRequest(), context.getExternalContext() + .getNativeResponse(), lifecycle); return new FlowFacesContext(context, defaultFacesContext); } diff --git a/spring-faces/src/test/java/org/springframework/faces/ui/resource/FlowResourceHelperTests.java b/spring-faces/src/test/java/org/springframework/faces/ui/resource/FlowResourceHelperTests.java index 692f4ada..d742d5b9 100644 --- a/spring-faces/src/test/java/org/springframework/faces/ui/resource/FlowResourceHelperTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/ui/resource/FlowResourceHelperTests.java @@ -1,17 +1,14 @@ package org.springframework.faces.ui.resource; -import org.easymock.EasyMock; - import java.io.IOException; import java.io.StringWriter; import junit.framework.TestCase; import org.apache.shale.test.mock.MockResponseWriter; +import org.easymock.EasyMock; import org.springframework.faces.webflow.JSFMockHelper; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.RequestPath; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; @@ -39,13 +36,9 @@ public class FlowResourceHelperTests extends TestCase { public final void testRenderScriptLink() throws IOException { String scriptPath = "/dojo/dojo.js"; - String expectedUrl = "/context/spring/resources/dojo/dojo.js"; + String expectedUrl = "/context/resources/dojo/dojo.js"; - FlowDefinitionRequestInfo expectedRequest = new FlowDefinitionRequestInfo("resources", new RequestPath( - scriptPath), null, null); - - EasyMock.expect(externalContext.buildFlowDefinitionUrl(requestInfoMatches(expectedRequest))).andReturn( - expectedUrl); + EasyMock.expect(externalContext.getContextPath()).andReturn("/context"); EasyMock.replay(new Object[] { requestContext, externalContext }); @@ -63,13 +56,9 @@ public class FlowResourceHelperTests extends TestCase { public final void testRenderStyleLink() throws IOException { String scriptPath = "/dijit/themes/dijit.css"; - String expectedUrl = "/context/spring/resources/dijit/themes/dijit.css"; + String expectedUrl = "/context/resources/dijit/themes/dijit.css"; - FlowDefinitionRequestInfo expectedRequest = new FlowDefinitionRequestInfo("resources", new RequestPath( - scriptPath), null, null); - - EasyMock.expect(externalContext.buildFlowDefinitionUrl(requestInfoMatches(expectedRequest))).andReturn( - expectedUrl); + EasyMock.expect(externalContext.getContextPath()).andReturn("/context"); EasyMock.replay(new Object[] { requestContext, externalContext }); @@ -82,9 +71,4 @@ public class FlowResourceHelperTests extends TestCase { assertEquals(expectedOutput, writer.toString()); } - - static FlowDefinitionRequestInfo requestInfoMatches(FlowDefinitionRequestInfo info) { - EasyMock.reportMatcher(new RequestInfoMatcher(info)); - return null; - } } diff --git a/spring-faces/src/test/java/org/springframework/faces/ui/resource/RequestInfoMatcher.java b/spring-faces/src/test/java/org/springframework/faces/ui/resource/RequestInfoMatcher.java deleted file mode 100644 index 24a4935a..00000000 --- a/spring-faces/src/test/java/org/springframework/faces/ui/resource/RequestInfoMatcher.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.springframework.faces.ui.resource; - -import org.easymock.IArgumentMatcher; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; - -class RequestInfoMatcher implements IArgumentMatcher { - - private FlowDefinitionRequestInfo expected; - - public RequestInfoMatcher(FlowDefinitionRequestInfo expected) { - this.expected = expected; - } - - public void appendTo(StringBuffer buffer) { - buffer.append("/" + expected.getFlowDefinitionId() + expected.getRequestPath()); - } - - public boolean matches(Object actual) { - FlowDefinitionRequestInfo actualRequest = (FlowDefinitionRequestInfo) actual; - return expected.getFlowDefinitionId().equals(actualRequest.getFlowDefinitionId()) - && expected.getRequestPath().equals(actualRequest.getRequestPath()); - } -} 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 977f3629..1a61c97c 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 @@ -21,7 +21,9 @@ import org.easymock.EasyMock; import org.jboss.el.ExpressionFactoryImpl; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.support.ParserContextImpl; -import org.springframework.webflow.context.ExternalContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.LocalParameterMap; @@ -73,7 +75,10 @@ public class JsfFinalResponseActionTests extends TestCase { RequestContext.class).expect(String.class)), null, lifecycle); finalResponseAction = new JsfFinalResponseAction(factory); RequestContextHolder.setRequestContext(context); - ExternalContext ext = new MockExternalContext(); + MockExternalContext ext = new MockExternalContext(); + ext.setNativeContext(new MockServletContext()); + ext.setNativeRequest(new MockHttpServletRequest()); + ext.setNativeResponse(new MockHttpServletResponse()); EasyMock.expect(context.getExternalContext()).andStubReturn(ext); AttributeMap flash = new LocalAttributeMap(); EasyMock.expect(context.getFlashScope()).andStubReturn(flash); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/ExternalRedirectAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/ExternalRedirectAction.java index 3dc2dd6d..df381ad6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/ExternalRedirectAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/ExternalRedirectAction.java @@ -16,7 +16,7 @@ public class ExternalRedirectAction extends AbstractAction { protected Event doExecute(RequestContext context) throws Exception { String resourceUri = (String) this.resourceUri.getValue(context); - context.getExternalContext().sendExternalRedirect(resourceUri); + context.getExternalContext().requestExternalRedirect(resourceUri); return success(); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java index 374485dd..61c825f8 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/FlowDefinitionRedirectAction.java @@ -6,60 +6,42 @@ import java.util.Map; import org.springframework.binding.expression.Expression; import org.springframework.util.Assert; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.RequestPath; -import org.springframework.webflow.core.collection.LocalParameterMap; -import org.springframework.webflow.core.collection.ParameterMap; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; public class FlowDefinitionRedirectAction extends AbstractAction { private Expression flowId; - private Expression[] requestElements; - private Map requestParameters; + private Map input; - public FlowDefinitionRedirectAction(Expression flowId, Expression[] requestElements, Map requestParameters) { + public FlowDefinitionRedirectAction(Expression flowId, Map input) { Assert.notNull(flowId, "The flow id to redirect to is required"); this.flowId = flowId; - this.requestElements = requestElements; - this.requestParameters = requestParameters; + this.input = input; } protected Event doExecute(RequestContext context) throws Exception { String flowId = (String) this.flowId.getValue(context); - RequestPath requestPath = evaluateRequestPath(context); - ParameterMap requestParameters = evaluateRequestParameters(context); - context.getExternalContext().sendFlowDefinitionRedirect( - new FlowDefinitionRequestInfo(flowId, requestPath, requestParameters, null)); + AttributeMap input = evaluateInput(context); + context.getExternalContext().requestFlowDefinitionRedirect(flowId, input); return success(); } - private RequestPath evaluateRequestPath(RequestContext context) { - if (this.requestElements == null || this.requestElements.length == 0) { - return null; - } - String[] requestElements = new String[this.requestElements.length]; - for (int i = 0; i < this.requestElements.length; i++) { - Expression element = this.requestElements[i]; - requestElements[i] = (String) element.getValue(context); - } - return RequestPath.valueOf(requestElements); - } - - private ParameterMap evaluateRequestParameters(RequestContext context) { - if (this.requestParameters == null) { + private AttributeMap evaluateInput(RequestContext context) { + if (this.input == null) { return null; } else { - Map requestParameters = new HashMap(); - for (Iterator it = this.requestParameters.entrySet().iterator(); it.hasNext();) { + Map input = new HashMap(); + for (Iterator it = this.input.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); Expression name = (Expression) entry.getKey(); Expression value = (Expression) entry.getValue(); String paramName = (String) name.getValue(context); - String paramValue = (String) value.getValue(context); - requestParameters.put(paramName, paramValue); + Object paramValue = value.getValue(context); + input.put(paramName, paramValue); } - return new LocalParameterMap(requestParameters); + return new LocalAttributeMap(input); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/AbstractFlowRequestInfo.java b/spring-webflow/src/main/java/org/springframework/webflow/context/AbstractFlowRequestInfo.java deleted file mode 100644 index 73ad37f1..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/AbstractFlowRequestInfo.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.springframework.webflow.context; - -import org.springframework.webflow.core.collection.ParameterMap; - -/** - * An abstract representation request to be sent into the Spring Web Flow system at a later date. Used to request - * operations such as flow definition redirects, flow execution redirects, as well as to generate flow definition and - * flow execution URLs. - * - * @author Keith Donald - */ -public abstract class AbstractFlowRequestInfo { - - /** - * The flow definition the request should be sent to. - */ - private String flowDefinitionId; - - /** - * Hierarchical data to be sent along in the request. - */ - private RequestPath requestPath; - - /** - * Query parameters to be sent along in the request. - */ - private ParameterMap requestParameters; - - /** - * An anchor fragment to be sent in the request, for interpretation by the agent that initiates the request. - */ - private String fragment; - - protected AbstractFlowRequestInfo(String flowDefinitionId, RequestPath requestPath, ParameterMap requestParameters, - String fragment) { - this.flowDefinitionId = flowDefinitionId; - this.requestPath = requestPath; - this.requestParameters = requestParameters; - this.fragment = fragment; - } - - /** - * Returns the flow definition this request should be sent to. - * @return the flow definition identifier - */ - public String getFlowDefinitionId() { - return flowDefinitionId; - } - - /** - * Returns hierarchical data to be sent along in the request. - * @return the request path - */ - public RequestPath getRequestPath() { - return requestPath; - } - - /** - * Returns query parameters to send along in the request. - * @return the request parameters - */ - public ParameterMap getRequestParameters() { - return requestParameters; - } - - /** - * Returns the fragment to be sent along in the request. A fragment is a free-form string value that may be - * interpreted by the client agent that initiates the request. - * @return the fragment - */ - public String getFragment() { - return fragment; - } -} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java index 610d435d..e2ced10d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java @@ -15,9 +15,9 @@ */ package org.springframework.webflow.context; -import java.io.PrintWriter; +import java.io.Writer; -import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.core.collection.ParameterMap; import org.springframework.webflow.core.collection.SharedAttributeMap; @@ -40,31 +40,10 @@ import org.springframework.webflow.core.collection.SharedAttributeMap; public interface ExternalContext { /** - * Returns the unique id of the flow definition invoked by this caller. For a "launch" request, this identifier is - * used directly to create a new flow execution. For a "resume" request, this identifier provides additional flow - * execution context. - * @return the flow definition identifier, never null - * @see #getFlowExecutionKey() + * Returns the logical path to the application hosting this external context. + * @return the context path */ - public String getFlowId(); - - /** - * Returns the unique key identifying the flow execution this client wishes to resume. - * @return the flow execution key, may be null if this is not a resume request - */ - public String getFlowExecutionKey(); - - /** - * Returns the type of this client request. For example, this request may be a "GET" request or a "POST" request. - * @return the request method - */ - public String getRequestMethod(); - - /** - * Returns the path of this request as a ordered list of fields. - * @return the elements of the request path - */ - public RequestPath getRequestPath(); + public String getContextPath(); /** * Provides access to the parameters associated with the user request that led to SWF being called. This map is @@ -105,113 +84,82 @@ public interface ExternalContext { */ public SharedAttributeMap getApplicationMap(); + /** + * Returns true if the current request is an Ajax request. + * @return true if the current request is an Ajax request + */ + public boolean isAjaxRequest(); + + /** + * Get a context-relative flow execution URL for the execution with the provided key. Typically used by response + * writers that write out references to the flow execution to support postback on a subsequent request. + * @param flowId the flow definition id + * @param flowExecutionKey the flow execution key + * @return the flow execution URL + */ + public String getFlowExecutionUri(String flowId, String flowExecutionKey); + /** * Provides access to the context object for the current environment. * @return the environment specific context object */ - public Object getContext(); + public Object getNativeContext(); /** * Provides access to the request object for the current environment. * @return the environment specific request object. */ - public Object getRequest(); + public Object getNativeRequest(); /** * Provides access to the response object for the current environment. * @return the environment specific response object. */ - public Object getResponse(); + public Object getNativeResponse(); /** * Get a writer for writing out a response. * @return the writer */ - public PrintWriter getResponseWriter(); + public Writer getResponseWriter(); /** - * Builds a context-relative flow definition URL, suitable for rendering links that a launch new execution of a flow - * definition when accessed. - * @param requestInfo data needed to build the flow definition path - * @return the generated flow definition URL + * Sets an entry in the response header. + * @param name the entry name + * @param value the entry value */ - public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo requestInfo); + public void setResponseHeader(String name, String value); /** - * Builds a flow execution URL, suitable for rendering links that resume a paused flow execution when accessed. - * @param requestInfo data needed to build the flow execution URL - * @param contextRelative whether the URL returned should be relative to this external context or absolute. - * @return the generated flow execution URL - */ - public String buildFlowExecutionUrl(FlowExecutionRequestInfo requestInfo, boolean contextRelative); - - /** - * Encode the provided string using the encoding scheme of this external context. - * @param string the string - * @return the encoded string - */ - public String encode(String string); - - /** - * Request that a flow execution redirect be sent as the response. A flow execution redirect tells the caller to - * resume a flow execution in a new request. Sets response committed to true. - * @param requestInfo data needed to issue the flow execution redirect + * Request that a flow execution redirect be performed by the calling environment. Typically called from within a + * flow execution to request a refresh operation, usually to support "refresh after event processing" behavior. + * Calling this method commits the response. * @see #isResponseCommitted() */ - public void sendFlowExecutionRedirect(FlowExecutionRequestInfo requestInfo); + public void requestFlowExecutionRedirect(); /** - * Request that a flow definition redirect be sent as the response. A flow definition redirect tells the caller to - * start a new execution of the flow definition with the input provided. - * @param requestInfo data needed to issue the flow definition redirect - */ - public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo requestInfo); - - /** - * Request that a external redirect be sent as the response. An external redirect tells the caller to access the - * resource at the given resource URL. Sets response committed to true. Note: no special encoding is performed on - * the string argument. Callers must perform their own encoding when necessary. - * @param resourceUrl the resource URL string + * Request that a flow definition redirect be performed by the calling environment. Typically called from within a + * flow execution end state to request starting a new, independent execution of a flow in a chain-like manner. + * Calling this method commits the response. * @see #isResponseCommitted() + * @param flowId the id of the flow definition to redirect to + * @param input input to pass the flow; this input is generally encoded the url to launch the flow */ - public void sendExternalRedirect(String resourceUrl); + public void requestFlowDefinitionRedirect(String flowId, AttributeMap input); /** - * Report that flow execution request processing ended with a "paused" result, indicating the flow execution paused - * and will be waiting to resume on a subsequent request. - * @param flowExecutionKey the flow execution key + * Request a redirect to an arbitrary resource URI. May not be supported in some environments. Calling this method + * commits the response. + * @see #isResponseCommitted() + * @param uri the URI to redirect to */ - public void setPausedResult(String flowExecutionKey); + public void requestExternalRedirect(String uri); /** - * Report that flow execution request processing ended with a "ended" result, indicating the flow execution - * terminated. - * @param flowExecutionKey the flow execution key, now invalid or null if never assigned - */ - public void setEndedResult(String flowExecutionKey); - - /** - * Report that flow execution request processing ended with a flow exception. - * @param e the flow exception - */ - public void setExceptionResult(FlowException e); - - /** - * Returns true if the current request has already provisioned the response that will be sent back to the calling - * system. - * @return true if the response has been committed, false otherwise - * @see #sendFlowExecutionRedirect(FlowExecutionRequestInfo) - * @see #sendFlowDefinitionRedirect(FlowDefinitionRequestInfo) - * @see #sendExternalRedirect(String) + * Has the response been committed? + * @return true if yes, false otherwise */ public boolean isResponseCommitted(); - /** - * Returns true if the current request is an Ajax request, determined by the value of the http accept header. - * @return true if the current request is an Ajax request - */ - public boolean isAjaxRequest(); - - public void setResponseHeader(String name, String value); - } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/FlowDefinitionRequestInfo.java b/spring-webflow/src/main/java/org/springframework/webflow/context/FlowDefinitionRequestInfo.java deleted file mode 100644 index 717a17f2..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/FlowDefinitionRequestInfo.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.springframework.webflow.context; - -import org.springframework.util.Assert; -import org.springframework.webflow.core.collection.ParameterMap; - -/** - * A request to launch a new execution of a flow definition. - * - * @author Keith Donald - */ -public class FlowDefinitionRequestInfo extends AbstractFlowRequestInfo { - - /** - * Creates a new flow definition request. - * @param flowDefinitionId the flow definition id (required) - * @param requestPath the request path (optional) - * @param requestParameters the request parameters (optional) - * @param fragment the fragment (optional) - */ - public FlowDefinitionRequestInfo(String flowDefinitionId, RequestPath requestPath, ParameterMap requestParameters, - String fragment) { - super(flowDefinitionId, requestPath, requestParameters, fragment); - Assert.hasText(flowDefinitionId, "The id of the flow definition to redirect to is required"); - } -} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/FlowExecutionRequestInfo.java b/spring-webflow/src/main/java/org/springframework/webflow/context/FlowExecutionRequestInfo.java deleted file mode 100644 index dcc5bd61..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/FlowExecutionRequestInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.springframework.webflow.context; - -import org.springframework.util.Assert; -import org.springframework.webflow.core.collection.ParameterMap; - -/** - * A request to resume an existing flow execution that is currently paused. - * - * @author Keith Donald - */ -public class FlowExecutionRequestInfo extends AbstractFlowRequestInfo { - - /** - * The key of the flow execution to resume. - */ - private String flowExecutionKey; - - /** - * Creates a new request to resume a flow execution. - * @param flowDefinitionId the definition identifier of the execution (required) - * @param flowExecutionKey the key of the flow execution (required) - */ - public FlowExecutionRequestInfo(String flowDefinitionId, String flowExecutionKey) { - this(flowDefinitionId, flowExecutionKey, null, null, null); - } - - /** - * Creates a new request to resume a flow execution. - * @param flowDefinitionId the definition identifier of the execution (required) - * @param flowExecutionKey the key of the flow execution (required) - * @param requestPath the request path (optional) - * @param requestParameters the request parameters (optional) - * @param fragment the fragment (optional) - */ - public FlowExecutionRequestInfo(String flowDefinitionId, String flowExecutionKey, RequestPath requestPath, - ParameterMap requestParameters, String fragment) { - super(flowDefinitionId, requestPath, requestParameters, fragment); - Assert.hasText(flowDefinitionId, - "The definition identifier of the flow execution to redirect to cannot be null"); - Assert.hasText(flowExecutionKey, "The flow execution key to redirect to cannot be null"); - this.flowExecutionKey = flowExecutionKey; - } - - /** - * Returns the unique key of the flow execution to resume. - * @return the flow execution key - */ - public String getFlowExecutionKey() { - return flowExecutionKey; - } -} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/RequestPath.java b/spring-webflow/src/main/java/org/springframework/webflow/context/RequestPath.java deleted file mode 100644 index 32baec76..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/RequestPath.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.springframework.webflow.context; - -import java.util.Arrays; - -import org.springframework.util.Assert; - -/** - * A representation of a flow request path. In a flow definition request URI,the request path is the portion after the - * flow definition identifier. For example, in a URI of http://host/booking-flow/1 the request path is "/1". - * - * @author Keith Donald - */ -public class RequestPath { - - private String[] elements; - - public RequestPath(String path) { - Assert.notNull(path, "The path string cannot be null"); - Assert.isTrue(path.startsWith("/"), "The path must start with a '/'"); - this.elements = path.substring(1).split("/"); - } - - private RequestPath(String[] elements) { - this.elements = elements; - } - - public RequestPath pop(int elementCount) { - if (elementCount < elements.length) { - String[] newElements = new String[elements.length - elementCount]; - System.arraycopy(elements, elementCount, newElements, 0, newElements.length); - return new RequestPath(newElements); - } else { - return null; - } - } - - public RequestPath pop() { - return pop(1); - } - - public int getElementCount() { - return elements.length; - } - - public String getFirstElement() { - return getElement(0); - } - - /** - * Get the elements of the request path. - * @return parsed request path elements - */ - public String[] getElements() { - return elements; - } - - /** - * Gets the path element at the specified index. - * @param index the element index - * @return the element - * @throws IndexOutOfBoundsException if the index is out of bounds - */ - public String getElement(int index) throws IndexOutOfBoundsException { - return elements[index]; - } - - public boolean equals(Object o) { - if (!(o instanceof RequestPath)) { - return false; - } - RequestPath other = (RequestPath) o; - return Arrays.equals(this.elements, other.elements); - } - - public int hashCode() { - return Arrays.hashCode(elements); - } - - public static RequestPath valueOf(String[] elements) { - return new RequestPath(elements); - } - - public String toString() { - StringBuffer buffer = new StringBuffer(); - buffer.append("/"); - for (int i = 0; i < elements.length; i++) { - buffer.append(elements[i]); - if (i < (elements.length - 1)) { - buffer.append("/"); - } - } - return buffer.toString(); - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpServletRequestParameterMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpServletRequestParameterMap.java index 574c3e41..6845333b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpServletRequestParameterMap.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpServletRequestParameterMap.java @@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.binding.collection.CompositeIterator; import org.springframework.binding.collection.StringKeyedMapAdapter; +import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.webflow.core.collection.CollectionUtils; @@ -41,6 +42,7 @@ public class HttpServletRequestParameterMap extends StringKeyedMapAdapter { * Create a new map wrapping the parameters of given request. */ public HttpServletRequestParameterMap(HttpServletRequest request) { + Assert.notNull(request, "The HTTP servlet request is required"); this.request = request; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java index 3f440996..a41df447 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java @@ -16,34 +16,21 @@ package org.springframework.webflow.context.servlet; import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Iterator; -import java.util.Map; +import java.io.Writer; import javax.servlet.ServletContext; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.core.style.ToStringCreator; import org.springframework.util.StringUtils; -import org.springframework.webflow.context.AbstractFlowRequestInfo; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.ExternalContextHolder; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.FlowExecutionRequestInfo; -import org.springframework.webflow.context.RequestPath; -import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.LocalParameterMap; import org.springframework.webflow.core.collection.LocalSharedAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.core.collection.ParameterMap; import org.springframework.webflow.core.collection.SharedAttributeMap; -import org.springframework.webflow.executor.FlowExecutor; /** * Provides contextual information about an HTTP Servlet environment that has interacted with Spring Web Flow. @@ -54,18 +41,12 @@ import org.springframework.webflow.executor.FlowExecutor; */ public class ServletExternalContext implements ExternalContext { - /** The default encoding scheme: UTF-8 */ - private static final String DEFAULT_ENCODING_SCHEME = "UTF-8"; - /** The accept header value that signifies an Ajax request */ private static final String AJAX_ACCEPT_CONTENT_TYPE = "text/html;type=ajax"; - /** Alternate request paramater to indicate an ajax request for cases when control of the header is not available */ + /** Alternate request parameter to indicate an Ajax request for cases when control of the header is not available */ private static final String AJAX_SOURCE_PARAM = "ajaxSource"; - /** The response header to be set on an Ajax redirect */ - private static final String FLOW_REDIRECT_URL_HEADER = "Flow-Redirect-URL"; - /** * The context. */ @@ -101,83 +82,59 @@ public class ServletExternalContext implements ExternalContext { */ private SharedAttributeMap applicationMap; - private FlowRequestInfo flowRequestInfo; + /** + * A flag indicating if a flow execution redirect has been requested. + */ + private boolean flowExecutionRedirectRequested; - private String encodingScheme = DEFAULT_ENCODING_SCHEME; + /** + * A string specifying the id of the flow to redirect to after request processing. If null, no flow definition + * redirect has been requested. + */ + private String flowDefinitionRedirectFlowId; - private FlowExecutionRedirector flowExecutionRedirector; + /** + * Input to pass the flow definition upon redirecting. May be null. Never set unless + * {@link #flowDefinitionRedirectFlowId} has been set. + */ + private AttributeMap flowDefinitionRedirectFlowInput; - private FlowDefinitionRedirector flowDefinitionRedirector; + /** + * A string specifying an arbitrary + */ + private String externalRedirectUrl; - private String resourceUri; - - private short result; - - private String processedFlowExecutionKey; - - private FlowException exception; + /** + * The strategy for generating flow execution urls. + */ + private FlowUrlHandler flowUrlHandler; /** * Create a new external context wrapping given servlet HTTP request and response and given servlet context. * @param context the servlet context - * @param request the servlet request - * @param response the servlet response + * @param request the http servlet request + * @param response the http servlet response */ - public ServletExternalContext(ServletContext context, ServletRequest request, ServletResponse response) { - this.context = context; - try { - this.request = (HttpServletRequest) request; - this.response = (HttpServletResponse) response; - } catch (ClassCastException e) { - throw new IllegalArgumentException("Request and response objects must be HTTP objects", e); - } - this.requestParameterMap = new LocalParameterMap(new HttpServletRequestParameterMap(this.request)); - this.requestMap = new LocalAttributeMap(new HttpServletRequestMap(this.request)); - this.sessionMap = new LocalSharedAttributeMap(new HttpSessionMap(this.request)); - this.applicationMap = new LocalSharedAttributeMap(new HttpServletContextMap(context)); - this.flowRequestInfo = parseFlowRequestInfo(this.request); + public ServletExternalContext(ServletContext context, HttpServletRequest request, HttpServletResponse response) { + init(context, request, response, new DefaultFlowUrlHandler()); } - public static class FlowRequestInfo { - private String flowId; - - private String flowExecutionKey; - - private RequestPath requestPath; - - public FlowRequestInfo(String flowId, String flowExecutionKey, RequestPath requestPath) { - this.flowId = flowId; - this.flowExecutionKey = flowExecutionKey; - this.requestPath = requestPath; - } - - public String getFlowId() { - return flowId; - } - - public String getFlowExecutionKey() { - return flowExecutionKey; - } - - public RequestPath getRequestPath() { - return requestPath; - } + /** + * Create a new external context wrapping given servlet HTTP request and response and given servlet context. + * @param context the servlet context + * @param request the http servlet request + * @param response the http servlet response + * @param flowUrlHandler the flow url handler + */ + public ServletExternalContext(ServletContext context, HttpServletRequest request, HttpServletResponse response, + FlowUrlHandler flowUrlHandler) { + init(context, request, response, flowUrlHandler); } - public String getFlowId() { - return flowRequestInfo.getFlowId(); - } + // implementing external context - public String getFlowExecutionKey() { - return flowRequestInfo.getFlowExecutionKey(); - } - - public RequestPath getRequestPath() { - return flowRequestInfo.getRequestPath(); - } - - public String getRequestMethod() { - return request.getMethod(); + public String getContextPath() { + return request.getContextPath(); } public ParameterMap getRequestParameterMap() { @@ -200,283 +157,18 @@ public class ServletExternalContext implements ExternalContext { return applicationMap; } - public Object getContext() { + public Object getNativeContext() { return context; } - public Object getRequest() { + public Object getNativeRequest() { return request; } - public Object getResponse() { + public Object getNativeResponse() { return response; } - public boolean isResponseCommitted() { - return flowExecutionRedirector != null || flowDefinitionRedirector != null || resourceUri != null; - } - - public PrintWriter getResponseWriter() { - try { - return response.getWriter(); - } catch (IOException e) { - // TODO - handle how? - throw new RuntimeException(e); - } - } - - public String encode(String string) { - try { - return URLEncoder.encode(string, encodingScheme); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Unsupported encoding errors should never happen", e); - } - } - - public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo requestInfo) { - return getFlowExecutorPath() + "/" + requestInfo.getFlowDefinitionId() + requestPath(requestInfo) - + requestParameters(requestInfo) + fragment(requestInfo); - } - - public String buildFlowExecutionUrl(FlowExecutionRequestInfo requestInfo, boolean contextRelative) { - String contextRelativeUrl = getFlowExecutionsPath() + requestInfo.getFlowDefinitionId() + "/" - + requestInfo.getFlowExecutionKey() + requestPath(requestInfo) + requestParameters(requestInfo) - + fragment(requestInfo); - if (contextRelative) { - return contextRelativeUrl; - } else { - return request.getScheme() + request.getServerName() + contextRelativeUrl; - } - } - - public void sendFlowExecutionRedirect(FlowExecutionRequestInfo request) { - flowExecutionRedirector = new FlowExecutionRedirector(request); - } - - public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo request) { - flowDefinitionRedirector = new FlowDefinitionRedirector(request); - } - - public void sendExternalRedirect(String resourceUri) { - this.resourceUri = resourceUri; - } - - // execution processing result setters - - public void setPausedResult(String flowExecutionKey) { - result = 0; - processedFlowExecutionKey = flowExecutionKey; - } - - public void setEndedResult(String flowExecutionKey) { - result = 1; - processedFlowExecutionKey = flowExecutionKey; - } - - public void setExceptionResult(FlowException e) { - result = 2; - exception = e; - processedFlowExecutionKey = flowRequestInfo.getFlowExecutionKey(); - } - - /** - * Execute the flow request lifecycle, including provision of the final response. - * @param flowExecutor the flow executor for calling into the Spring Web Flow system - * @throws IOException an IOException occurred issuing the response - */ - public void executeFlowRequest(FlowExecutor flowExecutor) throws IOException { - ExternalContextHolder.setExternalContext(this); - try { - flowExecutor.executeFlowRequest(this); - if (isPausedResult()) { - if (flowExecutionRedirector != null) { - flowExecutionRedirector.issueRedirect(); - } else if (flowDefinitionRedirector != null) { - flowDefinitionRedirector.issueRedirect(); - } else if (resourceUri != null) { - sendRedirect(resourceUri); - } else { - // commit response? - } - } else if (isEndResult()) { - if (flowExecutionRedirector != null) { - if (flowExecutionRedirector.redirectsTo(processedFlowExecutionKey)) { - throw new IllegalStateException( - "You cannot send a flow execution redirect when this execution has ended - programmer error"); - } else { - flowExecutionRedirector.issueRedirect(); - } - } else if (flowDefinitionRedirector != null) { - flowDefinitionRedirector.issueRedirect(); - } else if (resourceUri != null) { - sendRedirect(resourceUri); - } else { - // commit response? - } - } else if (isExceptionResult()) { - // response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, exception.getMessage()); - throw exception; - } - } finally { - ExternalContextHolder.setExternalContext(null); - } - } - - // hooks subclasses may override - - protected FlowRequestInfo parseFlowRequestInfo(HttpServletRequest request) { - String pathInfo = request.getPathInfo(); - if (pathInfo == null) { - throw new IllegalArgumentException( - "The requestPathInfo is null: unable to extract flow definition id or flow execution key"); - } - String flowId; - String flowExecutionKey; - RequestPath path = new RequestPath(pathInfo); - if (path.getElement(0).equals("executions")) { - flowId = path.getElement(1); - flowExecutionKey = path.getElement(2); - if (path.getElementCount() > 3) { - path = path.pop(3); - } else { - path = null; - } - } else { - flowId = path.getElement(0); - flowExecutionKey = null; - if (path.getElementCount() > 1) { - path = path.pop(1); - } else { - path = null; - } - } - return new FlowRequestInfo(flowId, flowExecutionKey, path); - } - - // private helpers - - private String getFlowExecutorPath() { - return request.getContextPath() + request.getServletPath(); - } - - private String getFlowExecutionsPath() { - return getFlowExecutorPath() + "/executions/"; - } - - private boolean isPausedResult() { - return result == 0; - } - - private boolean isEndResult() { - return result == 1; - } - - private boolean isExceptionResult() { - return result == 2; - } - - private String requestPath(AbstractFlowRequestInfo requestInfo) { - if (requestInfo.getRequestPath() == null) { - return ""; - } - StringBuffer buffer = new StringBuffer(); - String[] requestElements = requestInfo.getRequestPath().getElements(); - for (int i = 0; i < requestElements.length; i++) { - buffer.append('/').append(encode(requestElements[i], encodingScheme)); - } - return buffer.toString(); - } - - private String requestParameters(AbstractFlowRequestInfo requestInfo) { - if (requestInfo.getRequestParameters() == null) { - return ""; - } - StringBuffer queryString = new StringBuffer(); - queryString.append('?'); - ParameterMap requestParameters = requestInfo.getRequestParameters(); - Iterator it = requestParameters.asMap().entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - String parameterName = encode((String) entry.getKey(), encodingScheme); - String parameterValue = encode((String) entry.getValue(), encodingScheme); - queryString.append(parameterName).append('=').append(parameterValue); - if (it.hasNext()) { - queryString.append('&'); - } - } - return queryString.toString(); - } - - private String fragment(AbstractFlowRequestInfo requestInfo) { - if (requestInfo.getFragment() == null || requestInfo.getFragment().length() == 0) { - return ""; - } - return "#" + requestInfo.getFragment(); - } - - private String encode(String value, String encodingScheme) { - try { - return URLEncoder.encode(value, encodingScheme); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - private void sendRedirect(String targetUrl) throws IOException { - if (isAjaxRequest()) { - setResponseHeader(FLOW_REDIRECT_URL_HEADER, response.encodeRedirectURL(targetUrl)); - } else { - response.sendRedirect(response.encodeRedirectURL(targetUrl)); - } - } - - public String toString() { - return new ToStringCreator(this).append("requestParameterMap", getRequestParameterMap()).toString(); - } - - private abstract class FlowRedirector { - private AbstractFlowRequestInfo requestInfo; - - public FlowRedirector(AbstractFlowRequestInfo requestInfo) { - this.requestInfo = requestInfo; - } - - protected AbstractFlowRequestInfo getRequestInfo() { - return requestInfo; - } - - public abstract void issueRedirect() throws IOException; - } - - private class FlowExecutionRedirector extends FlowRedirector { - public FlowExecutionRedirector(FlowExecutionRequestInfo requestInfo) { - super(requestInfo); - } - - public boolean redirectsTo(String flowExecutionKey) { - FlowExecutionRequestInfo requestInfo = (FlowExecutionRequestInfo) getRequestInfo(); - return requestInfo.getFlowExecutionKey().equals(flowExecutionKey); - } - - public void issueRedirect() throws IOException { - FlowExecutionRequestInfo requestInfo = (FlowExecutionRequestInfo) getRequestInfo(); - String targetUrl = buildFlowExecutionUrl(requestInfo, true); - sendRedirect(targetUrl); - } - } - - private class FlowDefinitionRedirector extends FlowRedirector { - public FlowDefinitionRedirector(FlowDefinitionRequestInfo redirect) { - super(redirect); - } - - public void issueRedirect() throws IOException { - FlowDefinitionRequestInfo requestInfo = (FlowDefinitionRequestInfo) getRequestInfo(); - String targetUrl = buildFlowDefinitionUrl(requestInfo); - sendRedirect(targetUrl); - } - } - public boolean isAjaxRequest() { String acceptHeader = request.getHeader("Accept"); String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM); @@ -487,7 +179,75 @@ public class ServletExternalContext implements ExternalContext { } } + public String getFlowExecutionUri(String flowId, String flowExecutionKey) { + return flowUrlHandler.createFlowExecutionUrl(flowId, flowExecutionKey, request); + } + + public Writer getResponseWriter() { + try { + return response.getWriter(); + } catch (IOException e) { + throw new IllegalStateException("Unable to obtain response writer", e); + } + } + public void setResponseHeader(String name, String value) { response.setHeader(name, value); } + + public boolean isResponseCommitted() { + return flowExecutionRedirectRequested() || flowDefinitionRedirectRequested() || externalRedirectRequested(); + } + + public void requestFlowExecutionRedirect() { + flowExecutionRedirectRequested = true; + } + + public void requestExternalRedirect(String uri) { + externalRedirectUrl = uri; + } + + public void requestFlowDefinitionRedirect(String flowId, AttributeMap input) { + flowDefinitionRedirectFlowId = flowId; + flowDefinitionRedirectFlowInput = input; + } + + // implementation specific methods + + public boolean flowExecutionRedirectRequested() { + return flowExecutionRedirectRequested; + } + + public boolean flowDefinitionRedirectRequested() { + return flowDefinitionRedirectFlowId != null; + } + + public String getFlowRedirectFlowId() { + return flowDefinitionRedirectFlowId; + } + + public AttributeMap getFlowRedirectFlowInput() { + return flowDefinitionRedirectFlowInput; + } + + public boolean externalRedirectRequested() { + return externalRedirectUrl != null; + } + + public String getExternalRedirectUrl() { + return externalRedirectUrl; + } + + // private helpers + + private void init(ServletContext context, HttpServletRequest request, HttpServletResponse response, + FlowUrlHandler flowUrlHandler) { + this.context = context; + this.requestParameterMap = new LocalParameterMap(new HttpServletRequestParameterMap(request)); + this.requestMap = new LocalAttributeMap(new HttpServletRequestMap(request)); + this.sessionMap = new LocalSharedAttributeMap(new HttpSessionMap(request)); + this.applicationMap = new LocalSharedAttributeMap(new HttpServletContextMap(context)); + this.flowUrlHandler = flowUrlHandler; + } + } \ No newline at end of file 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 d379b28d..25836e1c 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 @@ -140,6 +140,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { */ private Serializable messagesMemento; + private transient Event outcome; + /** * Default constructor required for externalizable serialization. Should NOT be called programmatically. */ @@ -186,6 +188,14 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { return !flowSessions.isEmpty(); } + public boolean hasEnded() { + return hasStarted() && !isActive(); + } + + public Event getOutcome() { + return outcome; + } + public FlowSession getActiveSession() { if (!isActive()) { if (started) { @@ -211,7 +221,8 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { // methods implementing FlowExecution - public void start(ExternalContext externalContext) throws FlowExecutionException, IllegalStateException { + public void start(MutableAttributeMap input, ExternalContext externalContext) throws FlowExecutionException, + IllegalStateException { Assert.state(!started, "This flow has already been started; you cannot call 'start()' more than once"); if (logger.isDebugEnabled()) { logger.debug("Starting execution in " + externalContext); @@ -221,13 +232,13 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { RequestContextHolder.setRequestContext(context); listeners.fireRequestSubmitted(context); try { - start(flow, flow.createExecutionInputMap(externalContext), context); + start(flow, input, context); } catch (FlowExecutionException e) { handleException(e, context); } catch (Exception e) { handleException(wrap(e), context); } finally { - if (isActive()) { + if (!hasEnded()) { saveMessages(context); try { listeners.firePaused(context); @@ -266,7 +277,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { } catch (Exception e) { handleException(wrap(e), context); } finally { - if (isActive()) { + if (!hasEnded()) { saveMessages(context); try { listeners.firePaused(context); @@ -348,6 +359,9 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable { session.getFlow().end(context, output); flowSessions.removeLast(); listeners.fireSessionEnded(context, session, output); + if (hasEnded()) { + this.outcome = new Event(this, session.getState().getId(), output); + } return session; } 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 bd5953e4..82dc55d9 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 @@ -18,7 +18,6 @@ package org.springframework.webflow.engine.impl; import org.springframework.binding.message.MessageContext; import org.springframework.core.style.ToStringCreator; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.FlowExecutionRequestInfo; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.CollectionUtils; import org.springframework.webflow.core.collection.LocalAttributeMap; @@ -164,25 +163,15 @@ class RequestControlContextImpl implements RequestControlContext { // implementing RequestControlContext public String getFlowExecutionUrl() { - if (flowExecution.getKey() == null) { - throw new IllegalStateException( - "Flow execution key not yet assigned; unable to generate flow execution URL at this time"); + String key = flowExecution.getKey() != null ? flowExecution.getKey().toString() : null; + if (key != null) { + return externalContext.getFlowExecutionUri(flowExecution.getDefinition().getId(), key); } else { - FlowExecutionRequestInfo requestInfo = new FlowExecutionRequestInfo(flowExecution.getFlowId(), - flowExecution.getKey().toString()); - return externalContext.buildFlowExecutionUrl(requestInfo, true); + return null; } } public void sendFlowExecutionRedirect() { - if (flowExecution.getKey() == null) { - throw new IllegalStateException( - "Flow execution key not yet assigned; unable to send a flow execution redirect request"); - } else { - FlowExecutionRequestInfo requestInfo = new FlowExecutionRequestInfo(flowExecution.getFlowId(), - flowExecution.getKey().toString()); - externalContext.sendFlowExecutionRedirect(requestInfo); - } } public void setCurrentState(State state) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecution.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecution.java index 0a578e9e..a9083b21 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecution.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecution.java @@ -16,23 +16,10 @@ package org.springframework.webflow.execution; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.definition.FlowDefinition; -import org.springframework.webflow.execution.repository.FlowExecutionRepository; +import org.springframework.webflow.core.collection.MutableAttributeMap; /** - * An execution of a flow definition. This is the central interface for manipulating a runtime execution of a flow - * definition. - * - * A FlowExecution instance is typically created by a {@link FlowExecutionFactory factory}. A FlowExecution instance is - * persisted using a {@link FlowExecutionRepository repository}. A FlowExecution's lifecycle is observed by zero or - * more {@link FlowExecutionListener listeners} - * - * @see FlowDefinition - * @see FlowSession - * @see RequestContext - * @see FlowExecutionListener - * @see org.springframework.webflow.execution.repository.FlowExecutionRepository - * @see org.springframework.webflow.executor.FlowExecutor + * An execution of a flow definition. This is the central interface for manipulating a instance of a flow definition. * * @author Keith Donald * @author Erwin Vervaet @@ -44,11 +31,12 @@ public interface FlowExecution extends FlowExecutionContext { *

* When this method returns, execution status is either "paused" or "ended". If ended, the flow execution cannot be * used again. If "paused", the flow execution may be {@link #resume(ExternalContext)}. + * @param input flow execution input * @param context the external context representing the calling environment * @throws FlowExecutionException if an exception was thrown within a state of the flow execution during request * processing */ - public void start(ExternalContext context) throws FlowExecutionException; + public void start(MutableAttributeMap input, ExternalContext context) throws FlowExecutionException; /** * Resume this flow execution. May be called when the flow execution is paused. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java index db183d6c..7b1a4236 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionContext.java @@ -74,6 +74,21 @@ public interface FlowExecutionContext { */ public boolean isActive(); + /** + * Returns a flag indicating if this execution has ended. A flow execution that has ended has been started but is no + * longer active. + * @see #hasStarted() + * @see #isActive() + * @return true if ended, false if not started or still active + */ + public boolean hasEnded(); + + /** + * Returns the ending outcome event of this execution, or null if this execution has not yet ended. + * @return the outcome event + */ + public Event getOutcome(); + /** * Returns the active flow session of this flow execution. The active flow session is the currently executing * session -- it may be the "root flow" session, or it may be a subflow session if this flow execution has spawned a diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java index f798bf99..99d5b163 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/RequestContext.java @@ -123,10 +123,9 @@ public interface RequestContext { * constructs within that environment. *

* In addition, this context may be downcastable to a specific context type for a specific client environment, such - * as a {@link org.springframework.webflow.context.servlet.ServletExternalContext} for servlets or a - * {@link org.springframework.webflow.context.portlet.PortletExternalContext} for portlets. Such downcasting will - * give you full access to a native HttpServletRequest, for example. With that said, for portability reasons you - * should avoid coupling your flow artifacts to a specific deployment environment when possible. + * as Servlets or Portlets. Such downcasting will give you full access to a native HttpServletRequest, for example. + * With that said, for portability reasons you should avoid coupling your flow artifacts to a specific deployment + * environment when possible. * @return the originating external context, the one that triggered the current execution request */ public ExternalContext getExternalContext(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutor.java b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutor.java index 2a18cf14..a702fd0e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutor.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/executor/FlowExecutor.java @@ -16,6 +16,8 @@ package org.springframework.webflow.executor; import org.springframework.webflow.context.ExternalContext; +import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.MutableAttributeMap; /** * The central facade and entry-point service interface into the Spring Web Flow system for driving the executions of @@ -23,15 +25,27 @@ import org.springframework.webflow.context.ExternalContext; * clients. *

* Implementations of this interface abstract away much of the internal complexity of the web flow execution subsystem, - * which consists of launching and resuming managed flow executions from repositories. + * which consists of launching and resuming managed flow executions. * * @author Keith Donald */ public interface FlowExecutor { /** - * Execute the flow request initiated by the provided external context. - * @param context the external context, representing a client environment calling into Spring Web Flow + * Launch a new execution of the flow with the provided id. + * @param flowId the flow definition identifier; should be unique among all top-level flow definitions (required). + * @param input input to pass to the new execution on startup (optional) + * @param context access to the calling environment (required) */ - public void executeFlowRequest(ExternalContext context); + public FlowExecutionResult launchExecution(String flowId, MutableAttributeMap input, ExternalContext context) + throws FlowException; + + /** + * Resume the flow execution with the provided execution key. + * @param flowId the id of the flow definition + * @param flowExecutionKey the key of a paused execution of the flow definition + * @param context access to the calling environment + */ + public FlowExecutionResult resumeExecution(String flowExecutionKey, ExternalContext context) throws FlowException; + } \ 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 121021f8..5f1c97a4 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 @@ -18,6 +18,7 @@ package org.springframework.webflow.executor; import org.springframework.util.Assert; import org.springframework.webflow.context.ExternalContext; import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.definition.registry.FlowDefinitionLocator; import org.springframework.webflow.execution.FlowExecution; @@ -64,8 +65,8 @@ import org.springframework.webflow.execution.repository.FlowExecutionRepository; * @see FlowExecutionFactory * @see FlowExecutionRepository * - * @author Erwin Vervaet * @author Keith Donald + * @author Erwin Vervaet * @author Colin Sampaleanu */ public class FlowExecutorImpl implements FlowExecutor { @@ -101,59 +102,38 @@ public class FlowExecutorImpl implements FlowExecutor { this.executionRepository = executionRepository; } - public void executeFlowRequest(ExternalContext context) { - if (context.getFlowExecutionKey() != null) { - resumeExecution(context.getFlowExecutionKey(), context); + public FlowExecutionResult launchExecution(String flowId, MutableAttributeMap input, ExternalContext context) + throws FlowException { + FlowDefinition flowDefinition = definitionLocator.getFlowDefinition(flowId); + FlowExecution flowExecution = executionFactory.createFlowExecution(flowDefinition); + flowExecution.start(input, context); + if (!flowExecution.hasEnded()) { + executionRepository.putFlowExecution(flowExecution); + return FlowExecutionResult.createPausedResult(flowExecution.getDefinition().getId(), flowExecution.getKey() + .toString()); } else { - launchExecution(context.getFlowId(), context); + return FlowExecutionResult.createEndedResult(flowExecution.getDefinition().getId(), flowExecution + .getOutcome()); } } - private void launchExecution(String flowId, ExternalContext context) { + public FlowExecutionResult resumeExecution(String flowExecutionKey, ExternalContext context) throws FlowException { + FlowExecutionKey key = executionRepository.parseFlowExecutionKey(flowExecutionKey); + FlowExecutionLock lock = executionRepository.getLock(key); try { - FlowDefinition flowDefinition = definitionLocator.getFlowDefinition(flowId); - FlowExecution flowExecution = executionFactory.createFlowExecution(flowDefinition); - flowExecution.start(context); - if (flowExecution.isActive()) { + FlowExecution flowExecution = executionRepository.getFlowExecution(key); + flowExecution.resume(context); + if (!flowExecution.hasEnded()) { executionRepository.putFlowExecution(flowExecution); - context.setPausedResult(flowExecution.getKey().toString()); + return FlowExecutionResult.createPausedResult(flowExecution.getDefinition().getId(), flowExecution + .getKey().toString()); } else { - context.setEndedResult(null); - } - } catch (FlowException e) { - if (!handleException(e, context)) { - context.setExceptionResult(e); + executionRepository.removeFlowExecution(flowExecution); + return FlowExecutionResult.createEndedResult(flowExecution.getDefinition().getId(), flowExecution + .getOutcome()); } + } finally { + lock.unlock(); } } - - private void resumeExecution(String encodedKey, ExternalContext context) { - try { - FlowExecutionKey key = executionRepository.parseFlowExecutionKey(encodedKey); - FlowExecutionLock lock = executionRepository.getLock(key); - lock.lock(); - try { - FlowExecution flowExecution = executionRepository.getFlowExecution(key); - flowExecution.resume(context); - if (flowExecution.isActive()) { - executionRepository.putFlowExecution(flowExecution); - context.setPausedResult(flowExecution.getKey().toString()); - } else { - executionRepository.removeFlowExecution(flowExecution); - context.setEndedResult(flowExecution.getKey().toString()); - } - } finally { - lock.unlock(); - } - } catch (FlowException e) { - if (!handleException(e, context)) { - context.setExceptionResult(e); - } - } - } - - private boolean handleException(FlowException e, ExternalContext context) { - // TODO - return false; - } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowController.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowController.java index e84e9e3c..0c095bb3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowController.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowController.java @@ -1,11 +1,23 @@ package org.springframework.webflow.mvc; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; +import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler; +import org.springframework.webflow.context.servlet.FlowUrlHandler; import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.FlowException; +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.execution.repository.NoSuchFlowExecutionException; +import org.springframework.webflow.executor.FlowExecutionResult; import org.springframework.webflow.executor.FlowExecutor; /** @@ -16,24 +28,156 @@ public class FlowController extends AbstractController { private FlowExecutor flowExecutor; - /** - * Creates a new flow controller. - */ - public FlowController() { - } + private FlowUrlHandler urlHandler; + + private Map flowHandlers = new HashMap(); /** - * Sets the flow executor to delegate to. The FlowExecutor is the entry point into Spring Web Flow. - * @param flowExecutor the flow executor + * @param flowExecutor the web flow executor service */ - public void setFlowExecutor(FlowExecutor flowExecutor) { + public FlowController(FlowExecutor flowExecutor) { this.flowExecutor = flowExecutor; + this.urlHandler = new DefaultFlowUrlHandler(); + // set the cache seconds property to 0 so no pages are cached by default for flows + setCacheSeconds(0); + } + + public FlowUrlHandler getFlowUrlHandler() { + return urlHandler; + } + + public void setFlowRequestUrlHandler(FlowUrlHandler urlHandler) { + this.urlHandler = urlHandler; + } + + public void registerFlowHandler(FlowHandler handler) { + flowHandlers.put(handler.getFlowId(), handler); } protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { - ServletExternalContext context = new ServletExternalContext(getServletContext(), request, response); - context.executeFlowRequest(flowExecutor); + String flowExecutionKey = urlHandler.getFlowExecutionKey(request); + if (flowExecutionKey != null) { + try { + ServletExternalContext context = createServletExternalContext(request, response); + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + return handleFlowExecutionResult(result, context, request, response); + } catch (FlowException e) { + return handleFlowException(e, request, response); + } + } else { + try { + String flowId = urlHandler.getFlowId(request); + MutableAttributeMap input = getFlowInput(flowId, request); + ServletExternalContext context = createServletExternalContext(request, response); + FlowExecutionResult result = flowExecutor.launchExecution(flowId, input, context); + return handleFlowExecutionResult(result, context, request, response); + } catch (FlowException e) { + return handleFlowException(e, request, response); + } + } + } + + // subclassing hooks + + protected ServletExternalContext createServletExternalContext(HttpServletRequest request, + HttpServletResponse response) { + return new ServletExternalContext(getServletContext(), request, response, urlHandler); + } + + protected MutableAttributeMap defaultFlowExecutionInputMap(HttpServletRequest request) { + return new LocalAttributeMap(request.getParameterMap()); + } + + protected ModelAndView defaultHandleFlowOutcome(String flowId, String outcome, AttributeMap endedOutput, + HttpServletRequest request, HttpServletResponse response) throws IOException { + // by default, just start the flow over passing the output as input + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, endedOutput, request)); return null; } + + protected ModelAndView defaultHandleFlowException(String flowId, FlowException e, HttpServletRequest request, + HttpServletResponse response) throws IOException { + if (e instanceof NoSuchFlowExecutionException && flowId != null) { + // by default, attempt to restart the flow + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, null, request)); + return null; + } else { + throw e; + } + } + + // internal helpers + + private ModelAndView handleFlowExecutionResult(FlowExecutionResult result, ServletExternalContext context, + HttpServletRequest request, HttpServletResponse response) throws IOException { + if (result.paused()) { + if (context.flowExecutionRedirectRequested()) { + String url = urlHandler.createFlowExecutionUrl(result.getFlowId(), result.getPausedKey(), request); + response.sendRedirect(url); + return null; + } else if (context.externalRedirectRequested()) { + response.sendRedirect(context.getExternalRedirectUrl()); + return null; + } else { + // nothing to do: flow has handled the response + return null; + } + } else if (result.ended()) { + if (context.flowDefinitionRedirectRequested()) { + String flowId = context.getFlowRedirectFlowId(); + AttributeMap input = context.getFlowRedirectFlowInput(); + response.sendRedirect(urlHandler.createFlowDefinitionUrl(flowId, input, request)); + return null; + } else if (context.externalRedirectRequested()) { + response.sendRedirect(context.getExternalRedirectUrl()); + return null; + } else { + return handleFlowOutcome(result.getFlowId(), result.getEndedOutcome(), result.getEndedOutput(), + request, response); + } + } else { + throw new IllegalStateException("Execution result should have been one of [paused] or [ended]"); + } + } + + private MutableAttributeMap getFlowInput(String flowId, HttpServletRequest request) { + FlowHandler handler = getFlowHandler(flowId); + if (handler != null) { + return handler.createExecutionInputMap(request); + } else { + return defaultFlowExecutionInputMap(request); + } + } + + private ModelAndView handleFlowOutcome(String flowId, String outcome, AttributeMap endedOutput, + HttpServletRequest request, HttpServletResponse response) throws IOException { + FlowHandler handler = getFlowHandler(flowId); + if (handler != null) { + ModelAndView result = handler.handleExecutionOutcome(outcome, endedOutput, request, response); + return result != null ? result : defaultHandleFlowOutcome(flowId, outcome, endedOutput, request, response); + } else { + return defaultHandleFlowOutcome(flowId, outcome, endedOutput, request, response); + } + } + + private ModelAndView handleFlowException(FlowException e, HttpServletRequest request, HttpServletResponse response) + throws IOException { + String flowId = urlHandler.getFlowId(request); + if (flowId != null) { + FlowHandler handler = getFlowHandler(flowId); + if (handler != null) { + ModelAndView result = handler.handleException(e, request, response); + return result != null ? result : defaultHandleFlowException(flowId, e, request, response); + } else { + return defaultHandleFlowException(flowId, e, request, response); + } + } else { + return defaultHandleFlowException(null, e, request, response); + } + } + + private FlowHandler getFlowHandler(String flowId) { + return (FlowHandler) flowHandlers.get(flowId); + } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/MvcViewFactoryCreator.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/MvcViewFactoryCreator.java index 60a4a007..86f0d379 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/MvcViewFactoryCreator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/MvcViewFactoryCreator.java @@ -184,8 +184,8 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon model.put("flowExecutionRequestContext", context); model.put("flowExecutionUrl", context.getFlowExecutionUrl()); try { - view.render(model, (HttpServletRequest) context.getExternalContext().getRequest(), - (HttpServletResponse) context.getExternalContext().getResponse()); + view.render(model, (HttpServletRequest) context.getExternalContext().getNativeRequest(), + (HttpServletResponse) context.getExternalContext().getNativeResponse()); } catch (Exception e) { throw new IllegalStateException("Exception rendering view", e); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/servlet/SpringWebServlet.java b/spring-webflow/src/main/java/org/springframework/webflow/servlet/SpringWebServlet.java deleted file mode 100644 index 27702ae7..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/servlet/SpringWebServlet.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.springframework.webflow.servlet; - -import java.io.IOException; - -import javax.servlet.Servlet; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - -import org.springframework.util.StringUtils; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.XmlWebApplicationContext; -import org.springframework.webflow.context.servlet.ServletExternalContext; -import org.springframework.webflow.executor.FlowExecutor; - -public class SpringWebServlet implements Servlet { - - private ServletConfig config; - - private ConfigurableWebApplicationContext container; - - private FlowExecutor flowExecutor; - - public void init(ServletConfig config) throws ServletException { - this.config = config; - container = new XmlWebApplicationContext(); - container.setConfigLocations(getConfigLocations(config)); - container.setServletConfig(config); - container.setServletContext(config.getServletContext()); - container.refresh(); - this.flowExecutor = getFlowExecutor(container); - } - - public ServletConfig getServletConfig() { - return config; - } - - public String getServletInfo() { - return "Spring Web Servlet"; - } - - public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { - ServletContext servletContext = getServletConfig().getServletContext(); - new ServletExternalContext(servletContext, request, response).executeFlowRequest(flowExecutor); - } - - public void destroy() { - container.close(); - } - - private String[] getConfigLocations(ServletConfig config) { - String configLocations = config.getInitParameter("configLocations"); - if (configLocations != null) { - return StringUtils.tokenizeToStringArray(config.getInitParameter("configLocations"), - ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS); - } else { - return new String[] { "/WEB-INF/webapp-config.xml" }; - } - } - - private FlowExecutor getFlowExecutor(WebApplicationContext container) { - String[] beanNames = container.getBeanNamesForType(FlowExecutor.class); - if (beanNames.length == 0) { - throw new IllegalStateException("No bean of type FlowExecutor defined in context"); - } else if (beanNames.length > 1) { - throw new IllegalStateException("More than one bean of type FlowExecutor defined in context."); - } else { - return (FlowExecutor) container.getBean(beanNames[0]); - } - } -} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java index 06641fe8..c638911d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java @@ -15,16 +15,14 @@ */ package org.springframework.webflow.test; -import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; import java.util.HashMap; import java.util.Map; import org.springframework.binding.collection.SharedMapDecorator; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.FlowExecutionRequestInfo; -import org.springframework.webflow.context.RequestPath; -import org.springframework.webflow.core.FlowException; +import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.LocalSharedAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; @@ -33,20 +31,12 @@ import org.springframework.webflow.core.collection.SharedAttributeMap; /** * Mock implementation of the {@link ExternalContext} interface. - * * @see ExternalContext - * * @author Keith Donald */ public class MockExternalContext implements ExternalContext { - private String flowId; - - private String flowExecutionKey; - - private RequestPath requestPath; - - private String requestMethod; + private String contextPath; private ParameterMap requestParameterMap = new MockParameterMap(); @@ -58,26 +48,26 @@ public class MockExternalContext implements ExternalContext { private SharedAttributeMap applicationMap = new LocalSharedAttributeMap(new SharedMapDecorator(new HashMap())); - private Object context; + private Object nativeContext = new Object(); - private Object request; + private Object nativeRequest = new Object(); - private Object response; + private Object nativeResponse = new Object(); - private FlowDefinitionRequestInfo flowDefinitionRedirectResult; - - private FlowExecutionRequestInfo flowExecutionRedirectResult; - - private String externalRedirectResult; - - private String pausedFlowExecutionKeyResult; - - private FlowException exceptionResult; + private StringWriter responseWriter = new StringWriter(); private boolean ajaxRequest; private Map responseHeaders = new HashMap(); + private boolean flowExecutionRedirectRequested; + + private String flowDefinitionRedirectFlowId; + + private AttributeMap flowDefinitionRedirectFlowInput; + + private String externalRedirectUrl; + /** * Creates a mock external context with an empty request parameter map. Allows for bean style usage. */ @@ -97,20 +87,8 @@ public class MockExternalContext implements ExternalContext { // implementing external context - public String getFlowId() { - return flowId; - } - - public String getFlowExecutionKey() { - return flowExecutionKey; - } - - public String getRequestMethod() { - return requestMethod; - } - - public RequestPath getRequestPath() { - return requestPath; + public String getContextPath() { + return contextPath; } public ParameterMap getRequestParameterMap() { @@ -133,90 +111,57 @@ public class MockExternalContext implements ExternalContext { return applicationMap; } - public Object getContext() { - return context; + public Object getNativeContext() { + return nativeContext; } - public Object getRequest() { - return request; + public Object getNativeRequest() { + return nativeRequest; } - public Object getResponse() { - return response; + public Object getNativeResponse() { + return nativeResponse; } - public PrintWriter getResponseWriter() { - throw new UnsupportedOperationException("Not yet implemented"); + public boolean isAjaxRequest() { + return ajaxRequest; } - public String encode(String string) { - return string; + public String getFlowExecutionUri(String flowId, String flowExecutionKey) { + return "/" + flowId + "?execution=" + flowExecutionKey; } - public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo requestInfo) { - throw new UnsupportedOperationException("Not yet implemented"); + public Writer getResponseWriter() { + return responseWriter; } - public String buildFlowExecutionUrl(FlowExecutionRequestInfo requestInfo, boolean contextRelative) { - return "/executions/" + requestInfo.getFlowDefinitionId() + "/" + requestInfo.getFlowExecutionKey(); - } - - public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo requestInfo) { - this.flowDefinitionRedirectResult = requestInfo; - } - - public void sendFlowExecutionRedirect(FlowExecutionRequestInfo requestInfo) { - this.flowExecutionRedirectResult = requestInfo; - } - - public void sendExternalRedirect(String resourceUri) { - externalRedirectResult = resourceUri; - } - - public void setPausedResult(String flowExecutionKey) { - this.pausedFlowExecutionKeyResult = flowExecutionKey; - } - - public void setEndedResult(String flowExecutionKey) { - - } - - public void setExceptionResult(FlowException e) { - exceptionResult = e; + public void setResponseHeader(String name, String value) { + this.responseHeaders.put(name, value); } public boolean isResponseCommitted() { - return false; + return flowExecutionRedirectRequested() || flowDefinitionRedirectRequested() || externalRedirectRequested(); } - // helper setters - - public void setFlowId(String flowId) { - this.flowId = flowId; + public void requestFlowExecutionRedirect() { + flowExecutionRedirectRequested = true; } - public void setFlowExecutionKey(String flowExecutionKey) { - this.flowExecutionKey = flowExecutionKey; + public void requestExternalRedirect(String uri) { + externalRedirectUrl = uri; } - public void setRequestMethod(String requestMethod) { - this.requestMethod = requestMethod; + public void requestFlowDefinitionRedirect(String flowId, AttributeMap input) { + flowDefinitionRedirectFlowId = flowId; + flowDefinitionRedirectFlowInput = input; } - public void setRequestPath(RequestPath requestPath) { - this.requestPath = requestPath; - } - - public void setContext(Object context) { - this.context = context; - } - - public void setRequest(Object request) { - this.request = request; - } - - public void setResponse(Object response) { - this.response = response; + /** + * Set the context path of the application. + * @param contextPath the context path + */ + public void setContextPath(String contextPath) { + this.contextPath = contextPath; } /** @@ -260,6 +205,30 @@ public class MockExternalContext implements ExternalContext { this.applicationMap = applicationMap; } + /** + * Set the native context object. + * @param nativeContext the native context + */ + public void setNativeContext(Object nativeContext) { + this.nativeContext = nativeContext; + } + + /** + * Set the native request object. + * @param nativeRequest the native request object + */ + public void setNativeRequest(Object nativeRequest) { + this.nativeRequest = nativeRequest; + } + + /** + * Set the native response object. + * @param nativeResponse the native response object + */ + public void setNativeResponse(Object nativeResponse) { + this.nativeResponse = nativeResponse; + } + // convenience helpers /** @@ -288,39 +257,45 @@ public class MockExternalContext implements ExternalContext { getMockRequestParameterMap().put(parameterName, parameterValues); } - public FlowDefinitionRequestInfo getFlowDefinitionRedirectResult() { - return flowDefinitionRedirectResult; - } - - public FlowExecutionRequestInfo getFlowExecutionRedirectResult() { - return flowExecutionRedirectResult; - } - - public String getExternalRedirectResult() { - return externalRedirectResult; - } - - public String getPausedFlowExecutionKeyResult() { - return pausedFlowExecutionKeyResult; - } - - public FlowException getExceptionResult() { - return exceptionResult; - } - - public boolean isAjaxRequest() { - return ajaxRequest; - } - + /** + * Set whether this request is an ajax request. + * @param ajaxRequest true or false + */ public void setAjaxRequest(boolean ajaxRequest) { this.ajaxRequest = ajaxRequest; } - public void setResponseHeader(String name, String value) { - this.responseHeaders.put(name, value); - } - + /** + * Returns the value of the response header entry + * @param name the entry name + * @return the entry value, or null if no entry was set with this name + */ public String getResponseHeader(String name) { return (String) responseHeaders.get(name); } + + public boolean flowExecutionRedirectRequested() { + return flowExecutionRedirectRequested; + } + + public boolean flowDefinitionRedirectRequested() { + return flowDefinitionRedirectFlowId != null; + } + + public String getFlowRedirectFlowId() { + return flowDefinitionRedirectFlowId; + } + + public AttributeMap getFlowRedirectFlowInput() { + return flowDefinitionRedirectFlowInput; + } + + public boolean externalRedirectRequested() { + return externalRedirectUrl != null; + } + + public String getExternalRedirectUrl() { + return externalRedirectUrl; + } + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionContext.java index efb529eb..9d022904 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionContext.java @@ -20,6 +20,7 @@ import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecutionContext; import org.springframework.webflow.execution.FlowExecutionKey; import org.springframework.webflow.execution.FlowSession; @@ -45,6 +46,8 @@ public class MockFlowExecutionContext implements FlowExecutionContext { private MutableAttributeMap attributes = new LocalAttributeMap(); + private Event outcome; + /** * Creates a new mock flow execution context -- automatically installs a root flow definition and active flow * session. @@ -91,6 +94,10 @@ public class MockFlowExecutionContext implements FlowExecutionContext { return activeSession != null; } + public boolean hasEnded() { + return hasStarted() && !isActive(); + } + public FlowSession getActiveSession() throws IllegalStateException { if (activeSession == null) { throw new IllegalStateException("No flow session is active"); @@ -110,6 +117,10 @@ public class MockFlowExecutionContext implements FlowExecutionContext { return attributes; } + public Event getOutcome() { + return outcome; + } + // mutators /** @@ -147,6 +158,14 @@ public class MockFlowExecutionContext implements FlowExecutionContext { this.conversationScope = scope; } + /** + * Sets the result of this flow ending. + * @param outcome the ending outcome event. + */ + public void setOutcome(Event outcome) { + this.outcome = outcome; + } + // convenience accessors /** diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKey.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKey.java index 11e0ecd8..b7bc0a3d 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKey.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockFlowExecutionKey.java @@ -19,21 +19,19 @@ import org.springframework.webflow.execution.FlowExecutionKey; /** * A simple flow execution key implementation. New instances of this class get their values from a sequence encapsulated - * as a stativ private variable of this class. + * as a static private variable of this class. * * @author Keith Donald */ public class MockFlowExecutionKey extends FlowExecutionKey { - private static int nextKey = 1; - - private int value; + private String value; /** * Creates a new mock flow execution key. */ - public MockFlowExecutionKey() { - this.value = nextKey(); + public MockFlowExecutionKey(String value) { + this.value = value; } public boolean equals(Object o) { @@ -41,20 +39,14 @@ public class MockFlowExecutionKey extends FlowExecutionKey { return false; } MockFlowExecutionKey key = (MockFlowExecutionKey) o; - return value == key.value; + return value.equals(key.value); } public int hashCode() { - return value * 29; + return value.hashCode(); } public String toString() { - return String.valueOf(value); - } - - private static int nextKey() { - int key = nextKey; - nextKey++; - return key; + return value; } } \ No newline at end of file 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 4b94e3a9..1a364d0e 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 @@ -12,6 +12,6 @@ import org.springframework.webflow.execution.FlowExecutionKeyFactory; */ public class MockFlowExecutionKeyFactory implements FlowExecutionKeyFactory { public FlowExecutionKey getKey(FlowExecution execution) { - return new MockFlowExecutionKey(); + return new GeneratedFlowExecutionKey(); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java index 1d595675..bae89b0a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockRequestContext.java @@ -19,7 +19,6 @@ import org.springframework.binding.message.DefaultMessageContextFactory; import org.springframework.binding.message.MessageContext; import org.springframework.context.support.StaticMessageSource; import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.FlowExecutionRequestInfo; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; @@ -173,9 +172,7 @@ public class MockRequestContext implements RequestContext { "Flow execution key not yet assigned; unable to build the flow execution url"); } else { String flowDefinitionId = flowExecutionContext.getDefinition().getId(); - FlowExecutionRequestInfo requestInfo = new FlowExecutionRequestInfo(flowDefinitionId, flowExecutionContext - .getKey().toString()); - return externalContext.buildFlowExecutionUrl(requestInfo, true); + return externalContext.getFlowExecutionUri(flowDefinitionId, flowExecutionContext.getKey().toString()); } } @@ -184,10 +181,7 @@ public class MockRequestContext implements RequestContext { throw new IllegalStateException( "Flow execution key not yet assigned; unable to send a flow execution redirect request"); } else { - String flowDefinitionId = flowExecutionContext.getDefinition().getId(); - FlowExecutionRequestInfo requestInfo = new FlowExecutionRequestInfo(flowDefinitionId, flowExecutionContext - .getKey().toString()); - externalContext.sendFlowExecutionRedirect(requestInfo); + externalContext.requestFlowExecutionRedirect(); } } 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 77905bc4..b8a408a8 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 @@ -90,7 +90,7 @@ public class MockRequestControlContext extends MockRequestContext implements Req } public FlowExecutionKey assignFlowExecutionKey() { - MockFlowExecutionKey key = new MockFlowExecutionKey(); + GeneratedFlowExecutionKey key = new GeneratedFlowExecutionKey(); getMockFlowExecutionContext().setKey(key); return key; } 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 93b9ccf7..464a7b4d 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 @@ -104,7 +104,7 @@ public abstract class AbstractFlowExecutionTests extends TestCase { */ protected void startFlow(ExternalContext context) throws FlowExecutionException { flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition()); - flowExecution.start(context); + flowExecution.start(null, context); } /** diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/ExternalRedirectActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/ExternalRedirectActionTests.java index 935022ff..2b1aabe9 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/ExternalRedirectActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/ExternalRedirectActionTests.java @@ -1,10 +1,10 @@ package org.springframework.webflow.action; +import junit.framework.TestCase; + import org.springframework.binding.expression.support.StaticExpression; import org.springframework.webflow.test.MockRequestContext; -import junit.framework.TestCase; - public class ExternalRedirectActionTests extends TestCase { private ExternalRedirectAction action; @@ -12,7 +12,7 @@ public class ExternalRedirectActionTests extends TestCase { action = new ExternalRedirectAction(new StaticExpression("/wherever")); MockRequestContext context = new MockRequestContext(); action.execute(context); - assertEquals("/wherever", context.getMockExternalContext().getExternalRedirectResult()); + assertEquals("/wherever", context.getMockExternalContext().getExternalRedirectUrl()); } public void testExecuteWithNullResourceUri() throws Exception { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/FlowDefinitionRedirectActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/FlowDefinitionRedirectActionTests.java index ed402c8f..0bd96c3f 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/FlowDefinitionRedirectActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/FlowDefinitionRedirectActionTests.java @@ -7,7 +7,6 @@ import junit.framework.TestCase; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.support.StaticExpression; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; import org.springframework.webflow.test.MockRequestContext; public class FlowDefinitionRedirectActionTests extends TestCase { @@ -15,32 +14,26 @@ public class FlowDefinitionRedirectActionTests extends TestCase { public void testExecute() throws Exception { Expression flowId = new StaticExpression("user"); - Expression[] requestElements = new Expression[] { new StaticExpression("1") }; - Map requestParameters = new HashMap(); - requestParameters.put(new StaticExpression("foo"), new StaticExpression("bar")); - action = new FlowDefinitionRedirectAction(flowId, requestElements, requestParameters); + Map input = new HashMap(); + input.put(new StaticExpression("foo"), new StaticExpression("bar")); + action = new FlowDefinitionRedirectAction(flowId, input); MockRequestContext context = new MockRequestContext(); action.execute(context); - FlowDefinitionRequestInfo result = context.getMockExternalContext().getFlowDefinitionRedirectResult(); - assertEquals("user", result.getFlowDefinitionId()); - assertEquals("1", result.getRequestPath().getElement(0)); - assertEquals("bar", result.getRequestParameters().get("foo")); + assertEquals("user", context.getMockExternalContext().getFlowRedirectFlowId()); + assertEquals("bar", context.getMockExternalContext().getFlowRedirectFlowInput().get("foo")); } public void testExecuteWithNullRequestFields() throws Exception { Expression flowId = new StaticExpression("user"); - action = new FlowDefinitionRedirectAction(flowId, null, null); + action = new FlowDefinitionRedirectAction(flowId, null); MockRequestContext context = new MockRequestContext(); action.execute(context); - FlowDefinitionRequestInfo result = context.getMockExternalContext().getFlowDefinitionRedirectResult(); - assertEquals("user", result.getFlowDefinitionId()); - assertEquals(null, result.getRequestPath()); - assertEquals(null, result.getRequestParameters()); + assertEquals("user", context.getMockExternalContext().getFlowRedirectFlowId()); } public void testExecuteWithNullFlowId() throws Exception { try { - action = new FlowDefinitionRedirectAction(null, null, null); + action = new FlowDefinitionRedirectAction(null, null); fail("Should have failed"); } catch (IllegalArgumentException e) { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java index 35b9fb24..d54a6555 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/EnableScopesBeanDefinitionParserTests.java @@ -23,8 +23,6 @@ public class EnableScopesBeanDefinitionParserTests extends TestCase { public void testExecute() { MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - executor.executeFlowRequest(context); } public static class ConfigurationListener extends FlowExecutionListenerAdapter { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java index 4f011381..51bf66a2 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorBeanDefinitionParserTests.java @@ -20,8 +20,6 @@ public class FlowExecutorBeanDefinitionParserTests extends TestCase { public void testExecute() { MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - executor.executeFlowRequest(context); } public static class ConfigurationListener extends FlowExecutionListenerAdapter { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorFactoryBeanTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorFactoryBeanTests.java index 8554abdb..3cdda32f 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorFactoryBeanTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowExecutorFactoryBeanTests.java @@ -50,8 +50,6 @@ public class FlowExecutorFactoryBeanTests extends TestCase { factoryBean.afterPropertiesSet(); FlowExecutor executor = (FlowExecutor) factoryBean.getObject(); MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - executor.executeFlowRequest(context); } public void testGetFlowExecutorOptionsSpecified() throws Exception { @@ -78,11 +76,7 @@ public class FlowExecutorFactoryBeanTests extends TestCase { factoryBean.afterPropertiesSet(); FlowExecutor executor = (FlowExecutor) factoryBean.getObject(); MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - executor.executeFlowRequest(context); MockExternalContext context2 = new MockExternalContext(); - context2.setFlowExecutionKey(context.getFlowExecutionKey()); - executor.executeFlowRequest(context); } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/RequestPathTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/RequestPathTests.java deleted file mode 100644 index 1cb30908..00000000 --- a/spring-webflow/src/test/java/org/springframework/webflow/context/RequestPathTests.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.springframework.webflow.context; - -import junit.framework.TestCase; - -public class RequestPathTests extends TestCase { - public void testNewPathParse() { - RequestPath path = new RequestPath("/users/1"); - assertEquals(2, path.getElementCount()); - assertEquals("users", path.getElement(0)); - assertEquals("1", path.getElement(1)); - } - - public void testNewPathParseTrailingSlash() { - RequestPath path = new RequestPath("/users/1/"); - assertEquals(2, path.getElementCount()); - assertEquals("users", path.getElement(0)); - assertEquals("1", path.getElement(1)); - } - - public void testNewPathParseNoLeadingSlash() { - try { - new RequestPath("users/1/"); - fail("should have failed"); - } catch (IllegalArgumentException e) { - - } - } - - public void testOutOfBounds() { - RequestPath path = new RequestPath("/users/1/"); - assertEquals(2, path.getElementCount()); - assertEquals("users", path.getElement(0)); - try { - assertEquals("1", path.getElement(2)); - fail("should have failed"); - } catch (ArrayIndexOutOfBoundsException e) { - - } - } - - public void testEmptyPath() { - RequestPath path = new RequestPath("/"); - assertEquals(1, path.getElementCount()); - assertEquals("", path.getElement(0)); - } - - public void testSinglePathElement() { - RequestPath path = new RequestPath("/users"); - assertEquals(1, path.getElementCount()); - assertEquals("users", path.getElement(0)); - } - - public void testToString() { - RequestPath path2 = new RequestPath("/users"); - RequestPath path3 = new RequestPath("/users/1/foo/bar"); - assertEquals("/users", path2.toString()); - assertEquals("/users/1/foo/bar", path3.toString()); - } - - public void testPopElement() { - RequestPath path = new RequestPath("/users/1"); - assertEquals(2, path.getElementCount()); - path = path.pop(1); - assertEquals(1, path.getElementCount()); - assertEquals("/1", path.toString()); - } - - public void testPopAllElements() { - RequestPath path = new RequestPath("/users/1"); - assertEquals(2, path.getElementCount()); - path = path.pop(2); - assertNull(path); - } - - public void testPopEmptyPath() { - RequestPath path = new RequestPath("/"); - assertEquals(1, path.getElementCount()); - path = path.pop(); - assertNull(path); - } -} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java index 4657487f..66588ef1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java @@ -19,14 +19,6 @@ import junit.framework.TestCase; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletContext; -import org.springframework.webflow.context.ExternalContext; -import org.springframework.webflow.context.ExternalContextHolder; -import org.springframework.webflow.context.FlowDefinitionRequestInfo; -import org.springframework.webflow.context.FlowExecutionRequestInfo; -import org.springframework.webflow.context.RequestPath; -import org.springframework.webflow.executor.FlowExecutor; -import org.springframework.webflow.test.MockParameterMap; /** * Unit tests for {@link ServletExternalContext}. @@ -37,145 +29,15 @@ public class ServletExternalContextTests extends TestCase { private MockHttpServletResponse response; - private FlowExecutor flowExecutor; - private ServletExternalContext context; protected void setUp() { request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); - flowExecutor = new StubFlowExecutor(); } - public void testProcessLaunchFlowRequest() throws Exception { - request.setPathInfo("/booking"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("booking", context.getFlowId()); - } + public void testtest() { - public void testProcessLaunchFlowRequestTrailingSlash() throws Exception { - request.setPathInfo("/booking/"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("booking", context.getFlowId()); - } - - public void testProcessLaunchFlowRequestElements() throws Exception { - request.setPathInfo("/users/1"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("users", context.getFlowId()); - assertEquals(context.getRequestPath().getElement(0), "1"); - } - - public void testProcessLaunchFlowMultipleRequestElements() throws Exception { - request.setPathInfo("/users/1/foo/bar//baz/"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("users", context.getFlowId()); - assertEquals("1", context.getRequestPath().getElement(0)); - assertEquals("foo", context.getRequestPath().getElement(1)); - assertEquals("bar", context.getRequestPath().getElement(2)); - assertEquals("", context.getRequestPath().getElement(3)); - assertEquals("baz", context.getRequestPath().getElement(4)); - } - - public void testProcessResumeFlowExecution() throws Exception { - request.setPathInfo("/executions/booking/_c12345_k12345"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("booking", context.getFlowId()); - assertEquals("_c12345_k12345", context.getFlowExecutionKey()); - } - - public void testExternalContextUnbound() throws Exception { - request.setPathInfo("/executions/booking/_c12345_k12345"); - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - try { - ExternalContextHolder.getExternalContext(); - fail("Should have failed"); - } catch (IllegalStateException e) { - - } - } - - public void testNoRequestPathInfo() { - request.setPathInfo(null); - try { - context = new ServletExternalContext(new MockServletContext(), request, response); - fail("Should have failed"); - } catch (IllegalArgumentException e) { - - } - } - - public void testSendFlowExecutionRedirect() throws Exception { - request.setPathInfo("/users/1"); - flowExecutor = new FlowExecutor() { - public void executeFlowRequest(ExternalContext context) { - context.sendFlowExecutionRedirect(new FlowExecutionRequestInfo("users", "_c12345_k12345")); - } - }; - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("/executions/users/_c12345_k12345", response.getRedirectedUrl()); - } - - public void testFlowExecutionRedirectAttemptOnEnd() throws Exception { - request.setPathInfo("/users/1"); - flowExecutor = new FlowExecutor() { - public void executeFlowRequest(ExternalContext context) { - context.sendFlowExecutionRedirect(new FlowExecutionRequestInfo("users", "_c12345_k12345")); - context.setEndedResult("_c12345_k12345"); - } - }; - context = new ServletExternalContext(new MockServletContext(), request, response); - try { - context.executeFlowRequest(flowExecutor); - fail("Should have failed"); - } catch (IllegalStateException e) { - - } - } - - public void testSendFlowDefinitionRedirect() throws Exception { - request.setPathInfo("/users/1"); - flowExecutor = new FlowExecutor() { - public void executeFlowRequest(ExternalContext context) { - MockParameterMap parameters = new MockParameterMap(); - parameters.put("foo", "bar"); - parameters.put("bar", "baz"); - RequestPath requestPath = new RequestPath("/1/you&me"); - FlowDefinitionRequestInfo requestInfo = new FlowDefinitionRequestInfo("customers", requestPath, - parameters, "frag"); - context.sendFlowDefinitionRedirect(requestInfo); - context.setEndedResult(null); - } - }; - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("/customers/1/you%26me?foo=bar&bar=baz#frag", response.getRedirectedUrl()); - } - - public void testSendExternalRedirect() throws Exception { - request.setPathInfo("/users/1"); - flowExecutor = new FlowExecutor() { - public void executeFlowRequest(ExternalContext context) { - context.sendExternalRedirect("/foo/bar/baz"); - context.setEndedResult(null); - } - }; - context = new ServletExternalContext(new MockServletContext(), request, response); - context.executeFlowRequest(flowExecutor); - assertEquals("/foo/bar/baz", response.getRedirectedUrl()); - } - - public class StubFlowExecutor implements FlowExecutor { - public void executeFlowRequest(ExternalContext context) { - assertNotNull(ExternalContextHolder.getExternalContext()); - } } } 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 ab8f0d87..058a97a5 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 @@ -49,7 +49,7 @@ public class ViewStateTests extends TestCase { MockRequestControlContext context = new MockRequestControlContext(flow); state.enter(context); assertFalse("Render called", context.getFlowScope().contains("renderCalled")); - assertNotNull(context.getMockExternalContext().getFlowExecutionRedirectResult()); + assertTrue(context.getMockExternalContext().flowExecutionRedirectRequested()); } public void testEnterViewStateWithAlwaysRedirectOnPause() { @@ -62,7 +62,7 @@ public class ViewStateTests extends TestCase { context.setAlwaysRedirectOnPause(true); state.enter(context); assertFalse("Render called", context.getFlowScope().contains("renderCalled")); - assertNotNull(context.getMockExternalContext().getFlowExecutionRedirectResult()); + assertTrue(context.getMockExternalContext().flowExecutionRedirectRequested()); } public void testResumeViewStateForRefresh() { 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 e59cfe13..373d9504 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.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(listener1)); FlowExecution execution = factory.createFlowExecution(flowDefinition); assertFalse(execution.isActive()); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertTrue(starting); } @@ -103,7 +103,7 @@ public class FlowExecutionImplFactoryTests extends TestCase { } }); FlowExecution execution = factory.createFlowExecution(flowDefinition); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertTrue(getKeyCalled); assertNull(execution.getKey()); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java index f62e1431..f6f1a1d2 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java @@ -36,8 +36,8 @@ import org.springframework.webflow.execution.FlowSession; import org.springframework.webflow.execution.MockFlowExecutionListener; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; +import org.springframework.webflow.test.GeneratedFlowExecutionKey; import org.springframework.webflow.test.MockExternalContext; -import org.springframework.webflow.test.MockFlowExecutionKey; /** * General flow execution tests. @@ -59,7 +59,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource())); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); - execution.start(context); + execution.start(null, context); assertTrue(execution.hasStarted()); assertFalse(execution.isActive()); assertEquals(1, mockListener.getRequestsSubmittedCount()); @@ -91,7 +91,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setListeners(listeners); execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource())); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); assertTrue(execution.isActive()); assertEquals(1, mockListener.getPausedCount()); } @@ -111,7 +111,7 @@ public class FlowExecutionImplTests extends TestCase { MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { - execution.start(context); + execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); @@ -131,7 +131,7 @@ public class FlowExecutionImplTests extends TestCase { MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { - execution.start(context); + execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); @@ -152,7 +152,7 @@ public class FlowExecutionImplTests extends TestCase { MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { - execution.start(context); + execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException ex) { assertSame(e, ex); @@ -165,9 +165,9 @@ public class FlowExecutionImplTests extends TestCase { FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource())); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); try { - execution.start(context); + execution.start(null, context); fail("Should've failed"); } catch (IllegalStateException e) { @@ -184,7 +184,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); context = new MockExternalContext(); execution.resume(context); assertEquals(1, mockListener.getResumingCount()); @@ -204,7 +204,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource())); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); @@ -221,7 +221,7 @@ public class FlowExecutionImplTests extends TestCase { FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setMessageContextFactory(new DefaultMessageContextFactory(new StaticMessageSource())); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); try { execution.resume(context); fail("Should've failed"); @@ -244,7 +244,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); @@ -270,7 +270,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); @@ -294,7 +294,7 @@ public class FlowExecutionImplTests extends TestCase { execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); - execution.start(context); + execution.start(null, context); assertNull("RequestContext was not released", RequestContextHolder.getRequestContext()); context = new MockExternalContext(); @@ -305,7 +305,7 @@ public class FlowExecutionImplTests extends TestCase { private static class MockFlowExecutionKeyFactory implements FlowExecutionKeyFactory { public FlowExecutionKey getKey(FlowExecution execution) { - return new MockFlowExecutionKey(); + return new GeneratedFlowExecutionKey(); } } 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 5ae60709..4fda6f3d 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 @@ -17,7 +17,7 @@ import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.MockFlowExecutionListener; import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader; import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader; -import org.springframework.webflow.test.MockFlowExecutionKey; +import org.springframework.webflow.test.GeneratedFlowExecutionKey; public class FlowExecutionStateRestorerImplTests extends TestCase { private SimpleFlowDefinitionLocator definitionLocator; @@ -25,7 +25,7 @@ public class FlowExecutionStateRestorerImplTests extends TestCase { private LocalAttributeMap executionAttributes = new LocalAttributeMap(); private FlowExecutionListener listener = new MockFlowExecutionListener(); private FlowExecutionListenerLoader executionListenerLoader = new StaticFlowExecutionListenerLoader(listener); - MockFlowExecutionKey newKey = new MockFlowExecutionKey(); + GeneratedFlowExecutionKey newKey = new GeneratedFlowExecutionKey(); private FlowExecutionKeyFactory executionKeyFactory = new FlowExecutionKeyFactory() { public FlowExecutionKey getKey(FlowExecution execution) { return newKey; @@ -40,7 +40,7 @@ public class FlowExecutionStateRestorerImplTests extends TestCase { } public void testRestoreStateNoSessions() { - FlowExecutionKey key = new MockFlowExecutionKey(); + FlowExecutionKey key = new GeneratedFlowExecutionKey(); LocalAttributeMap conversationScope = new LocalAttributeMap(); FlowExecutionImpl execution = new FlowExecutionImpl("parent", new LinkedList()); stateRestorer.restoreState(execution, key, conversationScope, executionKeyFactory); @@ -55,7 +55,7 @@ public class FlowExecutionStateRestorerImplTests extends TestCase { } public void testRestoreStateFlowDefinitionIdNotSet() { - FlowExecutionKey key = new MockFlowExecutionKey(); + FlowExecutionKey key = new GeneratedFlowExecutionKey(); LocalAttributeMap conversationScope = new LocalAttributeMap(); FlowExecutionImpl execution = new FlowExecutionImpl(); try { @@ -67,7 +67,7 @@ public class FlowExecutionStateRestorerImplTests extends TestCase { } public void testRestoreStateFlowSessionsNotSet() { - FlowExecutionKey key = new MockFlowExecutionKey(); + FlowExecutionKey key = new GeneratedFlowExecutionKey(); LocalAttributeMap conversationScope = new LocalAttributeMap(); FlowExecutionImpl execution = new FlowExecutionImpl("parent", null); try { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/support/TransitionExecutingFlowExecutionExceptionHandlerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/support/TransitionExecutingFlowExecutionExceptionHandlerTests.java index 4f41b86e..793f189f 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/support/TransitionExecutingFlowExecutionExceptionHandlerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/support/TransitionExecutingFlowExecutionExceptionHandlerTests.java @@ -95,7 +95,7 @@ public class TransitionExecutingFlowExecutionExceptionHandlerTests extends TestC FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); factory.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(listener)); FlowExecution execution = factory.createFlowExecution(flow); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertTrue("Should have ended", !execution.isActive()); } @@ -105,7 +105,7 @@ public class TransitionExecutingFlowExecutionExceptionHandlerTests extends TestC flow.getExceptionHandlerSet().add(handler); FlowExecution execution = new FlowExecutionImplFactory().createFlowExecution(flow); try { - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); fail("Should have failed no such state"); } catch (IllegalArgumentException e) { } @@ -114,7 +114,7 @@ public class TransitionExecutingFlowExecutionExceptionHandlerTests extends TestC public void testStateExceptionHandlingRethrow() { FlowExecution execution = new FlowExecutionImplFactory().createFlowExecution(flow); try { - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); fail("Should have rethrown"); } catch (FlowExecutionException e) { // expected @@ -147,7 +147,7 @@ public class TransitionExecutingFlowExecutionExceptionHandlerTests extends TestC }; Flow flow = new FlowAssembler(builder, new MockFlowBuilderContext("flow")).assembleFlow(); FlowExecution execution = new FlowExecutionImplFactory().createFlowExecution(flow); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertTrue(execution.isActive()); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/continuation/SerializedFlowExecutionContinuationFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/continuation/SerializedFlowExecutionContinuationFactoryTests.java index f034030b..5ae83ae5 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/continuation/SerializedFlowExecutionContinuationFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/continuation/SerializedFlowExecutionContinuationFactoryTests.java @@ -43,7 +43,7 @@ public class SerializedFlowExecutionContinuationFactoryTests extends TestCase { public void testCreateContinuation() { FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow); - flowExecution.start(new MockExternalContext()); + flowExecution.start(null, new MockExternalContext()); flowExecution.getActiveSession().getScope().put("foo", "bar"); FlowExecutionContinuation continuation = factory.createContinuation(flowExecution); FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) continuation.unmarshal(); @@ -58,7 +58,7 @@ public class SerializedFlowExecutionContinuationFactoryTests extends TestCase { public void testRestoreContinuation() { FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow); - flowExecution.start(new MockExternalContext()); + flowExecution.start(null, new MockExternalContext()); flowExecution.getActiveSession().getScope().put("foo", "bar"); FlowExecutionContinuation continuation = factory.createContinuation(flowExecution); byte[] bytes = continuation.toByteArray(); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java index f9c8901a..b54d0d0d 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java @@ -90,7 +90,7 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase { FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); factory.setExecutionKeyFactory(repository); FlowExecution execution = factory.createFlowExecution(flow); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertNotNull(execution.getKey()); repository.putFlowExecution(execution); FlowExecution execution2 = repository.getFlowExecution(execution.getKey()); @@ -113,7 +113,7 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase { FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); factory.setExecutionKeyFactory(repository); FlowExecution execution = factory.createFlowExecution(flow); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); assertNotNull(execution.getKey()); repository.putFlowExecution(execution); repository.removeFlowExecution(execution); @@ -140,7 +140,7 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase { FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); factory.setExecutionKeyFactory(repository); FlowExecution execution = factory.createFlowExecution(flow); - execution.start(new MockExternalContext()); + execution.start(null, new MockExternalContext()); try { repository.removeFlowExecution(execution); repository.removeFlowExecution(execution); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/executor/FlowExecutorImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/executor/FlowExecutorImplTests.java index edd85c4d..c7ae49ca 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/executor/FlowExecutorImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/executor/FlowExecutorImplTests.java @@ -2,96 +2,182 @@ package org.springframework.webflow.executor; import junit.framework.TestCase; -import org.springframework.webflow.context.ExternalContextHolder; -import org.springframework.webflow.conversation.impl.SessionBindingConversationManager; -import org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl; -import org.springframework.webflow.engine.EndState; -import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.RequestControlContext; -import org.springframework.webflow.engine.State; -import org.springframework.webflow.engine.StubViewFactory; -import org.springframework.webflow.engine.ViewState; -import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; -import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer; -import org.springframework.webflow.execution.FlowExecutionException; -import org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository; +import org.easymock.EasyMock; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.FlowExecution; +import org.springframework.webflow.execution.FlowExecutionFactory; +import org.springframework.webflow.execution.FlowExecutionKey; +import org.springframework.webflow.execution.repository.FlowExecutionLock; +import org.springframework.webflow.execution.repository.FlowExecutionRepository; +import org.springframework.webflow.test.GeneratedFlowExecutionKey; import org.springframework.webflow.test.MockExternalContext; +import org.springframework.webflow.test.MockFlowExecutionKey; public class FlowExecutorImplTests extends TestCase { - private FlowDefinitionRegistryImpl definitionLocator; - private FlowExecutionImplFactory executionFactory; - private DefaultFlowExecutionRepository executionRepository; - private FlowExecutorImpl executor; + private FlowExecutor flowExecutor; + + // mocks + private FlowDefinitionLocator locator; + private FlowDefinition definition; + private FlowExecutionFactory factory; + private FlowExecution execution; + private FlowExecutionRepository repository; + private FlowExecutionLock lock; protected void setUp() { - definitionLocator = new FlowDefinitionRegistryImpl(); - executionFactory = new FlowExecutionImplFactory(); - executionRepository = new DefaultFlowExecutionRepository(new SessionBindingConversationManager(), - new FlowExecutionImplStateRestorer(definitionLocator)); - executionFactory.setExecutionKeyFactory(executionRepository); - executor = new FlowExecutorImpl(definitionLocator, executionFactory, executionRepository); + locator = (FlowDefinitionLocator) EasyMock.createMock(FlowDefinitionLocator.class); + definition = (FlowDefinition) EasyMock.createMock(FlowDefinition.class); + factory = (FlowExecutionFactory) EasyMock.createMock(FlowExecutionFactory.class); + execution = (FlowExecution) EasyMock.createMock(FlowExecution.class); + repository = (FlowExecutionRepository) EasyMock.createMock(FlowExecutionRepository.class); + lock = (FlowExecutionLock) EasyMock.createMock(FlowExecutionLock.class); + + flowExecutor = new FlowExecutorImpl(locator, factory, repository); } - public void testLaunchAndEnd() { - Flow flow = new Flow("flow"); - new EndState(flow, "end"); - definitionLocator.registerFlowDefinition(flow); + public void testLaunchFlowExecution() { + String flowId = "foo"; + MutableAttributeMap input = null; MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - ExternalContextHolder.setExternalContext(context); - executor.executeFlowRequest(context); - ExternalContextHolder.setExternalContext(null); + EasyMock.expect(locator.getFlowDefinition(flowId)).andReturn(definition); + EasyMock.expect(factory.createFlowExecution(definition)).andReturn(execution); - assertNull(context.getFlowExecutionRedirectResult()); - assertNull(context.getPausedFlowExecutionKeyResult()); - assertNull(context.getExceptionResult()); + execution.start(input, context); + + execution.hasEnded(); + EasyMock.expectLastCall().andReturn(Boolean.FALSE); + + repository.putFlowExecution(execution); + + EasyMock.expect(execution.getDefinition()).andReturn(definition); + EasyMock.expect(definition.getId()).andReturn("foo"); + EasyMock.expect(execution.getKey()).andReturn(new MockFlowExecutionKey("12345")); + + replayMocks(); + + FlowExecutionResult result = flowExecutor.launchExecution("foo", null, context); + assertTrue(result.paused()); + assertEquals("12345", result.getPausedKey()); + assertFalse(result.ended()); + assertNull(result.getEndedOutcome()); + assertNull(result.getEndedOutput()); + verifyMocks(); } - public void testLaunchAndResume() { - Flow flow = new Flow("flow"); - new ViewState(flow, "pause", new StubViewFactory()); - definitionLocator.registerFlowDefinition(flow); + public void testLaunchFlowExecutionEndsAfterProcessing() { + String flowId = "foo"; + MutableAttributeMap input = null; MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); - ExternalContextHolder.setExternalContext(context); - executor.executeFlowRequest(context); - ExternalContextHolder.setExternalContext(null); + EasyMock.expect(locator.getFlowDefinition(flowId)).andReturn(definition); + EasyMock.expect(factory.createFlowExecution(definition)).andReturn(execution); - assertNotNull(context.getPausedFlowExecutionKeyResult()); - assertNull(context.getExceptionResult()); - assertNull(context.getFlowExecutionRedirectResult()); + execution.start(input, context); - MockExternalContext context2 = new MockExternalContext(); - context2.setSessionMap(context.getSessionMap()); - context2.setFlowId("flow"); - context2.setFlowExecutionKey(context.getPausedFlowExecutionKeyResult()); + execution.hasEnded(); + EasyMock.expectLastCall().andReturn(Boolean.TRUE); - ExternalContextHolder.setExternalContext(context); - executor.executeFlowRequest(context2); - ExternalContextHolder.setExternalContext(null); + EasyMock.expect(execution.getDefinition()).andReturn(definition); + EasyMock.expect(definition.getId()).andReturn("foo"); + EasyMock.expect(execution.getOutcome()).andReturn(new Event(execution, "finish", null)); + + replayMocks(); + + FlowExecutionResult result = flowExecutor.launchExecution("foo", null, context); + assertTrue(result.ended()); + assertEquals("finish", result.getEndedOutcome()); + assertTrue(result.getEndedOutput().isEmpty()); + assertFalse(result.paused()); + assertNull(result.getPausedKey()); + verifyMocks(); } - public void testLaunchAndException() { - Flow flow = new Flow("flow"); - final UnsupportedOperationException e = new UnsupportedOperationException(); - new State(flow, "exception") { - protected void doEnter(RequestControlContext context) throws FlowExecutionException { - throw e; - } - }; - definitionLocator.registerFlowDefinition(flow); + public void testResumeFlowExecution() { + String flowExecutionKey = "12345"; MockExternalContext context = new MockExternalContext(); - context.setFlowId("flow"); + FlowExecutionKey key = new GeneratedFlowExecutionKey(); - ExternalContextHolder.setExternalContext(context); - executor.executeFlowRequest(context); - ExternalContextHolder.setExternalContext(null); + EasyMock.expect(repository.parseFlowExecutionKey(flowExecutionKey)).andReturn(key); + EasyMock.expect(repository.getLock(key)).andReturn(lock); - assertNull(context.getFlowExecutionRedirectResult()); - assertNull(context.getPausedFlowExecutionKeyResult()); - assertNotNull(context.getExceptionResult()); - assertSame(e, context.getExceptionResult().getCause()); + lock.lock(); + EasyMock.expect(repository.getFlowExecution(key)).andReturn(execution); + + execution.resume(context); + + execution.hasEnded(); + EasyMock.expectLastCall().andReturn(Boolean.FALSE); + + repository.putFlowExecution(execution); + + EasyMock.expect(execution.getDefinition()).andReturn(definition); + EasyMock.expect(definition.getId()).andReturn("foo"); + EasyMock.expect(execution.getKey()).andReturn(new MockFlowExecutionKey("12345")); + + lock.unlock(); + + replayMocks(); + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + verifyMocks(); + + assertTrue(result.paused()); + assertEquals("12345", result.getPausedKey()); + assertFalse(result.ended()); + assertNull(result.getEndedOutcome()); + assertNull(result.getEndedOutput()); + verifyMocks(); + + } + + public void testResumeFlowExecutionEndsAfterProcessing() { + String flowExecutionKey = "12345"; + MockExternalContext context = new MockExternalContext(); + FlowExecutionKey key = new MockFlowExecutionKey("12345"); + + EasyMock.expect(repository.parseFlowExecutionKey(flowExecutionKey)).andReturn(key); + EasyMock.expect(repository.getLock(key)).andReturn(lock); + + lock.lock(); + EasyMock.expect(repository.getFlowExecution(key)).andReturn(execution); + + execution.resume(context); + + execution.hasEnded(); + EasyMock.expectLastCall().andReturn(Boolean.TRUE); + + EasyMock.expect(execution.getDefinition()).andReturn(definition); + EasyMock.expect(definition.getId()).andReturn("foo"); + + LocalAttributeMap output = new LocalAttributeMap(); + output.put("foo", "bar"); + EasyMock.expect(execution.getOutcome()).andReturn(new Event(execution, "finish", output)); + + repository.removeFlowExecution(execution); + + lock.unlock(); + + replayMocks(); + + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + assertTrue(result.ended()); + assertEquals("finish", result.getEndedOutcome()); + assertEquals(output, result.getEndedOutput()); + assertFalse(result.paused()); + assertNull(result.getPausedKey()); + + verifyMocks(); + } + + private void replayMocks() { + EasyMock.replay(new Object[] { locator, definition, factory, execution, repository }); + } + + private void verifyMocks() { + EasyMock.verify(new Object[] { locator, definition, factory, execution, repository }); } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/MvcViewFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/MvcViewFactoryTests.java index 13247330..66a72d69 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/MvcViewFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/MvcViewFactoryTests.java @@ -25,8 +25,8 @@ 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.test.GeneratedFlowExecutionKey; import org.springframework.webflow.test.MockExternalContext; -import org.springframework.webflow.test.MockFlowExecutionKey; import org.springframework.webflow.test.MockRequestContext; public class MvcViewFactoryTests extends TestCase { @@ -55,10 +55,10 @@ public class MvcViewFactoryTests extends TestCase { MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - externalContext.setRequest(request); - externalContext.setResponse(response); + externalContext.setNativeRequest(request); + externalContext.setNativeResponse(response); context.setExternalContext(externalContext); - context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey()); + context.getMockFlowExecutionContext().setKey(new GeneratedFlowExecutionKey()); View view = viewFactory.getView(context); assertEquals(false, view.eventSignaled()); view.render(); @@ -75,10 +75,10 @@ public class MvcViewFactoryTests extends TestCase { MockExternalContext externalContext = new MockExternalContext(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - externalContext.setRequest(request); - externalContext.setResponse(response); + externalContext.setNativeRequest(request); + externalContext.setNativeResponse(response); context.setExternalContext(externalContext); - context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey()); + context.getMockFlowExecutionContext().setKey(new GeneratedFlowExecutionKey()); View view = viewFactory.getView(context); assertEquals(false, view.eventSignaled()); view.render(); @@ -103,10 +103,10 @@ public class MvcViewFactoryTests extends TestCase { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); externalContext.putRequestParameter("_eventId", "foo"); - externalContext.setRequest(request); - externalContext.setResponse(response); + externalContext.setNativeRequest(request); + externalContext.setNativeResponse(response); context.setExternalContext(externalContext); - context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey()); + context.getMockFlowExecutionContext().setKey(new GeneratedFlowExecutionKey()); View view = viewFactory.getView(context); assertEquals(true, view.eventSignaled()); Event e = view.getEvent(); @@ -134,10 +134,10 @@ public class MvcViewFactoryTests extends TestCase { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); externalContext.putRequestParameter("_eventId_foo", "true"); - externalContext.setRequest(request); - externalContext.setResponse(response); + externalContext.setNativeRequest(request); + externalContext.setNativeResponse(response); context.setExternalContext(externalContext); - context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey()); + context.getMockFlowExecutionContext().setKey(new GeneratedFlowExecutionKey()); View view = viewFactory.getView(context); assertEquals(true, view.eventSignaled()); Event e = view.getEvent();