diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java index f77bd46e..5ef78040 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/FlowHandler.java @@ -66,7 +66,7 @@ public interface FlowHandler { /** * Handles a flow exception that was not handled by the Web Flow system. Used by a Controller to handle a specific * type of exception dealing with this flow in a custom manner. - * @param e the unhandled exception orignating from Spring Web Flow. May be thrown by the flow execution itself or + * @param e the unhandled exception originating from Spring Web Flow. May be thrown by the flow execution itself or * the flow executor system if no execution could be restored. * @param request the current request * @param response the current response diff --git a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java index 5fded1da..350af5a1 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java @@ -20,6 +20,7 @@ import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.springframework.web.portlet.ModelAndView; +import org.springframework.webflow.core.FlowException; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; @@ -51,7 +52,26 @@ public interface FlowHandler { */ public MutableAttributeMap createExecutionInputMap(PortletRequest request); - public ModelAndView handleFlowOutcome(String endedOutcome, AttributeMap endedOutput, RenderRequest request, + /** + * Handles a specific flow execution outcome. Used to select a new view to render after the flow ends. + * @param outcome the outcome that was reached + * @param output the output returned by the flow execution + * @param request the current render request + * @param response the current render response + * @return the model and view to render on the occurrence of this outcome, or null if the outcome was not handled + */ + public ModelAndView handleFlowOutcome(String outcome, AttributeMap output, RenderRequest request, RenderResponse response); + /** + * Handles a flow exception that was not handled by the Web Flow system. Used by a Controller to handle a specific + * type of exception dealing with this flow in a custom manner. + * @param e the unhandled exception originating from Spring Web Flow. May be thrown by the flow execution itself or + * the flow executor system if no execution could be restored. + * @param request the current request + * @param response the current response + * @return the model and view to render on the occurrence of this exception, or null if the exception is not handled + */ + public ModelAndView handleException(FlowException e, RenderRequest request, RenderResponse response); + } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java index d7bbf9c9..5496b6fd 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java @@ -1,5 +1,9 @@ package org.springframework.webflow.portlet; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; + import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.PortletRequest; @@ -13,13 +17,19 @@ import org.springframework.web.portlet.ModelAndView; import org.springframework.web.portlet.context.PortletApplicationObjectSupport; import org.springframework.webflow.context.portlet.FlowUrlHandler; import org.springframework.webflow.context.portlet.PortletExternalContext; +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; public class FlowHandlerAdapter extends PortletApplicationObjectSupport implements HandlerAdapter { - private static final String FLOW_EXECUTION_RESULT = "flowExecutionResult"; + private static final String ACTION_FLOW_EXCEPTION_ATTRIBUTE = "actionFlowException"; + + private static final String FLOW_EXECUTION_RESULT_ATTRIBUTE = "flowExecutionResult"; private FlowExecutor flowExecutor; @@ -29,25 +39,46 @@ public class FlowHandlerAdapter extends PortletApplicationObjectSupport implemen this.flowExecutor = flowExecutor; } + public boolean supports(Object handler) { + return handler instanceof FlowHandler; + } + public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception { FlowHandler flowHandler = (FlowHandler) handler; + PortletSession session = request.getPortletSession(false); + if (session != null) { + FlowException e = (FlowException) session.getAttribute(ACTION_FLOW_EXCEPTION_ATTRIBUTE); + if (e != null) { + session.removeAttribute(ACTION_FLOW_EXCEPTION_ATTRIBUTE); + ModelAndView mv = flowHandler.handleException(e, request, response); + return mv != null ? mv : defaultHandleResumeFlowException(flowHandler, e, request, response); + } + } String flowExecutionKey = urlHandler.getFlowExecutionKey(request); if (flowExecutionKey != null) { PortletExternalContext context = createPortletExternalContext(request, response); - flowExecutor.resumeExecution(flowExecutionKey, context); - return null; - } else { - PortletSession session = request.getPortletSession(true); - FlowExecutionResult result = (FlowExecutionResult) session.getAttribute(FLOW_EXECUTION_RESULT); - if (result != null) { - session.removeAttribute(FLOW_EXECUTION_RESULT); - return flowHandler.handleFlowOutcome(result.getEndedOutcome(), result.getEndedOutput(), request, - response); - } else { - MutableAttributeMap input = flowHandler.createExecutionInputMap(request); - PortletExternalContext context = createPortletExternalContext(request, response); - flowExecutor.launchExecution(flowHandler.getFlowId(), input, context); + try { + flowExecutor.resumeExecution(flowExecutionKey, context); return null; + } catch (FlowException e) { + ModelAndView mv = flowHandler.handleException(e, request, response); + return mv != null ? mv : defaultHandleResumeFlowException(flowHandler, e, request, response); + } + } else { + if (session != null) { + FlowExecutionResult result = (FlowExecutionResult) session + .getAttribute(FLOW_EXECUTION_RESULT_ATTRIBUTE); + if (result != null) { + session.removeAttribute(FLOW_EXECUTION_RESULT_ATTRIBUTE); + String outcome = result.getEndedOutcome(); + AttributeMap output = result.getEndedOutput(); + ModelAndView mv = flowHandler.handleFlowOutcome(outcome, output, request, response); + return mv != null ? mv : defaultHandleFlowOutcome(flowHandler, outcome, output, request, response); + } else { + return startFlow(request, response, flowHandler); + } + } else { + return startFlow(request, response, flowHandler); } } } @@ -55,20 +86,85 @@ public class FlowHandlerAdapter extends PortletApplicationObjectSupport implemen public void handleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception { String flowExecutionKey = urlHandler.getFlowExecutionKey(request); PortletExternalContext context = createPortletExternalContext(request, response); - FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); - if (result.paused()) { - urlHandler.setFlowExecutionRenderParameter(flowExecutionKey, response); + try { + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + if (result.paused()) { + urlHandler.setFlowExecutionRenderParameter(flowExecutionKey, response); + } else { + setEndResult(result, request); + } + } catch (FlowException e) { + request.getPortletSession().setAttribute(ACTION_FLOW_EXCEPTION_ATTRIBUTE, e); + } + } + + // subclassing hooks + + protected MutableAttributeMap defaultFlowExecutionInputMap(PortletRequest request) { + LocalAttributeMap inputMap = new LocalAttributeMap(); + Map parameterMap = request.getParameterMap(); + Iterator it = parameterMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + String name = (String) entry.getKey(); + String[] values = (String[]) entry.getValue(); + if (values.length == 1) { + inputMap.put(name, values[0]); + } else { + inputMap.put(name, values); + } + } + return inputMap; + } + + protected ModelAndView defaultHandleFlowOutcome(FlowHandler flowHandler, String outcome, AttributeMap output, + RenderRequest request, RenderResponse response) throws IOException { + // by default, just start the flow over passing the output as input + String flowId = flowHandler.getFlowId(); + if (logger.isDebugEnabled()) { + logger.debug("Restarting a new execution of ended flow '" + flowId + "'"); + } + PortletExternalContext context = createPortletExternalContext(request, response); + flowExecutor.launchExecution(flowId, new LocalAttributeMap(output.asMap()), context); + return null; + } + + protected ModelAndView defaultHandleResumeFlowException(FlowHandler flowHandler, FlowException e, + RenderRequest request, RenderResponse response) throws IOException { + if (e instanceof NoSuchFlowExecutionException) { + String flowId = flowHandler.getFlowId(); + if (logger.isDebugEnabled()) { + logger.debug("Restarting a new execution of previously expired/ended flow '" + flowId + "'"); + } + // by default, attempt to restart the flow + PortletExternalContext context = createPortletExternalContext(request, response); + flowExecutor.launchExecution(flowId, null, context); + return null; } else { - setEndResult(result, request); + throw e; + } + } + + // helpers + + private ModelAndView startFlow(RenderRequest request, RenderResponse response, FlowHandler flowHandler) { + MutableAttributeMap input = flowHandler.createExecutionInputMap(request); + PortletExternalContext context = createPortletExternalContext(request, response); + try { + flowExecutor.launchExecution(flowHandler.getFlowId(), input, context); + return null; + } catch (FlowException e) { + ModelAndView mv = flowHandler.handleException(e, request, response); + if (mv != null) { + return mv; + } else { + throw e; + } } } private void setEndResult(FlowExecutionResult result, ActionRequest request) { - request.getPortletSession().setAttribute(FLOW_EXECUTION_RESULT, result); - } - - public boolean supports(Object handler) { - return handler instanceof FlowHandler; + request.getPortletSession().setAttribute(FLOW_EXECUTION_RESULT_ATTRIBUTE, result); } private PortletExternalContext createPortletExternalContext(PortletRequest request, PortletResponse response) {