001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.webflow.executor.mvc;
017:
018: import java.util.HashMap;
019: import java.util.Map;
020:
021: import javax.servlet.http.HttpServletRequest;
022: import javax.servlet.http.HttpServletResponse;
023:
024: import org.springframework.beans.factory.InitializingBean;
025: import org.springframework.util.Assert;
026: import org.springframework.web.servlet.ModelAndView;
027: import org.springframework.web.servlet.mvc.AbstractController;
028: import org.springframework.web.servlet.mvc.Controller;
029: import org.springframework.web.servlet.view.RedirectView;
030: import org.springframework.webflow.context.ExternalContext;
031: import org.springframework.webflow.context.servlet.ServletExternalContext;
032: import org.springframework.webflow.execution.support.ApplicationView;
033: import org.springframework.webflow.execution.support.ExternalRedirect;
034: import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
035: import org.springframework.webflow.execution.support.FlowExecutionRedirect;
036: import org.springframework.webflow.executor.FlowExecutor;
037: import org.springframework.webflow.executor.ResponseInstruction;
038: import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
039: import org.springframework.webflow.executor.support.FlowRequestHandler;
040: import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
041: import org.springframework.webflow.executor.support.RequestPathFlowExecutorArgumentHandler;
042: import org.springframework.webflow.executor.support.ResponseInstructionHandler;
043:
044: /**
045: * Point of integration between Spring Web MVC and Spring Web Flow: a
046: * {@link Controller} that routes incoming requests to one or more managed flow
047: * executions.
048: * <p>
049: * Requests into the web flow system are handled by a {@link FlowExecutor},
050: * which this class delegates to using a {@link FlowRequestHandler} helper.
051: * Consult the JavaDoc of that class for more information on how requests are
052: * processed.
053: * <p>
054: * Note: a single <code>FlowController</code> may execute all flows of your application.
055: * <ul>
056: * <li>By default, to have this controller launch a new flow execution
057: * (conversation), have the client send a
058: * {@link FlowExecutorArgumentHandler#getFlowIdArgumentName()} request
059: * parameter indicating the flow definition to launch.
060: * <li>To have this controller participate in an existing flow execution
061: * (conversation), have the client send a
062: * {@link FlowExecutorArgumentHandler#getFlowExecutionKeyArgumentName()}
063: * request parameter identifying the conversation to participate in.
064: * See the <code>flow-launcher</code> sample application for examples of the
065: * various strategies for launching and resuming flow executions.
066: * </ul>
067: * <p>
068: * Usage example:
069: * <pre>
070: * <!--
071: * Exposes flows for execution at a single request URL.
072: * The id of a flow to launch should be passed in by clients using
073: * the "_flowId" request parameter:
074: * e.g. /app.htm?_flowId=flow1
075: * -->
076: * <bean name="/app.htm" class="org.springframework.webflow.executor.mvc.FlowController">
077: * <property name="flowExecutor" ref="flowExecutor"/>
078: * </bean>
079: * </pre>
080: * <p>
081: * It is also possible to customize the {@link FlowExecutorArgumentHandler}
082: * strategy to allow for different types of controller parameterization, for
083: * example perhaps in conjunction with a REST-style request mapper (see
084: * {@link RequestPathFlowExecutorArgumentHandler}).
085: *
086: * @see org.springframework.webflow.executor.FlowExecutor
087: * @see org.springframework.webflow.executor.support.FlowRequestHandler
088: * @see org.springframework.webflow.executor.support.FlowExecutorArgumentHandler
089: *
090: * @author Erwin Vervaet
091: * @author Keith Donald
092: */
093: public class FlowController extends AbstractController implements
094: InitializingBean {
095:
096: /**
097: * The facade for executing flows (launching new executions, and resuming
098: * existing executions).
099: */
100: private FlowExecutor flowExecutor;
101:
102: /**
103: * The strategy for handling flow executor parameters.
104: */
105: private FlowExecutorArgumentHandler argumentHandler = new RequestParameterFlowExecutorArgumentHandler();
106:
107: /**
108: * Create a new flow controller. Allows bean style usage.
109: * @see #setFlowExecutor(FlowExecutor)
110: * @see #setArgumentHandler(FlowExecutorArgumentHandler)
111: */
112: public FlowController() {
113: // set the cache seconds property to 0 so no pages are cached by default
114: // for flows.
115: setCacheSeconds(0);
116: }
117:
118: /**
119: * Returns the flow executor used by this controller.
120: * @return the flow executor
121: */
122: public FlowExecutor getFlowExecutor() {
123: return flowExecutor;
124: }
125:
126: /**
127: * Sets the flow executor to use; setting this property is required.
128: * @param flowExecutor the fully configured flow executor to use
129: */
130: public void setFlowExecutor(FlowExecutor flowExecutor) {
131: this .flowExecutor = flowExecutor;
132: }
133:
134: /**
135: * Returns the flow executor argument handler used by this controller.
136: * Defaults to {@link RequestParameterFlowExecutorArgumentHandler}.
137: * @return the argument handler
138: */
139: public FlowExecutorArgumentHandler getArgumentHandler() {
140: return argumentHandler;
141: }
142:
143: /**
144: * Sets the flow executor argument handler to use. The default is
145: * {@link RequestParameterFlowExecutorArgumentHandler}.
146: * @param argumentHandler the fully configured argument handler
147: */
148: public void setArgumentHandler(
149: FlowExecutorArgumentHandler argumentHandler) {
150: this .argumentHandler = argumentHandler;
151: }
152:
153: /**
154: * Sets the identifier of the default flow to launch if no flowId argument
155: * can be extracted by the configured {@link FlowExecutorArgumentHandler}
156: * during request processing.
157: * <p>
158: * This is a convenience method that sets the default flow id of the
159: * controller's argument handler. Don't use this when using
160: * {@link #setArgumentHandler(FlowExecutorArgumentHandler)}.
161: */
162: public void setDefaultFlowId(String defaultFlowId) {
163: this .argumentHandler.setDefaultFlowId(defaultFlowId);
164: }
165:
166: public void afterPropertiesSet() {
167: Assert.notNull(flowExecutor,
168: "The flow executor property is required");
169: Assert.notNull(argumentHandler,
170: "The argument handler property is required");
171: }
172:
173: protected ModelAndView handleRequestInternal(
174: HttpServletRequest request, HttpServletResponse response)
175: throws Exception {
176: ServletExternalContext context = new ServletExternalContext(
177: getServletContext(), request, response);
178: ResponseInstruction responseInstruction = createRequestHandler()
179: .handleFlowRequest(context);
180: return toModelAndView(responseInstruction, context);
181: }
182:
183: /**
184: * Factory method that creates a new helper for processing a request into
185: * this flow controller. The handler is a basic template encapsulating
186: * reusable flow execution request handling workflow.
187: * This implementation just creates a new {@link FlowRequestHandler}.
188: * @return the controller helper
189: */
190: protected FlowRequestHandler createRequestHandler() {
191: return new FlowRequestHandler(getFlowExecutor(),
192: getArgumentHandler());
193: }
194:
195: /**
196: * Create a ModelAndView object based on the information in the selected
197: * response instruction. Subclasses can override this to return a
198: * specialized ModelAndView or to do custom processing on it.
199: * @param responseInstruction the response instruction to convert
200: * @return a new ModelAndView object
201: */
202: protected ModelAndView toModelAndView(
203: final ResponseInstruction responseInstruction,
204: final ExternalContext context) {
205: return (ModelAndView) new ResponseInstructionHandler() {
206: protected void handleApplicationView(ApplicationView view)
207: throws Exception {
208: // forward to a view as part of an active conversation
209: Map model = new HashMap(view.getModel());
210: argumentHandler.exposeFlowExecutionContext(
211: responseInstruction.getFlowExecutionKey(),
212: responseInstruction.getFlowExecutionContext(),
213: model);
214: setResult(new ModelAndView(view.getViewName(), model));
215: }
216:
217: protected void handleFlowDefinitionRedirect(
218: FlowDefinitionRedirect redirect) throws Exception {
219: // restart the flow by redirecting to flow launch URL
220: String flowUrl = argumentHandler
221: .createFlowDefinitionUrl(redirect, context);
222: setResult(new ModelAndView(new RedirectView(flowUrl)));
223: }
224:
225: protected void handleFlowExecutionRedirect(
226: FlowExecutionRedirect redirect) throws Exception {
227: // redirect to active flow execution URL
228: String flowExecutionUrl = argumentHandler
229: .createFlowExecutionUrl(responseInstruction
230: .getFlowExecutionKey(),
231: responseInstruction
232: .getFlowExecutionContext(),
233: context);
234: setResult(new ModelAndView(new RedirectView(
235: flowExecutionUrl)));
236: }
237:
238: protected void handleExternalRedirect(
239: ExternalRedirect redirect) throws Exception {
240: // redirect to external URL
241: String externalUrl = argumentHandler.createExternalUrl(
242: redirect, responseInstruction
243: .getFlowExecutionKey(), context);
244: setResult(new ModelAndView(
245: new RedirectView(externalUrl)));
246: }
247:
248: protected void handleNull() throws Exception {
249: // no response to issue
250: setResult(null);
251: }
252: }.handleQuietly(responseInstruction).getResult();
253: }
254: }
|