/*
* Copyright 2004-2012 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.executor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.context.ExternalContextHolder;
import org.springframework.webflow.core.FlowException;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.repository.FlowExecutionLock;
import org.springframework.webflow.execution.repository.FlowExecutionRepository;
/**
* The default implementation of the central facade for <i>driving</i> the execution of flows within an application.
* <p>
* This object is responsible for creating and launching new flow executions as requested by clients, as well as
* resuming existing, paused executions (that were waiting to be resumed in response to a user event).
* <p>
* This object is a facade or entry point into the Spring Web Flow execution system and makes the overall system easier
* to use. The name <i>executor</i> was chosen as <i>executors drive executions</i>.
* <p>
* <b>Commonly used configurable properties</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>description</b></td>
* <td><b>default</b></td>
* </tr>
* <tr>
* <td>definitionLocator</td>
* <td>The service locator responsible for loading flow definitions to execute.</td>
* <td>None</td>
* </tr>
* <tr>
* <td>executionFactory</td>
* <td>The factory responsible for creating new flow executions.</td>
* <td>None</td>
* </tr>
* <tr>
* <td>executionRepository</td>
* <td>The repository responsible for managing flow execution persistence.</td>
* <td>None</td>
* </tr>
* </table>
* </p>
*
* @see FlowDefinitionLocator
* @see FlowExecutionFactory
* @see FlowExecutionRepository
*
* @author Keith Donald
* @author Erwin Vervaet
* @author Colin Sampaleanu
*/
public class FlowExecutorImpl implements FlowExecutor {
private static final Log logger = LogFactory.getLog(FlowExecutorImpl.class);
/**
* The locator to access flow definitions registered in a central registry.
*/
private FlowDefinitionLocator definitionLocator;
/**
* The abstract factory for creating a new execution of a flow definition.
*/
private FlowExecutionFactory executionFactory;
/**
* The repository used to save, update, and load existing flow executions to/from a persistent store.
*/
private FlowExecutionRepository executionRepository;
/**
* Create a new flow executor.
* @param definitionLocator the locator for accessing flow definitions to execute
* @param executionFactory the factory for creating executions of flow definitions
* @param executionRepository the repository for persisting paused flow executions
*/
public FlowExecutorImpl(FlowDefinitionLocator definitionLocator, FlowExecutionFactory executionFactory,
FlowExecutionRepository executionRepository) {
Assert.notNull(definitionLocator, "The locator for accessing flow definitions is required");
Assert.notNull(executionFactory, "The execution factory for creating new flow executions is required");
Assert.notNull(executionRepository, "The repository for persisting flow executions is required");
this.definitionLocator = definitionLocator;
this.executionFactory = executionFactory;
this.executionRepository = executionRepository;
}
/**
* Returns the locator to load flow definitions to execute.
*/
public FlowDefinitionLocator getDefinitionLocator() {
return definitionLocator;
}
/**
* Returns the abstract factory used to create new executions of a flow.
*/
public FlowExecutionFactory getExecutionFactory() {
return executionFactory;
}
/**
* Returns the repository used to save, update, and load existing flow executions to/from a persistent store.
*/
public FlowExecutionRepository getExecutionRepository() {
return executionRepository;
}
public FlowExecutionResult launchExecution(String flowId, MutableAttributeMap<?> input, ExternalContext context)
throws FlowException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Launching new execution of flow '" + flowId + "' with input " + input);
}
ExternalContextHolder.setExternalContext(context);
FlowDefinition flowDefinition = definitionLocator.getFlowDefinition(flowId);
FlowExecution flowExecution = executionFactory.createFlowExecution(flowDefinition);
flowExecution.start(input, context);
if (!flowExecution.hasEnded()) {
FlowExecutionLock lock = executionRepository.getLock(flowExecution.getKey());
lock.lock();
try {
executionRepository.putFlowExecution(flowExecution);
} finally {
lock.unlock();
}
return createPausedResult(flowExecution);
} else {
return createEndResult(flowExecution);
}
} finally {
ExternalContextHolder.setExternalContext(null);
}
}
public FlowExecutionResult resumeExecution(String flowExecutionKey, ExternalContext context) throws FlowException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Resuming flow execution with key '" + flowExecutionKey);
}
ExternalContextHolder.setExternalContext(context);
FlowExecutionKey key = executionRepository.parseFlowExecutionKey(flowExecutionKey);
FlowExecutionLock lock = executionRepository.getLock(key);
lock.lock();
try {
FlowExecution flowExecution = executionRepository.getFlowExecution(key);
flowExecution.resume(context);
if (!flowExecution.hasEnded()) {
executionRepository.putFlowExecution(flowExecution);
return createPausedResult(flowExecution);
} else {
executionRepository.removeFlowExecution(flowExecution);
return createEndResult(flowExecution);
}
} finally {
lock.unlock();
}
} finally {
ExternalContextHolder.setExternalContext(null);
}
}
private FlowExecutionResult createEndResult(FlowExecution flowExecution) {
return FlowExecutionResult.createEndedResult(flowExecution.getDefinition().getId(), flowExecution.getOutcome());
}
private FlowExecutionResult createPausedResult(FlowExecution flowExecution) {
return FlowExecutionResult.createPausedResult(flowExecution.getDefinition().getId(), flowExecution.getKey()
.toString());
}
}