flow appliction context classloader used on deserialization

This commit is contained in:
Keith Donald
2008-05-05 02:53:16 +00:00
parent a413dabe3c
commit daccb49a1a
24 changed files with 411 additions and 532 deletions

View File

@@ -35,13 +35,12 @@ import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
import org.springframework.webflow.execution.repository.FlowExecutionRepository;
import org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshotFactory;
import org.springframework.webflow.execution.repository.snapshot.SerializedFlowExecutionSnapshotFactory;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.FlowExecutorImpl;
import org.springframework.webflow.mvc.builder.MvcEnvironment;
@@ -128,8 +127,9 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, I
public void afterPropertiesSet() throws Exception {
Assert.notNull(flowDefinitionLocator, "The flow definition locator property is required");
MutableAttributeMap executionAttributes = createFlowExecutionAttributes();
DefaultFlowExecutionRepository executionRepository = createFlowExecutionRepository(executionAttributes);
FlowExecutionFactory executionFactory = createFlowExecutionFactory(executionAttributes, executionRepository);
FlowExecutionImplFactory executionFactory = createFlowExecutionFactory(executionAttributes);
DefaultFlowExecutionRepository executionRepository = createFlowExecutionRepository(executionFactory);
executionFactory.setExecutionKeyFactory(executionRepository);
flowExecutor = new FlowExecutorImpl(flowDefinitionLocator, executionFactory, executionRepository);
}
@@ -169,15 +169,14 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, I
}
}
private DefaultFlowExecutionRepository createFlowExecutionRepository(AttributeMap executionAttributes) {
private DefaultFlowExecutionRepository createFlowExecutionRepository(FlowExecutionFactory executionFactory) {
ConversationManager conversationManager = createConversationManager();
FlowExecutionStateRestorer executionStateRestorer = createFlowExecutionStateRestorer(executionAttributes);
DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(conversationManager,
executionStateRestorer);
FlowExecutionSnapshotFactory snapshotFactory = createFlowExecutionSnapshotFactory(executionFactory);
DefaultFlowExecutionRepository rep = new DefaultFlowExecutionRepository(conversationManager, snapshotFactory);
if (maxFlowExecutionSnapshots != null) {
repository.setMaxSnapshots(maxFlowExecutionSnapshots.intValue());
rep.setMaxSnapshots(maxFlowExecutionSnapshots.intValue());
}
return repository;
return rep;
}
private ConversationManager createConversationManager() {
@@ -188,24 +187,16 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, I
return conversationManager;
}
private FlowExecutionStateRestorer createFlowExecutionStateRestorer(AttributeMap executionAttributes) {
FlowExecutionImplStateRestorer executionStateRestorer = new FlowExecutionImplStateRestorer(
flowDefinitionLocator);
executionStateRestorer.setExecutionAttributes(executionAttributes);
if (flowExecutionListenerLoader != null) {
executionStateRestorer.setExecutionListenerLoader(flowExecutionListenerLoader);
}
return executionStateRestorer;
private FlowExecutionSnapshotFactory createFlowExecutionSnapshotFactory(FlowExecutionFactory executionFactory) {
return new SerializedFlowExecutionSnapshotFactory(executionFactory, flowDefinitionLocator);
}
private FlowExecutionFactory createFlowExecutionFactory(AttributeMap executionAttributes,
FlowExecutionKeyFactory keyFactory) {
private FlowExecutionImplFactory createFlowExecutionFactory(AttributeMap executionAttributes) {
FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory();
executionFactory.setExecutionAttributes(executionAttributes);
if (flowExecutionListenerLoader != null) {
executionFactory.setExecutionListenerLoader(flowExecutionListenerLoader);
}
executionFactory.setExecutionKeyFactory(keyFactory);
return executionFactory;
}

View File

@@ -62,7 +62,7 @@ public interface Conversation {
public void lock() throws ConversationLockException;
/**
* Returns the conversation attribute with the specified name. You need to aquire the lock on this conversation
* Returns the conversation attribute with the specified name. You need to acquire the lock on this conversation
* before calling this method.
* @param name the attribute name
* @return the attribute value
@@ -70,7 +70,7 @@ public interface Conversation {
public Object getAttribute(Object name);
/**
* Puts a conversation attribute into this context. You need to aquire the lock on this conversation before calling
* Puts a conversation attribute into this context. You need to acquire the lock on this conversation before calling
* this method.
* @param name the attribute name
* @param value the attribute value
@@ -78,7 +78,7 @@ public interface Conversation {
public void putAttribute(Object name, Object value);
/**
* Removes a conversation attribute. You need to aquire the lock on this conversation before calling this method.
* Removes a conversation attribute. You need to acquire the lock on this conversation before calling this method.
* @param name the attribute name
*/
public void removeAttribute(Object name);
@@ -93,4 +93,5 @@ public interface Conversation {
* Unlock this conversation, making it available to others for manipulation.
*/
public void unlock();
}

View File

@@ -90,6 +90,9 @@ class ConversationContainer implements Serializable {
public synchronized Conversation createConversation(ConversationParameters parameters,
ConversationLockFactory lockFactory) {
ContainedConversation conversation = new ContainedConversation(this, nextId(), lockFactory.createLock());
conversation.putAttribute("name", parameters.getName());
conversation.putAttribute("caption", parameters.getCaption());
conversation.putAttribute("description", parameters.getDescription());
conversations.add(conversation);
if (maxExceeded()) {
// end oldest conversation

View File

@@ -18,8 +18,7 @@ package org.springframework.webflow.definition;
import org.springframework.context.ApplicationContext;
/**
* The definition of a flow, a program that when executed carries out the orchestration of a task on behalf of a single
* client.
* The definition of a flow, a program that when executed carries out a task on behalf of a single client.
* <p>
* A flow definition is a reusable, self-contained controller module that defines a blue print for an executable user
* task. Flows typically orchestrate controlled navigations or dialogs within web applications to guide users through
@@ -70,8 +69,14 @@ public interface FlowDefinition extends Annotated {
public String[] getPossibleOutcomes();
/**
* Returns a reference to application context hosting application objects and services needed by this flow
* definition.
* Returns the class loader used by this flow definition to load classes.
* @return the class loader
*/
public ClassLoader getClassLoader();
/**
* Returns a reference to application context hosting application objects and services used by this flow definition.
* @return the application context
*/
public ApplicationContext getApplicationContext();

View File

@@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.style.StylerUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.core.collection.AttributeMap;
@@ -224,6 +225,14 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
return (String[]) possibleOutcomes.toArray(new String[possibleOutcomes.size()]);
}
public ClassLoader getClassLoader() {
if (applicationContext != null) {
return applicationContext.getClassLoader();
} else {
return ClassUtils.getDefaultClassLoader();
}
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}

View File

@@ -20,8 +20,8 @@ import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -60,10 +60,9 @@ import org.springframework.webflow.execution.View;
* <p>
* This implementation of FlowExecution is serializable so it can be safely stored in an HTTP session or other
* persistent store such as a file, database, or client-side form field. Once deserialized, the
* {@link FlowExecutionImplStateRestorer} strategy is expected to be used to restore the execution to a usable state.
* {@link FlowExecutionImplFactory} is expected to be used to restore the execution to a usable state.
*
* @see FlowExecutionImplFactory
* @see FlowExecutionImplStateRestorer
*
* @author Keith Donald
* @author Erwin Vervaet
@@ -78,7 +77,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
/**
* The execution's root flow; the top level flow that acts as the starting point for this flow execution.
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient Flow flow;
@@ -96,7 +95,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
/**
* A thread-safe listener list, holding listeners monitoring the lifecycle of this flow execution.
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient FlowExecutionListeners listeners;
@@ -118,22 +117,17 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
/**
* A data structure for attributes shared by all flow sessions.
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient MutableAttributeMap conversationScope;
/**
* A data structure for runtime system execution attributes.
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient AttributeMap attributes;
/**
* Set so the transient {@link #flow} field can be restored by the {@link FlowExecutionImplStateRestorer}.
*/
private String flowId;
/**
* The outcome reached by this flow execution when it ends.
*/
@@ -160,16 +154,6 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
this.conversationScope.put(FLASH_SCOPE_ATTRIBUTE, new LocalAttributeMap());
}
/**
* Package private constructor only useful for testing restoration behavior for this object.
* @param flowId the flow id
* @param flowSessions the flow sessions
*/
FlowExecutionImpl(String flowId, LinkedList flowSessions) {
this.flowId = flowId;
this.flowSessions = flowSessions;
}
public String getCaption() {
return "execution of '" + flow.getId() + "'";
}
@@ -437,22 +421,23 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
this.attributes = attributes;
}
FlowExecutionKeyFactory getKeyFactory() {
return keyFactory;
}
void setKeyFactory(FlowExecutionKeyFactory keyFactory) {
this.keyFactory = keyFactory;
}
MessageContextFactory getMessageContextFactory() {
return messageContextFactory;
}
void setMessageContextFactory(MessageContextFactory messageContextFactory) {
this.messageContextFactory = messageContextFactory;
}
// Used by FlowExecutionImplStateRestorer
/**
* Returns the flow definition id of this flow execution.
*/
String getFlowId() {
return flowId;
}
// Used by {@link FlowExecutionImplFactory}
/**
* Returns the list of flow session maintained by this flow execution.
@@ -485,7 +470,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
/**
* Returns an iterator looping over the subflow sessions in this flow execution.
*/
ListIterator getSubflowSessionIterator() {
Iterator getSubflowSessionIterator() {
return flowSessions.listIterator(1);
}
@@ -514,13 +499,11 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
started = in.readBoolean();
flowId = (String) in.readObject();
flowSessions = (LinkedList) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeBoolean(started);
out.writeObject(flow.getId());
out.writeObject(flowSessions);
}
@@ -536,7 +519,7 @@ public class FlowExecutionImpl implements FlowExecution, Externalizable {
return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions)
.toString();
} else {
return "[Unhydrated execution of '" + flowId + "']";
return "[Unhydrated execution of '" + getRootSession().getFlowId() + "']";
}
}
}

View File

@@ -15,29 +15,59 @@
*/
package org.springframework.webflow.engine.impl;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.binding.message.DefaultMessageContextFactory;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
/**
* A factory for instances of the {@link FlowExecutionImpl default flow execution} implementation.
* @author Keith Donald
*/
public class FlowExecutionImplFactory extends FlowExecutionImplServicesConfigurer implements FlowExecutionFactory {
public class FlowExecutionImplFactory implements FlowExecutionFactory {
private static final Log logger = LogFactory.getLog(FlowExecutionImplFactory.class);
/**
* The factory used to assign keys to flow executions that need to be persisted.
*/
private AttributeMap executionAttributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP;
private FlowExecutionListenerLoader executionListenerLoader = StaticFlowExecutionListenerLoader.EMPTY_INSTANCE;
private FlowExecutionKeyFactory executionKeyFactory = new SimpleFlowExecutionKeyFactory();
/**
* Sets the attributes to apply to flow executions created by this factory. Execution attributes may affect flow
* execution behavior.
* @param executionAttributes flow execution system attributes
*/
public void setExecutionAttributes(AttributeMap executionAttributes) {
Assert.notNull(executionAttributes, "The execution attributes map is required");
this.executionAttributes = executionAttributes;
}
/**
* Sets the strategy for loading listeners that should observe executions of a flow definition. Allows full control
* over what listeners should apply for executions of a flow definition.
*/
public void setExecutionListenerLoader(FlowExecutionListenerLoader executionListenerLoader) {
Assert.notNull(executionListenerLoader, "The execution listener loader is required");
this.executionListenerLoader = executionListenerLoader;
}
/**
* Sets the strategy for generating flow execution keys for persistent flow executions.
*/
@@ -46,7 +76,7 @@ public class FlowExecutionImplFactory extends FlowExecutionImplServicesConfigure
}
public FlowExecution createFlowExecution(FlowDefinition flowDefinition) {
Assert.isInstanceOf(Flow.class, flowDefinition, "Flow definition is of wrong type: ");
Assert.isInstanceOf(Flow.class, flowDefinition, "FlowDefinition is of wrong type: ");
if (logger.isDebugEnabled()) {
logger.debug("Creating new execution of '" + flowDefinition.getId() + "'");
}
@@ -56,6 +86,48 @@ public class FlowExecutionImplFactory extends FlowExecutionImplServicesConfigure
return execution;
}
public FlowExecution restoreFlowExecution(FlowExecution flowExecution, FlowDefinition flowDefinition,
FlowExecutionKey flowExecutionKey, MutableAttributeMap conversationScope,
FlowDefinitionLocator subflowDefinitionLocator) {
Assert.isInstanceOf(FlowExecutionImpl.class, flowExecution, "FlowExecution is of wrong type: ");
Assert.isInstanceOf(Flow.class, flowDefinition, "FlowDefinition is of wrong type: ");
FlowExecutionImpl execution = (FlowExecutionImpl) flowExecution;
Flow flow = (Flow) flowDefinition;
execution.setFlow(flow);
if (execution.hasSessions()) {
FlowSessionImpl rootSession = execution.getRootSession();
rootSession.setFlow(flow);
rootSession.setState(flow.getStateInstance(rootSession.getStateId()));
if (execution.hasSubflowSessions()) {
for (Iterator it = execution.getSubflowSessionIterator(); it.hasNext();) {
FlowSessionImpl subflowSession = (FlowSessionImpl) it.next();
Flow subflowDef = (Flow) subflowDefinitionLocator.getFlowDefinition(subflowSession.getFlowId());
subflowSession.setFlow(subflowDef);
subflowSession.setState(subflowDef.getStateInstance(subflowSession.getStateId()));
}
}
}
execution.setKey(flowExecutionKey);
if (conversationScope == null) {
conversationScope = new LocalAttributeMap();
}
execution.setConversationScope(conversationScope);
configureServices(execution);
return execution;
}
/**
* Called by subclasses to apply the configured set of standard services to the flow execution.
* @param execution the flow execution
*/
protected void configureServices(FlowExecutionImpl execution) {
execution.setAttributes(executionAttributes);
execution.setListeners(executionListenerLoader.getListeners(execution.getDefinition()));
execution.setKeyFactory(executionKeyFactory);
execution.setMessageContextFactory(new DefaultMessageContextFactory(execution.getDefinition()
.getApplicationContext()));
}
/**
* Simple key factory suitable for standalone usage and testing. Not expected to be used in a web environment.
*/
@@ -102,7 +174,7 @@ public class FlowExecutionImplFactory extends FlowExecutionImplServicesConfigure
}
public int hashCode() {
return value * 29;
return value;
}
public String toString() {

View File

@@ -1,67 +0,0 @@
/*
* 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.engine.impl;
import org.springframework.binding.message.DefaultMessageContextFactory;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
abstract class FlowExecutionImplServicesConfigurer {
/**
* System execution attributes that may influence flow execution behavior. The default is an empty map.
*/
private AttributeMap executionAttributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP;
/**
* The strategy for loading listeners that should observe executions of a flow definition. The default simply loads
* an empty static listener list.
*/
private FlowExecutionListenerLoader executionListenerLoader = StaticFlowExecutionListenerLoader.EMPTY_INSTANCE;
/**
* Sets the attributes to apply to flow executions created by this factory. Execution attributes may affect flow
* execution behavior.
* @param executionAttributes flow execution system attributes
*/
public void setExecutionAttributes(AttributeMap executionAttributes) {
Assert.notNull(executionAttributes, "The execution attributes map is required");
this.executionAttributes = executionAttributes;
}
/**
* Sets the strategy for loading listeners that should observe executions of a flow definition. Allows full control
* over what listeners should apply for executions of a flow definition.
*/
public void setExecutionListenerLoader(FlowExecutionListenerLoader executionListenerLoader) {
Assert.notNull(executionListenerLoader, "The execution listener loader is required");
this.executionListenerLoader = executionListenerLoader;
}
/**
* Called by subclasses to apply the configured set of standard services to the flow execution.
* @param execution the flow execution
*/
protected void configureServices(FlowExecutionImpl execution) {
execution.setAttributes(executionAttributes);
execution.setListeners(executionListenerLoader.getListeners(execution.getDefinition()));
execution.setMessageContextFactory(new DefaultMessageContextFactory(execution.getDefinition()
.getApplicationContext()));
}
}

View File

@@ -1,85 +0,0 @@
/*
* 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.engine.impl;
import java.util.ListIterator;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
/**
* Restores the transient state of deserialized {@link FlowExecutionImpl} objects.
*
* @author Keith Donald
*/
public class FlowExecutionImplStateRestorer extends FlowExecutionImplServicesConfigurer implements
FlowExecutionStateRestorer {
/**
* Used to restore the flow execution's flow definition.
*/
private FlowDefinitionLocator definitionLocator;
/**
* Creates a new execution transient state restorer.
* @param definitionLocator the flow definition locator
*/
public FlowExecutionImplStateRestorer(FlowDefinitionLocator definitionLocator) {
Assert.notNull(definitionLocator, "The flow definition locator is required");
this.definitionLocator = definitionLocator;
}
public FlowExecution restoreState(FlowExecution flowExecution, FlowExecutionKey key,
MutableAttributeMap conversationScope, FlowExecutionKeyFactory keyFactory) {
FlowExecutionImpl execution = (FlowExecutionImpl) flowExecution;
if (execution.getFlowId() == null) {
throw new IllegalStateException("Cannot restore flow execution impl: the flow id is null");
}
if (execution.getFlowSessions() == null) {
throw new IllegalStateException("Cannot restore flow execution impl: the flowSessions list is null");
}
Flow flow = (Flow) definitionLocator.getFlowDefinition(execution.getFlowId());
execution.setFlow(flow);
if (execution.hasSessions()) {
FlowSessionImpl rootSession = execution.getRootSession();
rootSession.setFlow(flow);
rootSession.setState(flow.getStateInstance(rootSession.getStateId()));
if (execution.hasSubflowSessions()) {
for (ListIterator it = execution.getSubflowSessionIterator(); it.hasNext();) {
FlowSessionImpl subflowSession = (FlowSessionImpl) it.next();
Flow definition = (Flow) definitionLocator.getFlowDefinition(subflowSession.getFlowId());
subflowSession.setFlow(definition);
subflowSession.setState(definition.getStateInstance(subflowSession.getStateId()));
}
}
}
execution.setKey(key);
if (conversationScope == null) {
conversationScope = new LocalAttributeMap();
}
execution.setConversationScope(conversationScope);
configureServices(execution);
execution.setKeyFactory(keyFactory);
return execution;
}
}

View File

@@ -45,24 +45,24 @@ class FlowSessionImpl implements FlowSession, Externalizable {
/**
* The flow definition (a singleton).
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient Flow flow;
/**
* Set so the transient {@link #flow} field can be restored by the {@link FlowExecutionImplStateRestorer}.
* Set so the transient {@link #flow} field can be restored by the {@link FlowExecutionImplFactory}.
*/
private String flowId;
/**
* The current state of this flow session.
* <p>
* Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
* Transient to support restoration by the {@link FlowExecutionImplFactory}.
*/
private transient State state;
/**
* Set so the transient {@link #state} field can be restored by the {@link FlowExecutionImplStateRestorer}.
* Set so the transient {@link #state} field can be restored by the {@link FlowExecutionImplFactory}.
*/
private String stateId;
@@ -161,7 +161,7 @@ class FlowSessionImpl implements FlowSession, Externalizable {
return flow;
}
// package private setters used by FlowExecutionImplStateRestorer for setting/updating internal state
// package private setters used by FlowExecutionImplFactory for setting/updating internal state
/**
* Restores the definition of this flow session.
@@ -193,6 +193,14 @@ class FlowSessionImpl implements FlowSession, Externalizable {
return flowId;
}
/**
* Sets the de-serialized id indicating the flow id of this session. Used for testing only.
* @param flowId the flow id
*/
void setFlowId(String flowId) {
this.flowId = flowId;
}
/**
* Returns the de-serialized id indicating the current state of this session.
*/
@@ -200,6 +208,14 @@ class FlowSessionImpl implements FlowSession, Externalizable {
return stateId;
}
/**
* Sets the de-serialized id indicating the state of this session. Used for testing only.
* @param stateId the state id
*/
void setStateId(String stateId) {
this.stateId = stateId;
}
// internal helpers
/**

View File

@@ -15,7 +15,9 @@
*/
package org.springframework.webflow.execution;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
/**
* An abstract factory for creating flow executions. A flow execution represents a runtime, top-level instance of a flow
@@ -41,4 +43,19 @@ public interface FlowExecutionFactory {
* @return the new flow execution, fully initialized and awaiting to be started
*/
public FlowExecution createFlowExecution(FlowDefinition flowDefinition);
/**
* Restore the transient state of the flow execution.
* @param flowExecution the flow execution, newly deserialized and needing restoration
* @param flowDefinition the root flow definition for the execution, typically not part of the serialized form
* @param flowExecutionKey the flow execution key, typically not part of the serialized form
* @param conversationScope the execution's conversation scope, which is typically not part of the serialized form
* since it could be shared by multiple physical flow execution <i>copies</i> all sharing the same logical
* conversation
* @param subflowDefinitionLocator for locating the definitions of any subflows started by the execution
* @return the restored flow execution
*/
public FlowExecution restoreFlowExecution(FlowExecution flowExecution, FlowDefinition flowDefinition,
FlowExecutionKey flowExecutionKey, MutableAttributeMap conversationScope,
FlowDefinitionLocator subflowDefinitionLocator);
}

View File

@@ -23,10 +23,7 @@ import org.springframework.webflow.execution.repository.FlowExecutionRestoration
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.SerializedFlowExecutionSnapshotFactory;
import org.springframework.webflow.execution.repository.snapshot.SnapshotNotFoundException;
import org.springframework.webflow.execution.repository.snapshot.SnapshotUnmarshalException;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
/**
* The default flow execution repository implementation. Takes <i>one to {@link #getMaxSnapshots() max}</i> flow
@@ -65,27 +62,15 @@ public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExec
*/
private int maxSnapshots = 30;
/**
* Create a new default flow execution repository using the given state restorer and conversation manager. Defaults
* to a {@link SerializedFlowExecutionSnapshotFactory}.
* @param conversationManager the conversation manager to use
* @param executionStateRestorer the state restoration strategy to use
*/
public DefaultFlowExecutionRepository(ConversationManager conversationManager,
FlowExecutionStateRestorer executionStateRestorer) {
super(conversationManager, executionStateRestorer, new SerializedFlowExecutionSnapshotFactory());
}
/**
* 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 executionStateRestorer the state restoration strategy to use
* @param executionSnapshotFactory the flow execution snapshot factory to use
* @param snapshotFactory the flow execution snapshot factory to use
*/
public DefaultFlowExecutionRepository(ConversationManager conversationManager,
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionSnapshotFactory executionSnapshotFactory) {
super(conversationManager, executionStateRestorer, executionSnapshotFactory);
FlowExecutionSnapshotFactory snapshotFactory) {
super(conversationManager, snapshotFactory);
}
/**
@@ -116,12 +101,7 @@ public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExec
} catch (SnapshotNotFoundException e) {
throw new FlowExecutionRestorationFailureException(key, e);
}
try {
FlowExecution execution = snapshot.unmarshal();
return restoreTransientState(execution, key, conversation);
} catch (SnapshotUnmarshalException e) {
throw new FlowExecutionRestorationFailureException(key, e);
}
return restoreFlowExecution(snapshot, key, conversation);
}
public void putFlowExecution(FlowExecution flowExecution) {
@@ -142,9 +122,10 @@ public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExec
// implementing flow execution key factory
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
Conversation conversation = getConversation(execution.getKey());
getSnapshotGroup(conversation).removeAllSnapshots();
public void updateFlowExecutionSnapshot(FlowExecution execution) {
FlowExecutionKey key = execution.getKey();
Conversation conversation = getConversation(key);
getSnapshotGroup(conversation).updateSnapshot(getSnapshotId(key), snapshot(execution));
}
public void removeFlowExecutionSnapshot(FlowExecution execution) {
@@ -153,10 +134,9 @@ public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExec
getSnapshotGroup(conversation).removeSnapshot(getSnapshotId(key));
}
public void updateFlowExecutionSnapshot(FlowExecution execution) {
FlowExecutionKey key = execution.getKey();
Conversation conversation = getConversation(key);
getSnapshotGroup(conversation).updateSnapshot(getSnapshotId(key), snapshot(execution));
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
Conversation conversation = getConversation(execution.getKey());
getSnapshotGroup(conversation).removeAllSnapshots();
}
// hooks for subclassing

View File

@@ -18,12 +18,13 @@ package org.springframework.webflow.execution.repository.snapshot;
import java.io.Serializable;
import org.springframework.util.Assert;
import org.springframework.webflow.conversation.Conversation;
import org.springframework.webflow.conversation.ConversationManager;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.repository.support.AbstractFlowExecutionRepository;
import org.springframework.webflow.execution.repository.support.CompositeFlowExecutionKey;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
/**
* Base class for repositories that take flow execution snapshots using a {@link FlowExecutionSnapshotFactory}.
@@ -35,27 +36,26 @@ public abstract class AbstractSnapshottingFlowExecutionRepository extends Abstra
/**
* The factory to use to take flow execution snapshots.
*/
private FlowExecutionSnapshotFactory executionSnapshotFactory;
private FlowExecutionSnapshotFactory snapshotFactory;
/**
* Creates a new snapshotting flow execution repository.
* @param conversationManager the conversation manager
* @param executionStateRestorer the execution state restorer
* @param executionSnapshotFactory the execution snapshot factory
* @param snapshotFactory the execution snapshot factory
*/
public AbstractSnapshottingFlowExecutionRepository(ConversationManager conversationManager,
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionSnapshotFactory executionSnapshotFactory) {
super(conversationManager, executionStateRestorer);
Assert.notNull(executionSnapshotFactory, "The flow execution snapshot factory is required");
this.executionSnapshotFactory = executionSnapshotFactory;
FlowExecutionSnapshotFactory snapshotFactory) {
super(conversationManager);
Assert.notNull(snapshotFactory, "The flow execution snapshot factory is required");
this.snapshotFactory = snapshotFactory;
}
/**
* Returns the configured flow execution snapshot factory.
* @return the snapshot factory
*/
public FlowExecutionSnapshotFactory getExecutionSnapshotFactory() {
return executionSnapshotFactory;
public FlowExecutionSnapshotFactory getSnapshotFactory() {
return snapshotFactory;
}
/**
@@ -72,15 +72,29 @@ public abstract class AbstractSnapshottingFlowExecutionRepository extends Abstra
* @return the snapshot
*/
protected FlowExecutionSnapshot snapshot(FlowExecution flowExecution) {
return executionSnapshotFactory.createSnapshot(flowExecution);
return snapshotFactory.createSnapshot(flowExecution);
}
/**
* Deserialize a serialized flow execution.
* @param snapshotBytes the flow execution snapshot byte array
* @return the deserialized flow execution
* Restore a flow execution from a snapshot.
* @param snapshot the snapshot
* @param key the flow execution snapshot key
* @param conversation the governing conversation
* @return the restored flow execution
*/
protected FlowExecution deserializeExecution(byte[] snapshotBytes) {
return executionSnapshotFactory.restoreSnapshot(snapshotBytes).unmarshal();
protected FlowExecution restoreFlowExecution(FlowExecutionSnapshot snapshot, FlowExecutionKey key,
Conversation conversation) {
MutableAttributeMap conversationScope = (MutableAttributeMap) conversation.getAttribute("scope");
String flowId = (String) conversation.getAttribute("name");
return snapshotFactory.restoreExecution(snapshot, flowId, key, conversationScope, this);
}
/**
* Puts the value of conversation scope in the conversation object.
* @param flowExecution the flow execution holding a reference to conversation scope
* @param conversation the conversation where conversation scope is stored
*/
protected void putConversationScope(FlowExecution flowExecution, Conversation conversation) {
conversation.putAttribute("scope", flowExecution.getConversationScope());
}
}

View File

@@ -17,10 +17,8 @@ package org.springframework.webflow.execution.repository.snapshot;
import java.io.Serializable;
import org.springframework.webflow.execution.FlowExecution;
/**
* A snapshot of a flow execution that can be restored from and serialized to a byte array.
* A snapshot that can be used to restore a FlowExecution using a {@link FlowExecutionSnapshotFactory}.
*
* @see FlowExecutionSnapshotFactory
*
@@ -29,17 +27,4 @@ import org.springframework.webflow.execution.FlowExecution;
*/
public abstract class FlowExecutionSnapshot implements Serializable {
/**
* Returns underlying flow execution object.
* @return the unmarshalled flow execution
* @throws SnapshotUnmarshalException when there is a problem unmarshalling the execution
*/
public abstract FlowExecution unmarshal() throws SnapshotUnmarshalException;
/**
* Converts this snapshot to a byte array for convenient serialization.
* @return this as a byte array
*/
public abstract byte[] toByteArray();
}

View File

@@ -15,7 +15,11 @@
*/
package org.springframework.webflow.execution.repository.snapshot;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException;
/**
* A factory for creating different {@link FlowExecutionSnapshot} implementations.
@@ -34,10 +38,16 @@ public interface FlowExecutionSnapshotFactory {
public FlowExecutionSnapshot createSnapshot(FlowExecution flowExecution) throws SnapshotCreationException;
/**
* Restore a flow execution snapshot from a byte array.
* @param bytes the byte array
* @return the snapshot
* @throws SnapshotUnmarshalException if the snapshot could not be restored
* Restores a flow execution from a previously taken snapshot.
* @param snapshot the previously taken snapshot
* @param flowId the id of the root flow definition
* @param key the flow execution key
* @param conversationScope conversation scope
* @param keyFactory factory for creating new snapshot keys
* @return the restored flow execution
* @throws FlowExecutionRestorationFailureException if flow execution restoration fails
*/
public FlowExecutionSnapshot restoreSnapshot(byte[] bytes) throws SnapshotUnmarshalException;
public FlowExecution restoreExecution(FlowExecutionSnapshot snapshot, String flowId, FlowExecutionKey key,
MutableAttributeMap conversationScope, FlowExecutionKeyFactory keyFactory)
throws FlowExecutionRestorationFailureException;
}

View File

@@ -19,15 +19,18 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.webflow.execution.FlowExecution;
@@ -82,9 +85,15 @@ class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot implements E
return compressed;
}
public FlowExecution unmarshal() throws SnapshotUnmarshalException {
/**
* Unmarshal the flow execution from this snapshot's data.
* @param classLoader the classloader to use to resolve types during execution deserialization
* @return the unmarashalled flow execution
* @throws SnapshotUnmarshalException
*/
public FlowExecution unmarshal(ClassLoader classLoader) throws SnapshotUnmarshalException {
try {
return deserialize(getFlowExecutionData());
return deserialize(getFlowExecutionData(), classLoader);
} catch (IOException e) {
throw new SnapshotUnmarshalException(
"IOException thrown deserializing the flow execution stored in this snapshot -- this should not happen!",
@@ -98,22 +107,6 @@ class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot implements E
}
}
public byte[] toByteArray() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(flowExecutionData.length + 40);
ObjectOutputStream oos = new ObjectOutputStream(baos);
try {
oos.writeObject(this);
oos.flush();
} finally {
oos.close();
}
return baos.toByteArray();
} catch (IOException e) {
throw new IllegalStateException();
}
}
public boolean equals(Object o) {
if (!(o instanceof SerializedFlowExecutionSnapshot)) {
return false;
@@ -186,12 +179,14 @@ class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot implements E
* Internal helper method to deserialize given flow execution data. Override if a custom serialization method is
* used.
* @param data serialized flow flow execution data
* @param classLoader the class loader to use to resolve classes during deserialization
* @return deserialized flow execution
* @throws IOException when something goes wrong during deserialization
* @throws ClassNotFoundException when required classes cannot be loaded
*/
protected FlowExecution deserialize(byte[] data) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
protected FlowExecution deserialize(byte[] data, ClassLoader classLoader) throws IOException,
ClassNotFoundException {
ObjectInputStream ois = new ConfigurableObjectInputStream(new ByteArrayInputStream(data), classLoader);
try {
return (FlowExecution) ois.readObject();
} finally {
@@ -229,4 +224,19 @@ class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot implements E
}
return baos.toByteArray();
}
private static class ConfigurableObjectInputStream extends ObjectInputStream {
private final ClassLoader classLoader;
public ConfigurableObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException {
super(in);
this.classLoader = classLoader;
}
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
return ClassUtils.forName(desc.getName(), this.classLoader);
}
}
}

