/* * Copyright 2004-2014 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.config; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; import org.springframework.webflow.conversation.ConversationManager; import org.springframework.webflow.conversation.impl.SessionBindingConversationManager; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.definition.registry.FlowDefinitionLocator; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.execution.FlowExecutionFactory; import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader; import org.springframework.webflow.execution.factory.FlowExecutionListenerCriteriaFactory; import org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository; import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshotFactory; import org.springframework.webflow.execution.repository.snapshot.SerializedFlowExecutionSnapshotFactory; import org.springframework.webflow.execution.repository.snapshot.SimpleFlowExecutionSnapshotFactory; import org.springframework.webflow.executor.FlowExecutor; import org.springframework.webflow.executor.FlowExecutorImpl; import org.springframework.webflow.mvc.builder.MvcEnvironment; /** * A builder for {@link FlowExecutor} instances designed for programmatic use in * {@code @Bean} factory methods. For XML configuration consider using the * {@code webflow-config} XML namespace. * * @author Rossen Stoyanchev * @since 2.4 */ public class FlowExecutorBuilder { private final FlowDefinitionLocator flowRegistry; private Integer maxFlowExecutions; private Integer maxFlowExecutionSnapshots; private MvcEnvironment environment; private LocalAttributeMap<Object> executionAttributes = new LocalAttributeMap<Object>(); private ConditionalFlowExecutionListenerLoader listenerLoader; private FlowExecutionListenerCriteriaFactory listenerCriteriaFactory = new FlowExecutionListenerCriteriaFactory(); private ConversationManager conversationManager; /** * Create a new instance with the given flow registry and ApplicationContext. * * @param flowRegistry the flow registry that will locate flow definitions * @param applicationContext the Spring ApplicationContext to use for * initializing an instance of {@link MvcEnvironment} */ public FlowExecutorBuilder(FlowDefinitionLocator flowRegistry, ApplicationContext applicationContext) { Assert.notNull(flowRegistry, "FlowDefinitionLocator is required"); Assert.notNull(applicationContext, "applicationContext is required"); this.flowRegistry = flowRegistry; this.environment = MvcEnvironment.environmentFor(applicationContext); } /** * Set the maximum number of allowed flow executions per user. * @param maxFlowExecutions the max flow executions */ public FlowExecutorBuilder setMaxFlowExecutions(int maxFlowExecutions) { this.maxFlowExecutions = maxFlowExecutions; return this; } /** * Set the maximum number of history snapshots allowed per flow execution. * @param maxFlowExecutionSnapshots the max flow execution snapshots */ public FlowExecutorBuilder setMaxFlowExecutionSnapshots(int maxFlowExecutionSnapshots) { this.maxFlowExecutionSnapshots = maxFlowExecutionSnapshots; return this; } /** * Whether flow executions should redirect after they pause before rendering. * @param redirectOnPause whether to redirect or not */ public FlowExecutorBuilder setAlwaysRedirectOnPause(boolean redirectOnPause) { this.executionAttributes.put("alwaysRedirectOnPause", redirectOnPause); return this; } /** * Whether flow executions redirect after they pause for transitions that remain * in the same view state. This attribute effectively overrides the value of the * "always-redirect-on-pause" attribute in same state transitions. * @param redirectInSameState whether to redirect or not */ public FlowExecutorBuilder setRedirectInSameState(boolean redirectInSameState) { this.executionAttributes.put("redirectInSameState", redirectInSameState); return this; } /** * Add a single flow execution meta attribute. * @param name the attribute name * @param value the attribute value */ public FlowExecutorBuilder addFlowExecutionAttribute(String name, Object value) { this.executionAttributes.put(name, value); return this; } /** * Register a {@link FlowExecutionListener} that observes the lifecycle of all flow * executions launched by this executor. * @param listener the listener to be registered */ public FlowExecutorBuilder addFlowExecutionListener(FlowExecutionListener listener) { return addFlowExecutionListener(listener, "*"); } /** * Register a {@link FlowExecutionListener} that observes the lifecycle of flow * executions launched by this executor. * @param listener the listener to be registered * @param criteria the criteria that determines the flow definitions a listener * should observe, delimited by commas or '*' for "all". * Example: 'flow1,flow2,flow3'. */ public FlowExecutorBuilder addFlowExecutionListener(FlowExecutionListener listener, String criteria) { if (this.listenerLoader == null) { this.listenerLoader = new ConditionalFlowExecutionListenerLoader(); } this.listenerLoader.addListener(listener, this.listenerCriteriaFactory.getListenerCriteria(criteria)); return this; } /** * Set the ConversationManager implementation to use for storing conversations * in the session effectively controlling how state is stored physically when * a flow execution is paused.. Note that when this attribute is provided, the * "max-execution-snapshots" attribute is meaningless. * @param conversationManager the ConversationManager instance to use */ public FlowExecutorBuilder setConversationManager(ConversationManager conversationManager) { this.conversationManager = conversationManager; return this; } /** * Create and return a {@link FlowExecutor} instance. */ public FlowExecutor build() { FlowExecutionImplFactory executionFactory = getExecutionFactory(); DefaultFlowExecutionRepository executionRepository = getFlowExecutionRepository(executionFactory); executionFactory.setExecutionKeyFactory(executionRepository); return new FlowExecutorImpl(this.flowRegistry, executionFactory, executionRepository); } private FlowExecutionImplFactory getExecutionFactory() { FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory(); executionFactory.setExecutionAttributes(getExecutionAttributes()); if (this.listenerLoader != null) { executionFactory.setExecutionListenerLoader(this.listenerLoader); } return executionFactory; } private DefaultFlowExecutionRepository getFlowExecutionRepository(FlowExecutionFactory executionFactory) { ConversationManager manager = getConversationManager(); FlowExecutionSnapshotFactory snapshotFactory = getSnapshotFactory(executionFactory); DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(manager, snapshotFactory); if (this.maxFlowExecutionSnapshots != null) { repository.setMaxSnapshots((this.maxFlowExecutionSnapshots == 0) ? 1 : this.maxFlowExecutionSnapshots); } return repository; } private ConversationManager getConversationManager() { ConversationManager manager = this.conversationManager; if (manager == null) { manager = new SessionBindingConversationManager(); } if (this.maxFlowExecutions != null && manager instanceof SessionBindingConversationManager) { ((SessionBindingConversationManager) manager).setMaxConversations(this.maxFlowExecutions); } return manager; } private FlowExecutionSnapshotFactory getSnapshotFactory(FlowExecutionFactory executionFactory) { FlowExecutionSnapshotFactory factory = null; if (this.maxFlowExecutionSnapshots != null && this.maxFlowExecutionSnapshots == 0) { factory = new SimpleFlowExecutionSnapshotFactory(executionFactory, this.flowRegistry); } else { factory = new SerializedFlowExecutionSnapshotFactory(executionFactory, this.flowRegistry); } return factory; } private LocalAttributeMap<Object> getExecutionAttributes() { LocalAttributeMap<Object> attributes = new LocalAttributeMap<Object>(this.executionAttributes.asMap()); if (!attributes.contains("alwaysRedirectOnPause")) { attributes.put("alwaysRedirectOnPause", (this.environment != MvcEnvironment.PORTLET)); } if (!attributes.contains("redirectInSameState")) { attributes.put("redirectInSameState", (this.environment != MvcEnvironment.PORTLET)); } return attributes; } }