flow appliction context classloader used on deserialization
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() + "']";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user