From daccb49a1a38e984e4402fe2449078dac4fdea02 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 5 May 2008 02:53:16 +0000 Subject: [PATCH] flow appliction context classloader used on deserialization --- .../config/FlowExecutorFactoryBean.java | 35 +++--- .../webflow/conversation/Conversation.java | 7 +- .../impl/ConversationContainer.java | 3 + .../webflow/definition/FlowDefinition.java | 13 ++- .../springframework/webflow/engine/Flow.java | 9 ++ .../engine/impl/FlowExecutionImpl.java | 51 +++------ .../engine/impl/FlowExecutionImplFactory.java | 84 +++++++++++++- .../FlowExecutionImplServicesConfigurer.java | 67 ----------- .../impl/FlowExecutionImplStateRestorer.java | 85 -------------- .../webflow/engine/impl/FlowSessionImpl.java | 26 ++++- .../execution/FlowExecutionFactory.java | 17 +++ .../impl/DefaultFlowExecutionRepository.java | 42 ++----- ...ctSnapshottingFlowExecutionRepository.java | 46 +++++--- .../snapshot/FlowExecutionSnapshot.java | 17 +-- .../FlowExecutionSnapshotFactory.java | 20 +++- .../SerializedFlowExecutionSnapshot.java | 50 +++++---- ...erializedFlowExecutionSnapshotFactory.java | 55 ++++++--- .../AbstractFlowExecutionRepository.java | 40 +------ .../support/FlowExecutionStateRestorer.java | 13 ++- .../FlowDefinitionRegistryImplTests.java | 8 ++ .../impl/FlowExecutionImplFactoryTests.java | 96 ++++++++++++---- .../FlowExecutionStateRestorerImplTests.java | 105 ------------------ .../DefaultFlowExecutionRepositoryTests.java | 13 ++- ...izedFlowExecutionSnapshotFactoryTests.java | 41 ++----- 24 files changed, 411 insertions(+), 532 deletions(-) delete mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java delete mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplStateRestorer.java delete mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java index e0cd8195..9379e778 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowExecutorFactoryBean.java @@ -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; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/conversation/Conversation.java b/spring-webflow/src/main/java/org/springframework/webflow/conversation/Conversation.java index 9cdf255c..37977302 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/conversation/Conversation.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/conversation/Conversation.java @@ -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(); + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java b/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java index c400f10b..869e6580 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java @@ -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 diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/FlowDefinition.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/FlowDefinition.java index d44fbab1..949ada7c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/FlowDefinition.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/FlowDefinition.java @@ -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. *

* 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(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java index 4c35471c..cb6343ca 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java @@ -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; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java index c74ac485..5a7105a9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImpl.java @@ -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; *

* 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. *

- * 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. *

- * 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. *

- * 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. *

- * 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() + "']"; } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java index a8023ccc..1cf6721f 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactory.java @@ -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() { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java deleted file mode 100644 index 7901419c..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplServicesConfigurer.java +++ /dev/null @@ -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())); - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplStateRestorer.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplStateRestorer.java deleted file mode 100644 index 5b36c81c..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowExecutionImplStateRestorer.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java index 8ffc38d5..54ea8c23 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/impl/FlowSessionImpl.java @@ -45,24 +45,24 @@ class FlowSessionImpl implements FlowSession, Externalizable { /** * The flow definition (a singleton). *

- * 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. *

- * 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 /** diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionFactory.java index 5f1fb50d..5772b771 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/FlowExecutionFactory.java @@ -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 copies 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); } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java index b9a2e28c..7bacdc78 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepository.java @@ -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 one to {@link #getMaxSnapshots() max} 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 diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/AbstractSnapshottingFlowExecutionRepository.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/AbstractSnapshottingFlowExecutionRepository.java index e6db7379..7afc4fb3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/AbstractSnapshottingFlowExecutionRepository.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/AbstractSnapshottingFlowExecutionRepository.java @@ -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()); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshot.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshot.java index 5150c851..b98aa0c7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshot.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshot.java @@ -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(); - } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshotFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshotFactory.java index 34f46b6e..15e6a445 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshotFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/FlowExecutionSnapshotFactory.java @@ -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; } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java index 84cba750..04a91b7c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java @@ -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); + } + + } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactory.java index e42db193..05f867ca 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactory.java @@ -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; } + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/AbstractFlowExecutionRepository.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/AbstractFlowExecutionRepository.java index 564b3416..3c523bac 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/AbstractFlowExecutionRepository.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/AbstractFlowExecutionRepository.java @@ -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 diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/FlowExecutionStateRestorer.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/FlowExecutionStateRestorer.java index 63b53212..2c8570ff 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/FlowExecutionStateRestorer.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/support/FlowExecutionStateRestorer.java @@ -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 copies 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); } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java index b2661963..a5f49c47 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java @@ -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; } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java index 2d23fc0e..c0813beb 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplFactoryTests.java @@ -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()); + } + } + } } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java deleted file mode 100644 index 76dfb570..00000000 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionStateRestorerImplTests.java +++ /dev/null @@ -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()); - } - } - } -} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java index ea449ba3..7769f1d1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/impl/DefaultFlowExecutionRepositoryTests.java @@ -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() { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactoryTests.java b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactoryTests.java index 2cab2e73..a3532bf9 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactoryTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshotFactoryTests.java @@ -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()); } } \ No newline at end of file