View File

@@ -15,11 +15,15 @@
*/
package org.springframework.webflow.execution.repository.snapshot;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import org.springframework.util.Assert;
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.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException;
/**
* A factory that creates new instances of flow execution snapshots based on standard Java serialization.
@@ -29,11 +33,25 @@ import org.springframework.webflow.execution.FlowExecution;
*/
public class SerializedFlowExecutionSnapshotFactory implements FlowExecutionSnapshotFactory {
/**
* Flag to toggle snapshot compression; compression is on by default.
*/
private FlowExecutionFactory flowExecutionFactory;
private FlowDefinitionLocator flowDefinitionLocator;
private boolean compress = true;
/**
* Creates a new serialized flow execution snapshot factory
* @param flowDefinitionLocator the flow definition locator
* @param flowExecutionFactory the flow execution factory
*/
public SerializedFlowExecutionSnapshotFactory(FlowExecutionFactory flowExecutionFactory,
FlowDefinitionLocator flowDefinitionLocator) {
Assert.notNull(flowExecutionFactory, "The FlowExecutionFactory to restore transient flow state is required");
Assert.notNull(flowDefinitionLocator, "The FlowDefinitionLocator to restore FlowDefinitions is required");
this.flowExecutionFactory = flowExecutionFactory;
this.flowDefinitionLocator = flowDefinitionLocator;
}
/**
* Returns whether or not the snapshots should be compressed.
*/
@@ -52,18 +70,19 @@ public class SerializedFlowExecutionSnapshotFactory implements FlowExecutionSnap
return new SerializedFlowExecutionSnapshot(flowExecution, compress);
}
public FlowExecutionSnapshot restoreSnapshot(byte[] bytes) throws SnapshotUnmarshalException {
public FlowExecution restoreExecution(FlowExecutionSnapshot snapshot, String flowId, FlowExecutionKey key,
MutableAttributeMap conversationScope, FlowExecutionKeyFactory keyFactory)
throws FlowExecutionRestorationFailureException {
SerializedFlowExecutionSnapshot snapshotImpl = (SerializedFlowExecutionSnapshot) snapshot;
FlowDefinition def = flowDefinitionLocator.getFlowDefinition(flowId);
FlowExecution execution;
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
try {
return (FlowExecutionSnapshot) ois.readObject();
} finally {
ois.close();
}
} catch (IOException e) {
throw new SnapshotUnmarshalException("IO problem while creating the flow execution snapshot", e);
} catch (ClassNotFoundException e) {
throw new SnapshotUnmarshalException("Class not found while creating the flow execution snapshot", e);
execution = snapshotImpl.unmarshal(def.getClassLoader());
} catch (SnapshotUnmarshalException e) {
throw new FlowExecutionRestorationFailureException(key, e);
}
flowExecutionFactory.restoreFlowExecution(execution, def, key, conversationScope, flowDefinitionLocator);
return execution;
}
}

