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;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.springframework.binding.mapping.AttributeMapper;
021: import org.springframework.util.Assert;
022: import org.springframework.webflow.context.ExternalContext;
023: import org.springframework.webflow.context.ExternalContextHolder;
024: import org.springframework.webflow.core.FlowException;
025: import org.springframework.webflow.core.collection.LocalAttributeMap;
026: import org.springframework.webflow.core.collection.MutableAttributeMap;
027: import org.springframework.webflow.definition.FlowDefinition;
028: import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
029: import org.springframework.webflow.execution.FlowExecution;
030: import org.springframework.webflow.execution.FlowExecutionFactory;
031: import org.springframework.webflow.execution.ViewSelection;
032: import org.springframework.webflow.execution.repository.FlowExecutionKey;
033: import org.springframework.webflow.execution.repository.FlowExecutionLock;
034: import org.springframework.webflow.execution.repository.FlowExecutionRepository;
035:
036: /**
037: * The default implementation of the central facade for <i>driving</i> the
038: * execution of flows within an application.
039: * <p>
040: * This object is responsible for creating and starting new flow executions as
041: * requested by clients, as well as signaling events for processing by existing,
042: * paused executions (that are waiting to be resumed in response to a user
043: * event).
044: * <p>
045: * This object is a facade or entry point into the Spring Web Flow execution
046: * system and makes the overall system easier to use. The name <i>executor</i>
047: * was chosen as <i>executors drive executions</i>.
048: * <p>
049: * <b>Commonly used configurable properties</b><br>
050: * <table border="1">
051: * <tr>
052: * <td><b>name</b></td>
053: * <td><b>description</b></td>
054: * <td><b>default</b></td>
055: * </tr>
056: * <tr>
057: * <td>definitionLocator</td>
058: * <td>The service locator responsible for loading flow definitions to execute.</td>
059: * <td>None</td>
060: * </tr>
061: * <tr>
062: * <td>executionFactory</td>
063: * <td>The factory responsible for creating new flow executions.</td>
064: * <td>None</td>
065: * </tr>
066: * <tr>
067: * <td>executionRepository</td>
068: * <td>The repository responsible for managing flow execution persistence.</td>
069: * <td>None</td>
070: * </tr>
071: * <tr>
072: * <td>inputMapper</td>
073: * <td>The service responsible for mapping attributes of
074: * {@link ExternalContext external contexts} that request to launch new
075: * {@link FlowExecution flow executions}.
076: * After mapping, the target map is then passed to the FlowExecution, exposing
077: * external context attributes as input to the flow during startup.</td>
078: * <td>A
079: * {@link org.springframework.webflow.executor.RequestParameterInputMapper request parameter mapper},
080: * which exposes all request parameters in to the flow execution for input
081: * mapping.</td>
082: * </tr>
083: * </table>
084: * </p>
085: *
086: * @see FlowDefinitionLocator
087: * @see FlowExecutionFactory
088: * @see FlowExecutionRepository
089: * @see AttributeMapper
090: *
091: * @author Erwin Vervaet
092: * @author Keith Donald
093: * @author Colin Sampaleanu
094: */
095: public class FlowExecutorImpl implements FlowExecutor {
096:
097: private static final Log logger = LogFactory
098: .getLog(FlowExecutorImpl.class);
099:
100: /**
101: * A locator to access flow definitions registered in a central registry.
102: */
103: private FlowDefinitionLocator definitionLocator;
104:
105: /**
106: * An abstract factory for creating a new execution of a flow definition.
107: */
108: private FlowExecutionFactory executionFactory;
109:
110: /**
111: * An repository used to save, update, and load existing flow executions
112: * to/from a persistent store.
113: */
114: private FlowExecutionRepository executionRepository;
115:
116: /**
117: * The service responsible for mapping attributes of an
118: * {@link ExternalContext} to a new {@link FlowExecution} during the
119: * {@link #launch(String, ExternalContext) launch flow} operation.
120: * <p>
121: * This allows developers to control what attributes are made available in
122: * the <code>inputMap</code> to new top-level flow executions. The
123: * starting execution may then choose to map that available input into its
124: * own local scope.
125: * <p>
126: * The default implementation simply exposes all request parameters as flow
127: * execution input attributes. May be null.
128: */
129: private AttributeMapper inputMapper = new RequestParameterInputMapper();
130:
131: /**
132: * Create a new flow executor.
133: * @param definitionLocator the locator for accessing flow definitions to
134: * execute
135: * @param executionFactory the factory for creating executions of flow
136: * definitions
137: * @param executionRepository the repository for persisting paused flow
138: * executions
139: */
140: public FlowExecutorImpl(FlowDefinitionLocator definitionLocator,
141: FlowExecutionFactory executionFactory,
142: FlowExecutionRepository executionRepository) {
143: Assert
144: .notNull(definitionLocator,
145: "The locator for accessing flow definitions is required");
146: Assert
147: .notNull(executionFactory,
148: "The execution factory for creating new flow executions is required");
149: Assert
150: .notNull(executionRepository,
151: "The repository for persisting flow executions is required");
152: this .definitionLocator = definitionLocator;
153: this .executionFactory = executionFactory;
154: this .executionRepository = executionRepository;
155: }
156:
157: /**
158: * Exposes the configured input mapper to subclasses and privileged
159: * accessors.
160: * @return the input mapper
161: */
162: public AttributeMapper getInputMapper() {
163: return inputMapper;
164: }
165:
166: /**
167: * Set the service responsible for mapping attributes of an
168: * {@link ExternalContext} to a new {@link FlowExecution} during the
169: * {@link #launch(String, ExternalContext) launch flow} operation.
170: * <p>
171: * The default implementation simply exposes all request parameters as flow
172: * execution input attributes. May be null.
173: * @see RequestParameterInputMapper
174: */
175: public void setInputMapper(AttributeMapper inputMapper) {
176: this .inputMapper = inputMapper;
177: }
178:
179: /**
180: * Exposes the configured flow definition locator to subclasses and
181: * privileged accessors.
182: * @return the flow definition locator
183: */
184: public FlowDefinitionLocator getDefinitionLocator() {
185: return definitionLocator;
186: }
187:
188: /**
189: * Exposes the configured execution factory to subclasses and privileged
190: * accessors.
191: * @return the execution factory
192: */
193: public FlowExecutionFactory getExecutionFactory() {
194: return executionFactory;
195: }
196:
197: /**
198: * Exposes the execution repository to subclasses and privileged accessors.
199: * @return the execution repository
200: */
201: public FlowExecutionRepository getExecutionRepository() {
202: return executionRepository;
203: }
204:
205: public ResponseInstruction launch(String flowDefinitionId,
206: ExternalContext context) throws FlowException {
207: if (logger.isDebugEnabled()) {
208: logger
209: .debug("Launching flow execution for flow definition '"
210: + flowDefinitionId + "'");
211: }
212: // expose external context as a thread-bound service
213: ExternalContextHolder.setExternalContext(context);
214: try {
215: FlowDefinition flowDefinition = definitionLocator
216: .getFlowDefinition(flowDefinitionId);
217: FlowExecution flowExecution = executionFactory
218: .createFlowExecution(flowDefinition);
219: ViewSelection selectedView = flowExecution.start(
220: createInput(context), context);
221: if (flowExecution.isActive()) {
222: // execution still active => store it in the repository
223: FlowExecutionKey key = executionRepository
224: .generateKey(flowExecution);
225: FlowExecutionLock lock = executionRepository
226: .getLock(key);
227: lock.lock();
228: try {
229: executionRepository.putFlowExecution(key,
230: flowExecution);
231: } finally {
232: lock.unlock();
233: }
234: return new ResponseInstruction(key.toString(),
235: flowExecution, selectedView);
236: } else {
237: // execution already ended => just render the selected view
238: return new ResponseInstruction(flowExecution,
239: selectedView);
240: }
241: } finally {
242: ExternalContextHolder.setExternalContext(null);
243: }
244: }
245:
246: public ResponseInstruction resume(String flowExecutionKey,
247: String eventId, ExternalContext context)
248: throws FlowException {
249: if (logger.isDebugEnabled()) {
250: logger.debug("Resuming flow execution with key '"
251: + flowExecutionKey + "' on user event '" + eventId
252: + "'");
253: }
254: // expose external context as a thread-bound service
255: ExternalContextHolder.setExternalContext(context);
256: try {
257: FlowExecutionKey key = executionRepository
258: .parseFlowExecutionKey(flowExecutionKey);
259: FlowExecutionLock lock = executionRepository.getLock(key);
260: // make sure we're the only one manipulating the flow execution
261: lock.lock();
262: try {
263: FlowExecution flowExecution = executionRepository
264: .getFlowExecution(key);
265: ViewSelection selectedView = flowExecution.signalEvent(
266: eventId, context);
267: if (flowExecution.isActive()) {
268: // execution still active => store it in the repository
269: key = executionRepository.getNextKey(flowExecution,
270: key);
271: executionRepository.putFlowExecution(key,
272: flowExecution);
273: return new ResponseInstruction(key.toString(),
274: flowExecution, selectedView);
275: } else {
276: // execution ended => remove it from the repository
277: executionRepository.removeFlowExecution(key);
278: return new ResponseInstruction(flowExecution,
279: selectedView);
280: }
281: } finally {
282: lock.unlock();
283: }
284: } finally {
285: ExternalContextHolder.setExternalContext(null);
286: }
287: }
288:
289: public ResponseInstruction refresh(String flowExecutionKey,
290: ExternalContext context) throws FlowException {
291: if (logger.isDebugEnabled()) {
292: logger.debug("Refreshing flow execution with key '"
293: + flowExecutionKey + "'");
294: }
295: // expose external context as a thread-bound service
296: ExternalContextHolder.setExternalContext(context);
297: try {
298: FlowExecutionKey key = executionRepository
299: .parseFlowExecutionKey(flowExecutionKey);
300: FlowExecutionLock lock = executionRepository.getLock(key);
301: // make sure we're the only one manipulating the flow execution
302: lock.lock();
303: try {
304: FlowExecution flowExecution = executionRepository
305: .getFlowExecution(key);
306: ViewSelection selectedView = flowExecution
307: .refresh(context);
308: // don't generate a new key for a refresh, just update
309: // the flow execution with it's existing key
310: executionRepository
311: .putFlowExecution(key, flowExecution);
312: return new ResponseInstruction(key.toString(),
313: flowExecution, selectedView);
314: } finally {
315: lock.unlock();
316: }
317: } finally {
318: ExternalContextHolder.setExternalContext(null);
319: }
320: }
321:
322: // helper methods
323:
324: /**
325: * Factory method that creates the input attribute map for a newly created
326: * {@link FlowExecution}. This implementation uses the registered input mapper,
327: * if any.
328: * @param context the external context
329: * @return the input map, or null if no input
330: */
331: protected MutableAttributeMap createInput(ExternalContext context) {
332: if (inputMapper != null) {
333: MutableAttributeMap inputMap = new LocalAttributeMap();
334: inputMapper.map(context, inputMap, null);
335: return inputMap;
336: } else {
337: return null;
338: }
339: }
340: }
|