Introduced ResponseInstructionHandler convenience class and refactored FlowController, PortletFlowController, FlowAction and FlowPhaseListener to use it.

This commit is contained in:
Erwin Vervaet
2007-03-31 20:44:28 +00:00
parent 3ea61b06eb
commit 7e393dc471
6 changed files with 331 additions and 141 deletions

View File

@@ -56,6 +56,8 @@ Package org.springframework.webflow.executor
* Added support for configuring a <flow:executor> in a JSF environment, greatly simplifying JSF SWF
configuration and making it consistent with Spring MVC and Struts. This also means its much easier
to benefit from defaults such as a continuation-based flow execution repository and 'alwaysRedirectOnPause'.
* Introduced ResponseInstructionHandler convenience class and refactored FlowController, PortletFlowController,
FlowAction and FlowPhaseListener to use it.
Package org.springframework.webflow.test
* Added constructor taking test name argument to AbstractFlowExecutionTests, AbstractExternalizedFlowExecutionTests

View File

@@ -42,8 +42,10 @@ import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
import org.springframework.webflow.executor.ResponseInstruction;
import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.ResponseInstructionHandler;
/**
* JSF phase listener that is responsible for managing a {@link FlowExecution}
@@ -196,7 +198,7 @@ public class FlowPhaseListener implements PhaseListener {
return null;
}
protected void prepareResponse(JsfExternalContext context, FlowExecutionHolder holder) {
protected void prepareResponse(final JsfExternalContext context, final FlowExecutionHolder holder) {
if (holder.needsSave()) {
generateKey(context, holder);
}
@@ -205,26 +207,38 @@ public class FlowPhaseListener implements PhaseListener {
selectedView = holder.getFlowExecution().refresh(context);
holder.setViewSelection(selectedView);
}
if (selectedView instanceof ApplicationView) {
prepareApplicationView(context.getFacesContext(), holder);
}
else if (selectedView instanceof FlowExecutionRedirect) {
String url = argumentHandler.createFlowExecutionUrl(holder.getFlowExecutionKey().toString(), holder
.getFlowExecution(), context);
sendRedirect(url, context);
}
else if (selectedView instanceof ExternalRedirect) {
String flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey().toString()
: null;
String url = argumentHandler.createExternalUrl((ExternalRedirect) holder.getViewSelection(),
flowExecutionKey, context);
sendRedirect(url, context);
}
else if (selectedView instanceof FlowDefinitionRedirect) {
String url = argumentHandler.createFlowDefinitionUrl((FlowDefinitionRedirect) holder.getViewSelection(),
context);
sendRedirect(url, context);
}
new ResponseInstructionHandler() {
protected void handleApplicationView(ApplicationView view) throws Exception {
prepareApplicationView(context.getFacesContext(), holder);
}
protected void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception {
String url = argumentHandler.createFlowDefinitionUrl((FlowDefinitionRedirect) holder.getViewSelection(),
context);
sendRedirect(url, context);
}
protected void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception {
String url = argumentHandler.createFlowExecutionUrl(holder.getFlowExecutionKey().toString(), holder
.getFlowExecution(), context);
sendRedirect(url, context);
}
protected void handleExternalRedirect(ExternalRedirect redirect) throws Exception {
String flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey().toString()
: null;
String url = argumentHandler.createExternalUrl((ExternalRedirect) holder.getViewSelection(),
flowExecutionKey, context);
sendRedirect(url, context);
}
protected void handleNull() throws Exception {
// nothing to do
}
}.handleQuietly(new ResponseInstruction(holder.getFlowExecution(), selectedView));
}
protected void prepareApplicationView(FacesContext facesContext, FlowExecutionHolder holder) {

View File

@@ -32,12 +32,14 @@ import org.springframework.webflow.context.servlet.ServletExternalContext;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.ResponseInstruction;
import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.FlowRequestHandler;
import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.RequestPathFlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.ResponseInstructionHandler;
/**
* Point of integration between Spring Web MVC and Spring Web Flow: a
@@ -186,41 +188,45 @@ public class FlowController extends AbstractController implements InitializingBe
* Create a ModelAndView object based on the information in the selected
* response instruction. Subclasses can override this to return a
* specialized ModelAndView or to do custom processing on it.
* @param response instruction the response instruction to convert
* @param responseInstruction the response instruction to convert
* @return a new ModelAndView object
*/
protected ModelAndView toModelAndView(ResponseInstruction response, ExternalContext context) {
if (response.isApplicationView()) {
// forward to a view as part of an active conversation
ApplicationView view = (ApplicationView)response.getViewSelection();
Map model = new HashMap(view.getModel());
argumentHandler.exposeFlowExecutionContext(
response.getFlowExecutionKey(), response.getFlowExecutionContext(), model);
return new ModelAndView(view.getViewName(), model);
}
else if (response.isFlowDefinitionRedirect()) {
// restart the flow by redirecting to flow launch URL
String flowUrl = argumentHandler.createFlowDefinitionUrl((FlowDefinitionRedirect)response.getViewSelection(), context);
return new ModelAndView(new RedirectView(flowUrl));
}
else if (response.isFlowExecutionRedirect()) {
// redirect to active flow execution URL
String flowExecutionUrl = argumentHandler.createFlowExecutionUrl(
response.getFlowExecutionKey(), response.getFlowExecutionContext(), context);
return new ModelAndView(new RedirectView(flowExecutionUrl));
}
else if (response.isExternalRedirect()) {
// redirect to external URL
ExternalRedirect redirect = (ExternalRedirect)response.getViewSelection();
String externalUrl = argumentHandler.createExternalUrl(redirect, response.getFlowExecutionKey(), context);
return new ModelAndView(new RedirectView(externalUrl));
}
else if (response.isNull()) {
// no response to issue
return null;
}
else {
throw new IllegalArgumentException("Don't know how to handle response instruction " + response);
}
protected ModelAndView toModelAndView(
final ResponseInstruction responseInstruction, final ExternalContext context) {
return (ModelAndView)new ResponseInstructionHandler() {
protected void handleApplicationView(ApplicationView view) throws Exception {
// forward to a view as part of an active conversation
Map model = new HashMap(view.getModel());
argumentHandler.exposeFlowExecutionContext(responseInstruction.getFlowExecutionKey(),
responseInstruction.getFlowExecutionContext(), model);
setResult(new ModelAndView(view.getViewName(), model));
}
protected void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception {
// restart the flow by redirecting to flow launch URL
String flowUrl = argumentHandler.createFlowDefinitionUrl(redirect, context);
setResult(new ModelAndView(new RedirectView(flowUrl)));
}
protected void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception {
// redirect to active flow execution URL
String flowExecutionUrl = argumentHandler.createFlowExecutionUrl(
responseInstruction.getFlowExecutionKey(),
responseInstruction.getFlowExecutionContext(), context);
setResult(new ModelAndView(new RedirectView(flowExecutionUrl)));
}
protected void handleExternalRedirect(ExternalRedirect redirect) throws Exception {
// redirect to external URL
String externalUrl = argumentHandler.createExternalUrl(redirect,
responseInstruction.getFlowExecutionKey(), context);
setResult(new ModelAndView(new RedirectView(externalUrl)));
}
protected void handleNull() throws Exception {
// no response to issue
setResult(null);
}
}.handleQuietly(responseInstruction).getResult();
}
}