View File

@@ -28,7 +28,6 @@ import org.springframework.webflow.conversation.ConversationId;
import org.springframework.webflow.conversation.ConversationManager;
import org.springframework.webflow.conversation.ConversationParameters;
import org.springframework.webflow.conversation.NoSuchConversationException;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionFactory;
@@ -62,20 +61,15 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
private ConversationManager conversationManager;
private FlowExecutionStateRestorer executionStateRestorer;
private boolean alwaysGenerateNewNextKey = true;
/**
* Constructor for use in subclasses.
* @param conversationManager the conversation manager to use
*/
protected AbstractFlowExecutionRepository(ConversationManager conversationManager,
FlowExecutionStateRestorer executionStateRestorer) {
protected AbstractFlowExecutionRepository(ConversationManager conversationManager) {
Assert.notNull(conversationManager, "The conversation manager is required");
Assert.notNull(executionStateRestorer, "The execution state restorer is required");
this.conversationManager = conversationManager;
this.executionStateRestorer = executionStateRestorer;
}
/**
@@ -85,13 +79,6 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
return conversationManager;
}
/**
* The flow execution state restorer for restoring transient execution state.
*/
public FlowExecutionStateRestorer getExecutionStateRestorer() {
return executionStateRestorer;
}
/**
* The flag indicating if a new {@link FlowExecutionKey} should always be generated before each put call.
*/
@@ -107,6 +94,8 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
this.alwaysGenerateNewNextKey = alwaysGenerateNewNextKey;
}
// implementing flow execution key factory
public FlowExecutionKey getKey(FlowExecution execution) {
if (execution.getKey() == null) {
Conversation conversation = beginConversation(execution);
@@ -116,6 +105,8 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
}
}
// implementing flow execution repository
public FlowExecutionKey parseFlowExecutionKey(String encodedKey) throws FlowExecutionRepositoryException {
if (!StringUtils.hasText(encodedKey)) {
throw new BadlyFormattedFlowExecutionKeyException(encodedKey,
@@ -188,27 +179,6 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
}
}
/**
* Returns the transient state of the flow execution after potential deserialization.
* @param execution the flow execution
* @param key the flow execution key
* @param conversation the governing conversation where the execution is stored
*/
protected FlowExecution restoreTransientState(FlowExecution execution, FlowExecutionKey key,
Conversation conversation) {
MutableAttributeMap conversationScope = (MutableAttributeMap) conversation.getAttribute("scope");
return executionStateRestorer.restoreState(execution, key, conversationScope, this);
}
/**
* Puts the value of conversation scope in the conversation object.
* @param flowExecution the flow execution holding a reference to conversation scope
* @param conversation the conversation where conversation scope is stored
*/
protected void putConversationScope(FlowExecution flowExecution, Conversation conversation) {
conversation.putAttribute("scope", flowExecution.getConversationScope());
}
/**
* Assert that a flow execution key has been assigned to the execution.
* @param execution the flow execution

View File

@@ -16,9 +16,10 @@
package org.springframework.webflow.execution.repository.support;
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.FlowExecutionKey;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
/**
* A strategy used by repositories to restore transient flow execution state during execution restoration.
@@ -29,15 +30,15 @@ public interface FlowExecutionStateRestorer {
/**
* Restore the transient state of the flow execution.
* @param flowExecution the (potentially deserialized) flow execution
* @param execution the flow execution, newly deserialized and needing restoration
* @param definition the root flow definition for the execution, typically not part of the serialized form
* @param key the flow execution key, typically not part of the serialized form
* @param conversationScope the execution's conversation scope, which is typically not part of the serialized form
* since it could be shared by multiple physical flow execution <i>copies</i> all sharing the same logical
* conversation
* @param keyFactory the flow execution key factory the flow execution will use to assign itself a new key at a
* later date (typically the repository itself)
* @param subflowDefinitionLocator for locating the definitions of any subflows started by the execution
* @return the restored flow execution
*/
public FlowExecution restoreState(FlowExecution flowExecution, FlowExecutionKey key,
MutableAttributeMap conversationScope, FlowExecutionKeyFactory keyFactory);
public FlowExecution restoreState(FlowExecution execution, FlowDefinition definition, FlowExecutionKey key,
MutableAttributeMap conversationScope, FlowDefinitionLocator subflowDefinitionLocator);
}

