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.config;
017:
018: import java.util.Map;
019:
020: import org.springframework.beans.factory.FactoryBean;
021: import org.springframework.beans.factory.InitializingBean;
022: import org.springframework.binding.mapping.AttributeMapper;
023: import org.springframework.util.Assert;
024: import org.springframework.webflow.context.ExternalContext;
025: import org.springframework.webflow.conversation.ConversationManager;
026: import org.springframework.webflow.conversation.impl.SessionBindingConversationManager;
027: import org.springframework.webflow.core.collection.AttributeMap;
028: import org.springframework.webflow.core.collection.LocalAttributeMap;
029: import org.springframework.webflow.core.collection.MutableAttributeMap;
030: import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
031: import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
032: import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
033: import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer;
034: import org.springframework.webflow.execution.FlowExecution;
035: import org.springframework.webflow.execution.FlowExecutionFactory;
036: import org.springframework.webflow.execution.FlowExecutionListener;
037: import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
038: import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
039: import org.springframework.webflow.execution.repository.FlowExecutionRepository;
040: import org.springframework.webflow.execution.repository.continuation.ClientContinuationFlowExecutionRepository;
041: import org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepository;
042: import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
043: import org.springframework.webflow.execution.repository.support.SimpleFlowExecutionRepository;
044: import org.springframework.webflow.executor.FlowExecutor;
045: import org.springframework.webflow.executor.FlowExecutorImpl;
046:
047: /**
048: * The default flow executor factory implementation. As a <code>FactoryBean</code>,
049: * this class has been designed for use as a Spring managed bean.
050: * <p>
051: * This factory encapsulates the construction and assembly of a
052: * {@link FlowExecutor}, including the provision of its
053: * {@link FlowExecutionRepository} strategy.
054: * <p>
055: * The {@link #setDefinitionLocator(FlowDefinitionLocator) definition locator}
056: * property is required, all other properties are optional.
057: * <p>
058: * This class has been designed with subclassing in mind. If you want to do advanced
059: * Spring Web Flow customization, e.g. using a custom
060: * {@link org.springframework.webflow.executor.FlowExecutor} implementation,
061: * consider subclassing this class and overriding one or more of the provided
062: * hook methods.
063: *
064: * @author Keith Donald
065: * @author Erwin Vervaet
066: */
067: public class FlowExecutorFactoryBean implements FactoryBean,
068: InitializingBean {
069:
070: /**
071: * The locator the executor will use to access flow definitions registered
072: * in a central registry. Required.
073: */
074: private FlowDefinitionLocator definitionLocator;
075:
076: /**
077: * Execution attributes to apply.
078: */
079: private MutableAttributeMap executionAttributes;
080:
081: /**
082: * The loader that will determine which listeners to attach to flow definition executions.
083: */
084: private FlowExecutionListenerLoader executionListenerLoader;
085:
086: /**
087: * The conversation manager to be used by the flow execution repository to
088: * store state associated with conversations driven by Spring Web Flow.
089: */
090: private ConversationManager conversationManager;
091:
092: /**
093: * The maximum number of allowed concurrent conversations in the session.
094: */
095: private Integer maxConversations;
096:
097: /**
098: * The type of execution repository to configure with executors created by
099: * this factory. Optional. Will fallback to default value if not set.
100: */
101: private RepositoryType repositoryType;
102:
103: /**
104: * The maximum number of allowed continuations for a single conversation.
105: * Only used when the repository type is {@link RepositoryType#CONTINUATION}.
106: */
107: private Integer maxContinuations;
108:
109: /**
110: * A custom attribute mapper to use for mapping attributes of an
111: * {@link ExternalContext} to a new {@link FlowExecution} during the
112: * {@link FlowExecutor#launch(String, ExternalContext) launch flow} operation.
113: */
114: private AttributeMapper inputMapper;
115:
116: /**
117: * The flow executor this factory bean creates.
118: */
119: private FlowExecutor flowExecutor;
120:
121: /**
122: * Spring Web Flow executor system defaults.
123: */
124: private FlowSystemDefaults defaults = new FlowSystemDefaults();
125:
126: /**
127: * Sets the flow definition locator that will locate flow definitions needed
128: * for execution. Typically also a {@link FlowDefinitionRegistry}. Required.
129: * @param definitionLocator the flow definition locator (registry)
130: */
131: public void setDefinitionLocator(
132: FlowDefinitionLocator definitionLocator) {
133: this .definitionLocator = definitionLocator;
134: }
135:
136: /**
137: * Sets the system attributes that apply to flow executions launched by the
138: * executor created by this factory. Execution attributes may affect flow
139: * execution behavior.
140: * <p>
141: * Note: this method simply accepts a generic <code>java.util.Map</code>
142: * to allow for easy configuration by Spring. The map entries should consist
143: * of non-null String keys with object values.
144: * @param executionAttributes the flow execution system attributes
145: */
146: public void setExecutionAttributes(Map executionAttributes) {
147: this .executionAttributes = new LocalAttributeMap(
148: executionAttributes);
149: }
150:
151: /**
152: * Convenience setter that sets a single listener that always applies to flow
153: * executions launched by the executor created by this factory.
154: * @param executionListener the flow execution listener
155: */
156: public void setExecutionListener(
157: FlowExecutionListener executionListener) {
158: setExecutionListeners(new FlowExecutionListener[] { executionListener });
159: }
160:
161: /**
162: * Convenience setter that sets a list of listeners that always apply to
163: * flow executions launched by the executor created by this factory.
164: * @param executionListeners the flow execution listeners
165: */
166: public void setExecutionListeners(
167: FlowExecutionListener[] executionListeners) {
168: setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(
169: executionListeners));
170: }
171:
172: /**
173: * Sets the strategy for loading the listeners that will observe executions
174: * of a flow definition. Allows full control over what listeners should
175: * apply to executions of a flow definition launched by the executor created
176: * by this factory.
177: */
178: public void setExecutionListenerLoader(
179: FlowExecutionListenerLoader executionListenerLoader) {
180: this .executionListenerLoader = executionListenerLoader;
181: }
182:
183: /**
184: * Sets the type of flow execution repository that should be configured for
185: * the flow executors created by this factory. This factory encapsulates the
186: * construction of the repository implementation corresponding to the
187: * provided type.
188: * @param repositoryType the flow execution repository type
189: */
190: public void setRepositoryType(RepositoryType repositoryType) {
191: this .repositoryType = repositoryType;
192: }
193:
194: /**
195: * Set the maximum number of continuation snapshots allowed for a single
196: * conversation when using the {@link RepositoryType#CONTINUATION continuation}
197: * flow execution repository.
198: * @see ContinuationFlowExecutionRepository#setMaxContinuations(int)
199: * @since 1.0.1
200: */
201: public void setMaxContinuations(int maxContinuations) {
202: this .maxContinuations = new Integer(maxContinuations);
203: }
204:
205: /**
206: * Returns the configured maximum number of continuation snapshots allowed
207: * for a single conversation when using the
208: * {@link RepositoryType#CONTINUATION continuation} flow execution repository.
209: * @return the configured value or null if the user did not explicitly
210: * specify a value and wants to use the default
211: * @since 1.0.1
212: */
213: protected Integer getMaxContinuations() {
214: return maxContinuations;
215: }
216:
217: /**
218: * Sets the strategy for managing conversations that should be configured
219: * for flow executors created by this factory.
220: * <p>
221: * The conversation manager is used by the flow execution repository
222: * subsystem to begin and end new conversations that store execution state.
223: * <p>
224: * By default, a {@link SessionBindingConversationManager} is used. Do not
225: * use {@link #setMaxConversations(int)} when using this method.
226: */
227: public void setConversationManager(
228: ConversationManager conversationManager) {
229: this .conversationManager = conversationManager;
230: }
231:
232: /**
233: * Set the maximum number of allowed concurrent conversations in the session. This
234: * is a convenience setter to allow easy configuration of the maxConversations
235: * property of the default {@link SessionBindingConversationManager}. Do not use
236: * this when using {@link #setConversationManager(ConversationManager)}.
237: * @see SessionBindingConversationManager#setMaxConversations(int)
238: * @since 1.0.1
239: */
240: public void setMaxConversations(int maxConversations) {
241: this .maxConversations = new Integer(maxConversations);
242: }
243:
244: /**
245: * Returns the configured maximum number of allowed concurrent conversations
246: * in the session. Will only be used when using the default conversation manager,
247: * e.g. when no explicit conversation manager has been configured using
248: * {@link #setConversationManager(ConversationManager)}.
249: * @return the configured value or null if the user did not explicitly
250: * specify a value and wants to use the default
251: * @since 1.0.1
252: */
253: protected Integer getMaxConversations() {
254: return maxConversations;
255: }
256:
257: /**
258: * Set the service responsible for mapping attributes of an
259: * {@link ExternalContext} to a new {@link FlowExecution} during the
260: * {@link FlowExecutor#launch(String, ExternalContext) launch flow} operation.
261: * <p>
262: * This is optional. If not set, a default implementation will be used
263: * that simply exposes all request parameters as flow execution input attributes.
264: */
265: public void setInputMapper(AttributeMapper inputMapper) {
266: this .inputMapper = inputMapper;
267: }
268:
269: /**
270: * Return the configured input mapper.
271: */
272: protected AttributeMapper getInputMapper() {
273: return inputMapper;
274: }
275:
276: /**
277: * Set system defaults that should be used.
278: * @param defaults the defaults to use.
279: */
280: public void setDefaults(FlowSystemDefaults defaults) {
281: this .defaults = defaults;
282: }
283:
284: // implementing InitializingBean
285:
286: public void afterPropertiesSet() throws Exception {
287: Assert.notNull(definitionLocator,
288: "The flow definition locator is required");
289:
290: // apply defaults
291: executionAttributes = defaults
292: .applyExecutionAttributes(executionAttributes);
293: repositoryType = defaults.applyIfNecessary(repositoryType);
294:
295: // pass all available parameters to the hook methods so that they
296: // can participate in the construction process
297:
298: // a factory for flow executions
299: FlowExecutionFactory executionFactory = createFlowExecutionFactory(
300: executionAttributes, executionListenerLoader);
301:
302: // a strategy to restore deserialized flow executions
303: FlowExecutionStateRestorer executionStateRestorer = createFlowExecutionStateRestorer(
304: definitionLocator, executionAttributes,
305: executionListenerLoader);
306:
307: // a repository to store flow executions
308: FlowExecutionRepository executionRepository = createExecutionRepository(
309: repositoryType, executionStateRestorer,
310: conversationManager);
311:
312: // combine all pieces of the puzzle to get an operational flow executor
313: flowExecutor = createFlowExecutor(definitionLocator,
314: executionFactory, executionRepository);
315: }
316:
317: // subclassing hook methods
318:
319: /**
320: * Create the conversation manager to be used in the default case, e.g. when no
321: * explicit conversation manager has been configured using
322: * {@link #setConversationManager(ConversationManager)}. This implementation
323: * return a {@link SessionBindingConversationManager}.
324: * @return the default conversation manager
325: */
326: protected ConversationManager createDefaultConversationManager() {
327: SessionBindingConversationManager conversationManager = new SessionBindingConversationManager();
328: if (getMaxConversations() != null) {
329: conversationManager
330: .setMaxConversations(getMaxConversations()
331: .intValue());
332: }
333: return conversationManager;
334: }
335:
336: /**
337: * Create the flow execution factory to be used by the executor produced by this
338: * factory bean. Configure the execution factory appropriately. Subclasses may
339: * override if they which to use a custom execution factory, e.g. to use a custom
340: * FlowExecution implementation.
341: * @param executionAttributes execution attributes to apply to created executions
342: * @param executionListenerLoader decides which listeners to apply to created executions
343: * @return a new flow execution factory instance
344: */
345: protected FlowExecutionFactory createFlowExecutionFactory(
346: AttributeMap executionAttributes,
347: FlowExecutionListenerLoader executionListenerLoader) {
348: FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory();
349: executionFactory.setExecutionAttributes(executionAttributes);
350: if (executionListenerLoader != null) {
351: executionFactory
352: .setExecutionListenerLoader(executionListenerLoader);
353: }
354: return executionFactory;
355: }
356:
357: /**
358: * Create the flow execution state restorer to be used by the executor produced by
359: * this factory bean. Configure the state restorer appropriately. Subclasses may
360: * override if they which to use a custom state restorer implementation.
361: * @param definitionLocator the definition locator to use
362: * @param executionAttributes execution attributes to apply to restored executions
363: * @param executionListenerLoader decides which listeners should apply to restored
364: * flow executions
365: * @return a new state restorer instance
366: */
367: protected FlowExecutionStateRestorer createFlowExecutionStateRestorer(
368: FlowDefinitionLocator definitionLocator,
369: AttributeMap executionAttributes,
370: FlowExecutionListenerLoader executionListenerLoader) {
371: FlowExecutionImplStateRestorer executionStateRestorer = new FlowExecutionImplStateRestorer(
372: definitionLocator);
373: executionStateRestorer
374: .setExecutionAttributes(executionAttributes);
375: if (executionListenerLoader != null) {
376: executionStateRestorer
377: .setExecutionListenerLoader(executionListenerLoader);
378: }
379: return executionStateRestorer;
380: }
381:
382: /**
383: * Factory method for creating the flow execution repository for saving and
384: * loading executing flows. Subclasses may override to customize the
385: * repository implementation used.
386: * @param repositoryType a hint indicating what type of repository to create
387: * @param executionStateRestorer the execution state restorer strategy to be used by
388: * the repository
389: * @param conversationManager the conversation manager specified by the user,
390: * could be null in which case the default conversation manager should be used
391: * @return a new flow execution repository instance
392: */
393: protected FlowExecutionRepository createExecutionRepository(
394: RepositoryType repositoryType,
395: FlowExecutionStateRestorer executionStateRestorer,
396: ConversationManager conversationManager) {
397: if (repositoryType == RepositoryType.CLIENT) {
398: if (conversationManager == null) {
399: // use the default no-op conversation manager
400: return new ClientContinuationFlowExecutionRepository(
401: executionStateRestorer);
402: } else {
403: // use the conversation manager specified by the user
404: return new ClientContinuationFlowExecutionRepository(
405: executionStateRestorer, conversationManager);
406: }
407: } else {
408: // determine the conversation manager to use
409: ConversationManager conversationManagerToUse = conversationManager;
410: if (conversationManagerToUse == null) {
411: conversationManagerToUse = createDefaultConversationManager();
412: }
413:
414: if (repositoryType == RepositoryType.SIMPLE) {
415: return new SimpleFlowExecutionRepository(
416: executionStateRestorer,
417: conversationManagerToUse);
418: } else if (repositoryType == RepositoryType.CONTINUATION) {
419: ContinuationFlowExecutionRepository repository = new ContinuationFlowExecutionRepository(
420: executionStateRestorer,
421: conversationManagerToUse);
422: if (getMaxContinuations() != null) {
423: repository
424: .setMaxContinuations(getMaxContinuations()
425: .intValue());
426: }
427: return repository;
428: } else if (repositoryType == RepositoryType.SINGLEKEY) {
429: SimpleFlowExecutionRepository repository = new SimpleFlowExecutionRepository(
430: executionStateRestorer,
431: conversationManagerToUse);
432: repository.setAlwaysGenerateNewNextKey(false);
433: return repository;
434: } else {
435: throw new IllegalStateException(
436: "Cannot create execution repository - unsupported repository type "
437: + repositoryType);
438: }
439: }
440: }
441:
442: /**
443: * Create the flow executor instance created by this factory bean and configure
444: * it appropriately. Subclasses may override if they which to use a custom executor
445: * implementation.
446: * @param definitionLocator the definition locator to use
447: * @param executionFactory the execution factory to use
448: * @param executionRepository the execution repository to use
449: * @return a new flow executor instance
450: */
451: protected FlowExecutor createFlowExecutor(
452: FlowDefinitionLocator definitionLocator,
453: FlowExecutionFactory executionFactory,
454: FlowExecutionRepository executionRepository) {
455: FlowExecutorImpl flowExecutor = new FlowExecutorImpl(
456: definitionLocator, executionFactory,
457: executionRepository);
458: if (getInputMapper() != null) {
459: flowExecutor.setInputMapper(inputMapper);
460: }
461: return flowExecutor;
462: }
463:
464: // implementing FactoryBean
465:
466: public Class getObjectType() {
467: return FlowExecutor.class;
468: }
469:
470: public boolean isSingleton() {
471: return true;
472: }
473:
474: public Object getObject() throws Exception {
475: return getFlowExecutor();
476: }
477:
478: /**
479: * Returns the flow executor constructed by the factory bean.
480: * @since 1.0.2
481: */
482: public FlowExecutor getFlowExecutor() {
483: return flowExecutor;
484: }
485: }
|