View File

@@ -34,10 +34,12 @@ import org.springframework.webflow.context.portlet.PortletExternalContext;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.ResponseInstruction;
import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.ResponseInstructionHandler;
/**
* Point of integration between Spring Portlet MVC and Spring Web Flow: a
@@ -186,50 +188,59 @@ public class PortletFlowController extends AbstractController implements Initial
}
}
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response) throws Exception {
PortletExternalContext context = new PortletExternalContext(getPortletContext(), request, response);
String flowExecutionKey = argumentHandler.extractFlowExecutionKey(context);
String eventId = argumentHandler.extractEventId(context);
protected void handleActionRequestInternal(final ActionRequest request, final ActionResponse response)
throws Exception {
final PortletExternalContext context = new PortletExternalContext(getPortletContext(), request, response);
final String flowExecutionKey = argumentHandler.extractFlowExecutionKey(context);
final String eventId = argumentHandler.extractEventId(context);
// signal the event against the flow execution, returning the next
// response instruction
ResponseInstruction responseInstruction = flowExecutor.resume(flowExecutionKey, eventId, context);
if (responseInstruction.isApplicationView()) {
// response instruction is a forward to an "application view"
if (responseInstruction.isActiveView()) {
// is an "active" forward from a view-state (not end-state) --
// set the flow execution key render parameter to support
// browser refresh
final ResponseInstruction responseInstruction = flowExecutor.resume(flowExecutionKey, eventId, context);
new ResponseInstructionHandler() {
protected void handleApplicationView(ApplicationView view) throws Exception {
// response instruction is a forward to an "application view"
if (responseInstruction.isActiveView()) {
// is an "active" forward from a view-state (not end-state) --
// set the flow execution key render parameter to support
// browser refresh
response.setRenderParameter(
argumentHandler.getFlowExecutionKeyArgumentName(),
responseInstruction.getFlowExecutionKey());
}
// cache response instruction for access during render phase of this
// portlet
exposeToRenderPhase(responseInstruction, request);
}
protected void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception {
// set flow id render parameter to request that a new flow be
// launched within this portlet
response.setRenderParameters(redirect.getExecutionInput());
response.setRenderParameter(argumentHandler.getFlowIdArgumentName(), redirect.getFlowDefinitionId());
}
protected void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception {
// is a flow execution redirect: simply expose key parameter to
// support refresh during render phase
response.setRenderParameter(
argumentHandler.getFlowExecutionKeyArgumentName(),
responseInstruction.getFlowExecutionKey());
}
// cache response instruction for access during render phase of this
// portlet
exposeToRenderPhase(responseInstruction, request);
}
else if (responseInstruction.isFlowExecutionRedirect()) {
// is a flow execution redirect: simply expose key parameter to
// support refresh during render phase
response.setRenderParameter(
argumentHandler.getFlowExecutionKeyArgumentName(),
responseInstruction.getFlowExecutionKey());
}
else if (responseInstruction.isFlowDefinitionRedirect()) {
// set flow id render parameter to request that a new flow be
// launched within this portlet
FlowDefinitionRedirect redirect = (FlowDefinitionRedirect)responseInstruction.getViewSelection();
response.setRenderParameters(redirect.getExecutionInput());
response.setRenderParameter(argumentHandler.getFlowIdArgumentName(), redirect.getFlowDefinitionId());
}
else if (responseInstruction.isExternalRedirect()) {
// issue the redirect to the external URL
ExternalRedirect redirect = (ExternalRedirect)responseInstruction.getViewSelection();
String url = argumentHandler.createExternalUrl(redirect, flowExecutionKey, context);
response.sendRedirect(url);
}
else {
throw new IllegalArgumentException("Don't know how to handle response instruction " + responseInstruction);
}
protected void handleExternalRedirect(ExternalRedirect redirect) throws Exception {
// issue the redirect to the external URL
String url = argumentHandler.createExternalUrl(redirect, flowExecutionKey, context);
response.sendRedirect(url);
}
protected void handleNull() throws Exception {
// nothing to do
}
}.handle(responseInstruction);
}
// helpers
@@ -269,21 +280,22 @@ public class PortletFlowController extends AbstractController implements Initial
* Convert given response instruction into a Spring Portlet MVC model and
* view.
*/
protected ModelAndView toModelAndView(ResponseInstruction response) {
if (response.isApplicationView()) {
protected ModelAndView toModelAndView(ResponseInstruction responseInstruction) {
if (responseInstruction.isApplicationView()) {
// forward to a view as part of an active conversation
ApplicationView forward = (ApplicationView)response.getViewSelection();
ApplicationView forward = (ApplicationView)responseInstruction.getViewSelection();
Map model = new HashMap(forward.getModel());
argumentHandler.exposeFlowExecutionContext(
response.getFlowExecutionKey(), response.getFlowExecutionContext(), model);
responseInstruction.getFlowExecutionKey(), responseInstruction.getFlowExecutionContext(), model);
return new ModelAndView(forward.getViewName(), model);
}
else if (response.isNull()) {
else if (responseInstruction.isNull()) {
// no response to issue
return null;
}
else {
throw new IllegalArgumentException("Don't know how to handle response instruction " + response);
throw new IllegalArgumentException(
"Don't know how to handle response instruction " + responseInstruction);
}
}
}

View File

@@ -35,11 +35,13 @@ import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.ResponseInstruction;
import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.FlowRequestHandler;
import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
import org.springframework.webflow.executor.support.ResponseInstructionHandler;
/**
* Point of integration between Struts and Spring Web Flow: a Struts Action that
@@ -232,50 +234,52 @@ public class FlowAction extends ActionSupport {
* Return a Struts ActionForward given a ResponseInstruction. Adds all
* attributes from the ResponseInstruction as request attributes.
*/
protected ActionForward toActionForward(ResponseInstruction response, ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse httpResponse, ExternalContext context) throws Exception {
if (response.isApplicationView()) {
// forward to a view as part of an active conversation
ApplicationView forward = (ApplicationView)response.getViewSelection();
Map model = new HashMap(forward.getModel());
argumentHandler.exposeFlowExecutionContext(
response.getFlowExecutionKey(), response.getFlowExecutionContext(), model);
WebUtils.exposeRequestAttributes(request, model);
if (form instanceof SpringBindingActionForm) {
SpringBindingActionForm bindingForm = (SpringBindingActionForm)form;
// expose the form object and associated errors as the
// "current form object" in the request
Errors currentErrors = (Errors)model.get(FormObjectAccessor.getCurrentFormErrorsName());
bindingForm.expose(currentErrors, request);
protected ActionForward toActionForward(final ResponseInstruction responseInstruction,
final ActionMapping mapping, final ActionForm form,
final HttpServletRequest request, final HttpServletResponse response,
final ExternalContext context) throws Exception {
return (ActionForward)new ResponseInstructionHandler() {
protected void handleApplicationView(ApplicationView view) throws Exception {
// forward to a view as part of an active conversation
Map model = new HashMap(view.getModel());
argumentHandler.exposeFlowExecutionContext(
responseInstruction.getFlowExecutionKey(), responseInstruction.getFlowExecutionContext(), model);
WebUtils.exposeRequestAttributes(request, model);
if (form instanceof SpringBindingActionForm) {
SpringBindingActionForm bindingForm = (SpringBindingActionForm)form;
// expose the form object and associated errors as the
// "current form object" in the request
Errors currentErrors = (Errors)model.get(FormObjectAccessor.getCurrentFormErrorsName());
bindingForm.expose(currentErrors, request);
}
setResult(findForward(view, mapping));
}
return findForward(forward, mapping);
}
else if (response.isFlowExecutionRedirect()) {
// redirect to active flow execution URL
String flowExecutionUrl = argumentHandler.createFlowExecutionUrl(
response.getFlowExecutionKey(), response.getFlowExecutionContext(), context);
return createRedirectForward(flowExecutionUrl, httpResponse);
}
else if (response.isFlowDefinitionRedirect()) {
// restart the flow by redirecting to flow launch URL
String flowUrl = argumentHandler.createFlowDefinitionUrl(
(FlowDefinitionRedirect)response.getViewSelection(), context);
return createRedirectForward(flowUrl, httpResponse);
}
else if (response.isExternalRedirect()) {
// redirect to external URL
String externalUrl = argumentHandler.createExternalUrl(
(ExternalRedirect)response.getViewSelection(), response.getFlowExecutionKey(), context);
return createRedirectForward(externalUrl, httpResponse);
}
else if (response.isNull()) {
// no response to issue
return null;
}
else {
throw new IllegalArgumentException("Don't know how to handle response instruction " + response);
}
protected void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception {
// restart the flow by redirecting to flow launch URL
String flowUrl = argumentHandler.createFlowDefinitionUrl(redirect, context);
setResult(createRedirectForward(flowUrl, response));
}
protected void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception {
// redirect to active flow execution URL
String flowExecutionUrl = argumentHandler.createFlowExecutionUrl(
responseInstruction.getFlowExecutionKey(), responseInstruction.getFlowExecutionContext(), context);
setResult(createRedirectForward(flowExecutionUrl, response));
}
protected void handleExternalRedirect(ExternalRedirect redirect) throws Exception {
// redirect to external URL
String externalUrl = argumentHandler.createExternalUrl(
redirect, responseInstruction.getFlowExecutionKey(), context);
setResult(createRedirectForward(externalUrl, response));
}
protected void handleNull() throws Exception {
// no response to issue
setResult(null);
}
}.handle(responseInstruction).getResult();
}
/**

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2004-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.executor.support;
import org.springframework.webflow.execution.ViewSelection;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
import org.springframework.webflow.executor.ResponseInstruction;
/**
* Abstract helper class that allows easy handling of all known view
* selection types. Users need to implement each of the hook methods
* dealing with a particular type of view selection, typically in an
* anonymous inner subclass of this class.
*
* @see ViewSelection
*
* @author Erwin Vervaet
*/
public abstract class ResponseInstructionHandler {
private Object result;
/**
* Set the object resulting from response handling.
* This is optional.
* @param result the result object
*/
public void setResult(Object result) {
this.result = result;
}
/**
* Returns the object resulting from response handling.
* This is optional and will only be set if the subclass
* calls {@link #setResult(Object)} to set the result object.
* @return the result object, or null if none
*/
public Object getResult() {
return result;
}
/**
* Issue a response for given response instruction. Will delegate to
* any of the available hook methods depending on the type of view selection
* contained in the response instruction.
* @param responseInstruction the response instruction to issue a response for
* @return this object, for call chaining
* @throws Exception when an error occured
*/
public final ResponseInstructionHandler handle(ResponseInstruction responseInstruction) throws Exception {
if (responseInstruction.isApplicationView()) {
handleApplicationView((ApplicationView)responseInstruction.getViewSelection());
}
else if (responseInstruction.isFlowDefinitionRedirect()) {
handleFlowDefinitionRedirect((FlowDefinitionRedirect)responseInstruction.getViewSelection());
}
else if (responseInstruction.isFlowExecutionRedirect()) {
handleFlowExecutionRedirect((FlowExecutionRedirect)responseInstruction.getViewSelection());
}
else if (responseInstruction.isExternalRedirect()) {
handleExternalRedirect((ExternalRedirect)responseInstruction.getViewSelection());
}
else if (responseInstruction.isNull()) {
handleNull();
}
else {
throw new IllegalArgumentException(
"Don't know how to handle response instruction " + responseInstruction);
}
return this;
}
/**
* Quietly issue a response for given response instruction, turning any Exception
* raised while handling the response instruction into a RuntimeException.
* Will delegate to any of the available hook methods depending on the type of view selection
* contained in the response instruction.
* @param responseInstruction the response instruction to issue a response for
* @return this object, for call chaining
*/
public final ResponseInstructionHandler handleQuietly(ResponseInstruction responseInstruction) {
try {
return handle(responseInstruction);
}
catch (Exception e) {
throw new RuntimeException(
"Unexpected exception handling response instruction " + responseInstruction, e);
}
}
// template methods
/**
* Issue a response for given application view.
* @param view the application view to issue a response for
* @throws Exception when an error occured
* @see ResponseInstruction#isActiveView()
* @see ApplicationView
*/
protected abstract void handleApplicationView(ApplicationView view) throws Exception;
/**
* Issue a response for given flow definition redirect.
* @param redirect the flow definition redirect to issue a response for
* @throws Exception when an error occured
* @see ResponseInstruction#isFlowDefinitionRedirect()
* @see FlowDefinitionRedirect
*/
protected abstract void handleFlowDefinitionRedirect(FlowDefinitionRedirect redirect) throws Exception;
/**
* Issue a response for given flow execution redirect.
* @param redirect the flow execution redirect to issue a response for
* @throws Exception when an error occured
* @see ResponseInstruction#isFlowExecutionRedirect()
* @see FlowExecutionRedirect
*/
protected abstract void handleFlowExecutionRedirect(FlowExecutionRedirect redirect) throws Exception;
/**
* Issue a response for given external redirect.
* @param redirect the external redirect to issue a response for
* @throws Exception when an error occured
* @see ResponseInstruction#isExternalRedirect()
* @see ExternalRedirect
*/
protected abstract void handleExternalRedirect(ExternalRedirect redirect) throws Exception;
/**
* Issue a respone for the null view selection.
* @throws Exception
* @see ResponseInstruction#isNull()
* @see ViewSelection#NULL_VIEW
*/
protected abstract void handleNull() throws Exception;
}