View File

@@ -132,6 +132,10 @@ public class FlowDefinitionRegistryImplTests extends TestCase {
return null;
}
public ClassLoader getClassLoader() {
return null;
}
public ApplicationContext getApplicationContext() {
return null;
}
@@ -169,6 +173,10 @@ public class FlowDefinitionRegistryImplTests extends TestCase {
return null;
}
public ClassLoader getClassLoader() {
return null;
}
public ApplicationContext getApplicationContext() {
return null;
}

View File

@@ -19,6 +19,10 @@ import junit.framework.TestCase;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException;
import org.springframework.webflow.engine.EndState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.RequestControlContext;
@@ -33,6 +37,7 @@ import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
import org.springframework.webflow.test.MockExternalContext;
import org.springframework.webflow.test.MockFlowExecutionKey;
/**
* Test case for {@link FlowExecutionImplFactory}.
@@ -105,25 +110,7 @@ public class FlowExecutionImplFactoryTests extends TestCase {
}
};
flowDefinition.setStartState(state);
factory.setExecutionKeyFactory(new FlowExecutionKeyFactory() {
public FlowExecutionKey getKey(FlowExecution execution) {
getKeyCalled = true;
return null;
}
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
removeAllSnapshotsCalled = true;
}
public void removeFlowExecutionSnapshot(FlowExecution execution) {
removeSnapshotCalled = true;
}
public void updateFlowExecutionSnapshot(FlowExecution execution) {
updateSnapshotCalled = true;
}
});
factory.setExecutionKeyFactory(new MockFlowExecutionKeyFactory());
FlowExecution execution = factory.createFlowExecution(flowDefinition);
execution.start(null, new MockExternalContext());
assertTrue(getKeyCalled);
@@ -132,4 +119,75 @@ public class FlowExecutionImplFactoryTests extends TestCase {
assertTrue(updateSnapshotCalled);
assertNull(execution.getKey());
}
public void testRestoreExecutionState() {
FlowExecutionImpl flowExecution = (FlowExecutionImpl) factory.createFlowExecution(flowDefinition);
LocalAttributeMap executionAttributes = new LocalAttributeMap();
factory.setExecutionAttributes(executionAttributes);
FlowExecutionListener listener = new FlowExecutionListenerAdapter() {
};
factory.setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(listener));
MockFlowExecutionKeyFactory keyFactory = new MockFlowExecutionKeyFactory();
factory.setExecutionKeyFactory(keyFactory);
FlowExecutionKey flowExecutionKey = new MockFlowExecutionKey("e1s1");
LocalAttributeMap conversationScope = new LocalAttributeMap();
SimpleFlowDefinitionLocator locator = new SimpleFlowDefinitionLocator();
FlowSessionImpl session1 = new FlowSessionImpl();
session1.setFlowId("flow");
session1.setStateId("end");
FlowSessionImpl session2 = new FlowSessionImpl();
session2.setFlowId("child");
session2.setStateId("state");
flowExecution.getFlowSessions().add(session1);
flowExecution.getFlowSessions().add(session2);
factory.restoreFlowExecution(flowExecution, flowDefinition, flowExecutionKey, conversationScope, locator);
assertSame(executionAttributes, flowExecution.getAttributes());
assertEquals(1, flowExecution.getListeners().length);
assertSame(listener, flowExecution.getListeners()[0]);
assertSame(flowExecutionKey, flowExecution.getKey());
assertSame(keyFactory, flowExecution.getKeyFactory());
assertSame(conversationScope, flowExecution.getConversationScope());
assertSame(((FlowSession) flowExecution.getFlowSessions().get(0)).getDefinition(), flowDefinition);
assertSame(((FlowSession) flowExecution.getFlowSessions().get(0)).getDefinition().getState("end"),
flowDefinition.getState("end"));
assertSame(((FlowSession) flowExecution.getFlowSessions().get(1)).getDefinition(), locator.child);
assertSame(((FlowSession) flowExecution.getFlowSessions().get(1)).getDefinition().getState("state"),
locator.child.getState("state"));
}
private class MockFlowExecutionKeyFactory implements FlowExecutionKeyFactory {
public FlowExecutionKey getKey(FlowExecution execution) {
getKeyCalled = true;
return null;
}
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
removeAllSnapshotsCalled = true;
}
public void removeFlowExecutionSnapshot(FlowExecution execution) {
removeSnapshotCalled = true;
}
public void updateFlowExecutionSnapshot(FlowExecution execution) {
updateSnapshotCalled = true;
}
}
private class SimpleFlowDefinitionLocator implements FlowDefinitionLocator {
Flow child = new Flow("child");
public SimpleFlowDefinitionLocator() {
new EndState(child, "state");
}
public FlowDefinition getFlowDefinition(String flowId) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException {
if (flowId.equals(child.getId())) {
return child;
} else {
throw new IllegalArgumentException(flowId.toString());
}
}
}
}

View File

@@ -1,105 +0,0 @@
package org.springframework.webflow.engine.impl;
import java.util.LinkedList;
import junit.framework.TestCase;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.FlowExecutionListener;
import org.springframework.webflow.execution.MockFlowExecutionListener;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
import org.springframework.webflow.test.GeneratedFlowExecutionKey;
public class FlowExecutionStateRestorerImplTests extends TestCase {
private SimpleFlowDefinitionLocator definitionLocator;
private FlowExecutionImplStateRestorer stateRestorer;
private LocalAttributeMap executionAttributes = new LocalAttributeMap();
private FlowExecutionListener listener = new MockFlowExecutionListener();
private FlowExecutionListenerLoader executionListenerLoader = new StaticFlowExecutionListenerLoader(listener);
GeneratedFlowExecutionKey newKey = new GeneratedFlowExecutionKey();
private FlowExecutionKeyFactory executionKeyFactory = new FlowExecutionKeyFactory() {
public FlowExecutionKey getKey(FlowExecution execution) {
return newKey;
}
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
}
public void removeFlowExecutionSnapshot(FlowExecution execution) {
}
public void updateFlowExecutionSnapshot(FlowExecution execution) {
}
};
protected void setUp() {
definitionLocator = new SimpleFlowDefinitionLocator();
stateRestorer = new FlowExecutionImplStateRestorer(definitionLocator);
stateRestorer.setExecutionAttributes(executionAttributes);
stateRestorer.setExecutionListenerLoader(executionListenerLoader);
}
public void testRestoreStateNoSessions() {
FlowExecutionKey key = new GeneratedFlowExecutionKey();
LocalAttributeMap conversationScope = new LocalAttributeMap();
FlowExecutionImpl execution = new FlowExecutionImpl("parent", new LinkedList());
stateRestorer.restoreState(execution, key, conversationScope, executionKeyFactory);
assertSame(definitionLocator.parent, execution.getDefinition());
assertTrue(execution.getFlowSessions().isEmpty());
assertSame(conversationScope, execution.getConversationScope());
assertSame(key, execution.getKey());
assertSame(executionAttributes, execution.getAttributes());
assertEquals(1, execution.getListeners().length);
execution.assignKey();
assertEquals(newKey, execution.getKey());
}
public void testRestoreStateFlowDefinitionIdNotSet() {
FlowExecutionKey key = new GeneratedFlowExecutionKey();
LocalAttributeMap conversationScope = new LocalAttributeMap();
FlowExecutionImpl execution = new FlowExecutionImpl();
try {
stateRestorer.restoreState(execution, key, conversationScope, executionKeyFactory);
fail("Should've failed");
} catch (IllegalStateException e) {
}
}
public void testRestoreStateFlowSessionsNotSet() {
FlowExecutionKey key = new GeneratedFlowExecutionKey();
LocalAttributeMap conversationScope = new LocalAttributeMap();
FlowExecutionImpl execution = new FlowExecutionImpl("parent", null);
try {
stateRestorer.restoreState(execution, key, conversationScope, executionKeyFactory);
fail("Should've failed");
} catch (IllegalStateException e) {
}
}
private class SimpleFlowDefinitionLocator implements FlowDefinitionLocator {
Flow parent = new Flow("parent");
Flow child = new Flow("child");
public FlowDefinition getFlowDefinition(String flowId) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException {
if (flowId.equals(parent.getId())) {
return parent;
} else if (flowId.equals(child.getId())) {
return child;
} else {
throw new IllegalArgumentException(flowId.toString());
}
}
}
}

View File

@@ -21,19 +21,19 @@ import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.RequestControlContext;
import org.springframework.webflow.engine.State;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException;
import org.springframework.webflow.execution.repository.FlowExecutionLock;
import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException;
import org.springframework.webflow.execution.repository.snapshot.SerializedFlowExecutionSnapshotFactory;
import org.springframework.webflow.test.MockExternalContext;
public class DefaultFlowExecutionRepositoryTests extends TestCase {
private Flow flow;
private ConversationManager conversationManager;
private FlowExecutionImplStateRestorer stateRestorer;
private DefaultFlowExecutionRepository repository;
protected void setUp() throws Exception {
@@ -44,13 +44,16 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
}
};
conversationManager = new StubConversationManager();
stateRestorer = new FlowExecutionImplStateRestorer(new FlowDefinitionLocator() {
FlowDefinitionLocator locator = new FlowDefinitionLocator() {
public FlowDefinition getFlowDefinition(String flowId) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException {
return flow;
}
});
repository = new DefaultFlowExecutionRepository(conversationManager, stateRestorer);
};
FlowExecutionFactory executionFactory = new FlowExecutionImplFactory();
SerializedFlowExecutionSnapshotFactory snapshotFactory = new SerializedFlowExecutionSnapshotFactory(
executionFactory, locator);
repository = new DefaultFlowExecutionRepository(conversationManager, snapshotFactory);
}
public void testParseFlowExecutionKey() {

View File

@@ -11,19 +11,15 @@ import org.springframework.webflow.engine.RequestControlContext;
import org.springframework.webflow.engine.State;
import org.springframework.webflow.engine.impl.FlowExecutionImpl;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
import org.springframework.webflow.test.MockExternalContext;
import org.springframework.webflow.test.MockFlowExecutionKeyFactory;
public class SerializedFlowExecutionSnapshotFactoryTests extends TestCase {
private Flow flow;
private SerializedFlowExecutionSnapshotFactory factory;
private FlowExecutionStateRestorer stateRestorer;
private FlowExecutionKeyFactory executionKeyFactory;
private FlowExecutionImplFactory executionFactory;
public void setUp() {
flow = new Flow("myFlow");
@@ -31,46 +27,31 @@ public class SerializedFlowExecutionSnapshotFactoryTests extends TestCase {
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
}
};
factory = new SerializedFlowExecutionSnapshotFactory();
stateRestorer = new FlowExecutionImplStateRestorer(new FlowDefinitionLocator() {
FlowDefinitionLocator locator = new FlowDefinitionLocator() {
public FlowDefinition getFlowDefinition(String flowId) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException {
return flow;
}
});
executionKeyFactory = new MockFlowExecutionKeyFactory();
};
executionFactory = new FlowExecutionImplFactory();
executionFactory.setExecutionKeyFactory(executionKeyFactory);
factory = new SerializedFlowExecutionSnapshotFactory(executionFactory, locator);
}
public void testCreateSnapshot() {
FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow);
FlowExecutionImpl flowExecution = (FlowExecutionImpl) executionFactory.createFlowExecution(flow);
flowExecution.start(null, new MockExternalContext());
flowExecution.getActiveSession().getScope().put("foo", "bar");
FlowExecutionSnapshot snapshot = factory.createSnapshot(flowExecution);
FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) snapshot.unmarshal();
FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) factory.restoreExecution(snapshot, "myFlow", null,
flowExecution.getConversationScope(), executionKeyFactory);
assertNotSame(flowExecution, flowExecution2);
stateRestorer.restoreState(flowExecution2, null, flowExecution.getConversationScope(), executionKeyFactory);
assertEquals(flowExecution.getDefinition().getId(), flowExecution2.getDefinition().getId());
assertEquals(flowExecution.getActiveSession().getScope().get("foo"), flowExecution2.getActiveSession()
.getScope().get("foo"));
assertEquals(flowExecution.getActiveSession().getState().getId(), flowExecution2.getActiveSession().getState()
.getId());
}
public void testRestoreSnapshot() {
FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow);
flowExecution.start(null, new MockExternalContext());
flowExecution.getActiveSession().getScope().put("foo", "bar");
FlowExecutionSnapshot snapshot = factory.createSnapshot(flowExecution);
byte[] bytes = snapshot.toByteArray();
FlowExecutionSnapshot continuation2 = factory.restoreSnapshot(bytes);
assertEquals(snapshot, continuation2);
FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) continuation2.unmarshal();
assertNotSame(flowExecution, flowExecution2);
stateRestorer.restoreState(flowExecution2, null, flowExecution.getConversationScope(), executionKeyFactory);
assertEquals(flowExecution.getDefinition().getId(), flowExecution2.getDefinition().getId());
assertEquals(flowExecution.getActiveSession().getScope().get("foo"), flowExecution2.getActiveSession()
.getScope().get("foo"));
assertEquals(flowExecution.getActiveSession().getState().getId(), flowExecution2.getActiveSession().getState()
.getId());
assertNull(flowExecution2.getKey());
assertSame(flowExecution.getConversationScope(), flowExecution2.getConversationScope());
}
}