executor refinements; better exception handling, flow handler adapter mvc integration, 1.x usage compatability

This commit is contained in:
Keith Donald
2008-02-26 16:10:16 +00:00
parent 631f0667ea
commit 9804e6a858
48 changed files with 785 additions and 1478 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {
* <p>
* 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.

View File

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

View File

@@ -123,10 +123,9 @@ public interface RequestContext {
* constructs within that environment.
* <p>
* 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();

View File

@@ -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 <i>driving the executions of
@@ -23,15 +25,27 @@ import org.springframework.webflow.context.ExternalContext;
* clients.
* <p>
* 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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