/*
* Copyright 2004-2008 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.execution.repository.impl;
import java.io.Serializable;
import org.springframework.webflow.conversation.Conversation;
import org.springframework.webflow.conversation.ConversationManager;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException;
import org.springframework.webflow.execution.repository.snapshot.AbstractSnapshottingFlowExecutionRepository;
import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshot;
import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshotFactory;
import org.springframework.webflow.execution.repository.snapshot.SnapshotNotFoundException;
/**
* The default flow execution repository implementation. Takes <i>one to {@link #getMaxSnapshots() max}</i> flow
* execution snapshots, where each snapshot represents a copy of a {@link FlowExecution} taken at a point in time.
* Snapshots are created via a {@link FlowExecutionSnapshotFactory} and that may or may not involve creating a copy of a
* flow execution through Java serialization. In particular when the flow-execution-repository element is configured
* with max-execution-snapshots="0", creating snapshot copies is effectively turned off.
* <p>
* The set of active flow executions are managed by a {@link ConversationManager} implementation, which this repository
* delegates to.
* <p>
* This repository is responsible for:
* <ul>
* <li>Beginning a new {@link Conversation} when a {@link FlowExecution} is assigned a persistent key. Each conversation
* is assigned a unique conversation id which forms one part of the flow execution key.
* <li>Taking {@link FlowExecutionSnapshot execution snapshots} to persist flow execution state. A snapshot is a copy of
* the execution created at a point in time <i>that can be restored and continued</i>. Snapshotting supports users going
* back in their browser to continue their flow execution from a previoius point.
* <li>Ending conversations when flow executions end.
* </ul>
* <p>
* This repository implementation also provides support for <i>execution invalidation after completion</i>, where once a
* logical flow execution completes, it and all of its snapshots are removed. This cleans up memory and prevents the
* possibility of duplicate submission after completion.
*
* @author Keith Donald
*/
public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExecutionRepository {
/**
* The conversation attribute that stores the group of flow execution snapshots.
*/
private static final String SNAPSHOT_GROUP_ATTRIBUTE = "flowExecutionSnapshotGroup";
/**
* The maximum number of snapshots that can be taken per execution. The default is 30, which is generally high
* enough not to interfere with the user experience of normal users using the back button, but low enough to avoid
* excessive resource usage or denial of service attacks.
*/
private int maxSnapshots = 30;
/**
* Create a new default flow execution repository using the given state restorer, conversation manager, and snapshot
* factory.
* @param conversationManager the conversation manager to use
* @param snapshotFactory the flow execution snapshot factory to use
*/
public DefaultFlowExecutionRepository(ConversationManager conversationManager,
FlowExecutionSnapshotFactory snapshotFactory) {
super(conversationManager, snapshotFactory);
}
/**
* Returns the max number of snapshots allowed per flow execution by this repository.
*/
public int getMaxSnapshots() {
return maxSnapshots;
}
/**
* Sets the maximum number of snapshots allowed per flow execution by this repository. Use -1 for unlimited. The
* default is 30.
*/
public void setMaxSnapshots(int maxSnapshots) {
this.maxSnapshots = maxSnapshots;
}
// supporting flow execution key factory impl
protected Serializable nextSnapshotId(Serializable executionId) {
return getSnapshotGroup(getConversation(executionId)).nextSnapshotId();
}
// implementing flow execution repository
public FlowExecution getFlowExecution(FlowExecutionKey key) {
if (logger.isDebugEnabled()) {
logger.debug("Getting flow execution with key '" + key + "'");
}
Conversation conversation = getConversation(key);
FlowExecutionSnapshot snapshot;
try {
snapshot = getSnapshotGroup(conversation).getSnapshot(getSnapshotId(key));
} catch (SnapshotNotFoundException e) {
throw new FlowExecutionRestorationFailureException(key, e);
}
return restoreFlowExecution(snapshot, key, conversation);
}
public void putFlowExecution(FlowExecution flowExecution) {
assertKeySet(flowExecution);
if (logger.isDebugEnabled()) {
logger.debug("Putting flow execution '" + flowExecution + "' into repository");
}
FlowExecutionKey key = flowExecution.getKey();
Conversation conversation = getConversation(key);
FlowExecutionSnapshotGroup snapshotGroup = getSnapshotGroup(conversation);
FlowExecutionSnapshot snapshot = snapshot(flowExecution);
if (logger.isDebugEnabled()) {
logger.debug("Adding snapshot to group with id " + getSnapshotId(key));
}
snapshotGroup.addSnapshot(getSnapshotId(key), snapshot);
putConversationScope(flowExecution, conversation);
}
// implementing flow execution key factory
public void updateFlowExecutionSnapshot(FlowExecution execution) {
FlowExecutionKey key = execution.getKey();
if (key == null) {
return;
}
Conversation conversation = getConversation(key);
getSnapshotGroup(conversation).updateSnapshot(getSnapshotId(key), snapshot(execution));
}
public void removeFlowExecutionSnapshot(FlowExecution execution) {
FlowExecutionKey key = execution.getKey();
if (key == null) {
return;
}
Conversation conversation = getConversation(key);
getSnapshotGroup(conversation).removeSnapshot(getSnapshotId(key));
}
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
FlowExecutionKey key = execution.getKey();
if (key == null) {
return;
}
Conversation conversation = getConversation(execution.getKey());
getSnapshotGroup(conversation).removeAllSnapshots();
}
// hooks for subclassing
protected FlowExecutionSnapshotGroup createFlowExecutionSnapshotGroup() {
SimpleFlowExecutionSnapshotGroup group = new SimpleFlowExecutionSnapshotGroup();
group.setMaxSnapshots(maxSnapshots);
return group;
}
/**
* Returns the snapshot group associated with the governing conversation.
* @param conversation the conversation where the snapshot group is stored
* @return the snapshot group
*/
protected FlowExecutionSnapshotGroup getSnapshotGroup(Conversation conversation) {
FlowExecutionSnapshotGroup group = (FlowExecutionSnapshotGroup) conversation
.getAttribute(SNAPSHOT_GROUP_ATTRIBUTE);
if (group == null) {
group = createFlowExecutionSnapshotGroup();
conversation.putAttribute(SNAPSHOT_GROUP_ATTRIBUTE, group);
}
return group;
}
}