conversation/continuation terms renamed to execution/snapshot terms for simplicity / improved lexicon
This commit is contained in:
@@ -175,7 +175,7 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, I
|
||||
DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(conversationManager,
|
||||
executionStateRestorer);
|
||||
if (maxFlowExecutionSnapshots != null) {
|
||||
repository.setMaxContinuations(maxFlowExecutionSnapshots.intValue());
|
||||
repository.setMaxSnapshots(maxFlowExecutionSnapshots.intValue());
|
||||
}
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -15,60 +15,72 @@
|
||||
*/
|
||||
package org.springframework.webflow.execution.repository.continuation;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.webflow.conversation.ConversationManager;
|
||||
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 create flow execution snapshots called "continuations".
|
||||
* Base class for repositories that take flow execution snapshots using a {@link FlowExecutionSnapshotFactory}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class AbstractFlowExecutionContinuationRepository extends AbstractFlowExecutionRepository {
|
||||
public abstract class AbstractSnapshottingFlowExecutionRepository extends AbstractFlowExecutionRepository {
|
||||
|
||||
/**
|
||||
* The continuation factory that will be used to create new continuations to be added to active conversations.
|
||||
* The factory to use to take flow execution snapshots.
|
||||
*/
|
||||
private FlowExecutionContinuationFactory continuationFactory;
|
||||
private FlowExecutionSnapshotFactory executionSnapshotFactory;
|
||||
|
||||
/**
|
||||
* Creates a new continuation repository.
|
||||
* Creates a new snapshotting flow execution repository.
|
||||
* @param conversationManager the conversation manager
|
||||
* @param executionStateRestorer the execution state restorer
|
||||
* @param continuationFactory the continuation factory
|
||||
* @param executionSnapshotFactory the execution snapshot factory
|
||||
*/
|
||||
public AbstractFlowExecutionContinuationRepository(ConversationManager conversationManager,
|
||||
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionContinuationFactory continuationFactory) {
|
||||
public AbstractSnapshottingFlowExecutionRepository(ConversationManager conversationManager,
|
||||
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionSnapshotFactory executionSnapshotFactory) {
|
||||
super(conversationManager, executionStateRestorer);
|
||||
Assert.notNull(continuationFactory, "The flow execution continuation factory is required");
|
||||
this.continuationFactory = continuationFactory;
|
||||
Assert.notNull(executionSnapshotFactory, "The flow execution snapshot factory is required");
|
||||
this.executionSnapshotFactory = executionSnapshotFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured flow execution snapshot factory.
|
||||
* @return the snapshot factory
|
||||
*/
|
||||
public FlowExecutionContinuationFactory getContinuationFactory() {
|
||||
return continuationFactory;
|
||||
public FlowExecutionSnapshotFactory getExecutionSnapshotFactory() {
|
||||
return executionSnapshotFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a new continuation snapshot.
|
||||
* @param flowExecution the execution to snapshot
|
||||
* @return the continuation snapshot
|
||||
* Returns the snapshotId portion of the flow execution key.
|
||||
* @param key the execution key
|
||||
*/
|
||||
protected FlowExecutionContinuation snapshot(FlowExecution flowExecution) {
|
||||
return continuationFactory.createContinuation(flowExecution);
|
||||
protected Serializable getSnapshotId(FlowExecutionKey key) {
|
||||
return ((CompositeFlowExecutionKey) key).getSnapshotId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a new flow execution snapshot.
|
||||
* @param flowExecution the execution to snapshot
|
||||
* @return the snapshot
|
||||
*/
|
||||
protected FlowExecutionSnapshot snapshot(FlowExecution flowExecution) {
|
||||
return executionSnapshotFactory.createSnapshot(flowExecution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a serialized flow execution.
|
||||
* @param continuationBytes the flow execution snapshot byte array
|
||||
* @param snapshotBytes the flow execution snapshot byte array
|
||||
* @return the deserialized flow execution
|
||||
*/
|
||||
protected FlowExecution deserializeExecution(byte[] continuationBytes) {
|
||||
return continuationFactory.restoreContinuation(continuationBytes).unmarshal();
|
||||
protected FlowExecution deserializeExecution(byte[] snapshotBytes) {
|
||||
return executionSnapshotFactory.restoreSnapshot(snapshotBytes).unmarshal();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.execution.repository.continuation;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
|
||||
|
||||
/**
|
||||
* Thrown when no flow execution continuation exists within a continuation group with a particular id. This might occur
|
||||
* if the continuation has expired or was explictly invalidated but a client's browser page cache still references it.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public class ContinuationNotFoundException extends FlowExecutionRepositoryException {
|
||||
|
||||
/**
|
||||
* The unique continuation identifier that was not found.
|
||||
*/
|
||||
private Serializable continuationId;
|
||||
|
||||
/**
|
||||
* Creates a continuation not found exception.
|
||||
* @param continuationId the continuation id that could not be found
|
||||
*/
|
||||
public ContinuationNotFoundException(Serializable continuationId) {
|
||||
super("No flow execution continuation could be found in this group with id '" + continuationId
|
||||
+ "' -- perhaps the continuation has expired or has been invalidated? ");
|
||||
this.continuationId = continuationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuation id that could not be found.
|
||||
*/
|
||||
public Serializable getContinuationId() {
|
||||
return continuationId;
|
||||
}
|
||||
}
|
||||
@@ -22,22 +22,22 @@ import org.springframework.webflow.execution.FlowExecution;
|
||||
/**
|
||||
* A snapshot of a flow execution that can be restored from and serialized to a byte array.
|
||||
*
|
||||
* @see FlowExecutionContinuationFactory
|
||||
* @see FlowExecutionSnapshotFactory
|
||||
*
|
||||
* @author Erwin Vervaet
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class FlowExecutionContinuation implements Serializable {
|
||||
public abstract class FlowExecutionSnapshot implements Serializable {
|
||||
|
||||
/**
|
||||
* Restores the flow execution wrapped in this continuation.
|
||||
* Returns underlying flow execution object.
|
||||
* @return the unmarshalled flow execution
|
||||
* @throws ContinuationUnmarshalException when there is a problem unmarshalling this continuation
|
||||
* @throws SnapshotUnmarshalException when there is a problem unmarshalling the execution
|
||||
*/
|
||||
public abstract FlowExecution unmarshal() throws ContinuationUnmarshalException;
|
||||
public abstract FlowExecution unmarshal() throws SnapshotUnmarshalException;
|
||||
|
||||
/**
|
||||
* Converts this continuation to a byte array for convenient serialization.
|
||||
* Converts this snapshot to a byte array for convenient serialization.
|
||||
* @return this as a byte array
|
||||
*/
|
||||
public abstract byte[] toByteArray();
|
||||
@@ -18,27 +18,26 @@ package org.springframework.webflow.execution.repository.continuation;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
|
||||
/**
|
||||
* A factory for creating different {@link FlowExecutionContinuation} implementations.
|
||||
* A factory for creating different {@link FlowExecutionSnapshot} implementations.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public interface FlowExecutionContinuationFactory {
|
||||
public interface FlowExecutionSnapshotFactory {
|
||||
|
||||
/**
|
||||
* Creates a new flow execution continuation for given flow execution.
|
||||
* Takes a snapshot of the flow execution.
|
||||
* @param flowExecution the flow execution
|
||||
* @return the continuation
|
||||
* @throws ContinuationCreationException when the continuation cannot be created
|
||||
* @return the new snapshot
|
||||
* @throws SnapshotCreationException if the snapshot could not be created
|
||||
*/
|
||||
public FlowExecutionContinuation createContinuation(FlowExecution flowExecution)
|
||||
throws ContinuationCreationException;
|
||||
public FlowExecutionSnapshot createSnapshot(FlowExecution flowExecution) throws SnapshotCreationException;
|
||||
|
||||
/**
|
||||
* Restore a flow execution continuation object from the provided byte array.
|
||||
* @param bytes the flow execution byte array
|
||||
* @return the continuation
|
||||
* @throws ContinuationUnmarshalException when the continuation cannot be restored
|
||||
* 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
|
||||
*/
|
||||
public FlowExecutionContinuation restoreContinuation(byte[] bytes) throws ContinuationUnmarshalException;
|
||||
public FlowExecutionSnapshot restoreSnapshot(byte[] bytes) throws SnapshotUnmarshalException;
|
||||
}
|
||||
@@ -32,73 +32,66 @@ import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
|
||||
/**
|
||||
* A continuation implementation that is based on standard Java serialization, created by a
|
||||
* {@link SerializedFlowExecutionContinuationFactory}.
|
||||
* A snapshot implementation that is based on standard Java serialization, created by a
|
||||
* {@link SerializedFlowExecutionSnapshotFactory}.
|
||||
*
|
||||
* @see SerializedFlowExecutionContinuationFactory
|
||||
* @see SerializedFlowExecutionSnapshotFactory
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
class SerializedFlowExecutionContinuation extends FlowExecutionContinuation implements Externalizable {
|
||||
class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot implements Externalizable {
|
||||
|
||||
/**
|
||||
* The serialized flow execution.
|
||||
*/
|
||||
private byte[] flowExecutionData;
|
||||
|
||||
/**
|
||||
* Whether or not the flow execution byte array is compressed.
|
||||
*/
|
||||
private boolean compressed;
|
||||
|
||||
/**
|
||||
* Default constructor necessary for {@link Externalizable} custom serialization semantics. Should not be called by
|
||||
* application code.
|
||||
*/
|
||||
public SerializedFlowExecutionContinuation() {
|
||||
public SerializedFlowExecutionSnapshot() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new serialized flow execution continuation. This will marshall given flow execution into a serialized
|
||||
* continuation form.
|
||||
* Creates a new serialized flow execution snapshot.
|
||||
* @param flowExecution the flow execution
|
||||
* @param compress whether or not the flow execution should be compressed
|
||||
* @param compress whether or not to apply compression during snapshotting
|
||||
*/
|
||||
public SerializedFlowExecutionContinuation(FlowExecution flowExecution, boolean compress)
|
||||
throws ContinuationCreationException {
|
||||
public SerializedFlowExecutionSnapshot(FlowExecution flowExecution, boolean compress)
|
||||
throws SnapshotCreationException {
|
||||
try {
|
||||
flowExecutionData = serialize(flowExecution);
|
||||
if (compress) {
|
||||
flowExecutionData = compress(flowExecutionData);
|
||||
}
|
||||
} catch (NotSerializableException e) {
|
||||
throw new ContinuationCreationException(flowExecution, "Could not serialize flow execution; "
|
||||
throw new SnapshotCreationException(flowExecution, "Could not serialize flow execution; "
|
||||
+ "make sure all objects stored in flow or flash scope are serializable", e);
|
||||
} catch (IOException e) {
|
||||
throw new ContinuationCreationException(flowExecution,
|
||||
throw new SnapshotCreationException(flowExecution,
|
||||
"IOException thrown serializing flow execution -- this should not happen!", e);
|
||||
}
|
||||
this.compressed = compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the flow execution data in this continuation is compressed.
|
||||
* Returns whether or not the flow execution data in this snapshot is compressed.
|
||||
*/
|
||||
public boolean isCompressed() {
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public FlowExecution unmarshal() throws ContinuationUnmarshalException {
|
||||
public FlowExecution unmarshal() throws SnapshotUnmarshalException {
|
||||
try {
|
||||
return deserialize(getFlowExecutionData());
|
||||
} catch (IOException e) {
|
||||
throw new ContinuationUnmarshalException(
|
||||
"IOException thrown deserializing the flow execution stored in this continuation -- this should not happen!",
|
||||
throw new SnapshotUnmarshalException(
|
||||
"IOException thrown deserializing the flow execution stored in this snapshot -- this should not happen!",
|
||||
e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ContinuationUnmarshalException(
|
||||
"ClassNotFoundException thrown deserializing the flow execution stored in this continuation -- "
|
||||
throw new SnapshotUnmarshalException(
|
||||
"ClassNotFoundException thrown deserializing the flow execution stored in this snapshot -- "
|
||||
+ "This should not happen! Make sure there are no classloader issues. "
|
||||
+ "For example, perhaps the Web Flow system is being loaded by a classloader "
|
||||
+ "that is a parent of the classloader loading application classes?", e);
|
||||
@@ -122,10 +115,10 @@ class SerializedFlowExecutionContinuation extends FlowExecutionContinuation impl
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SerializedFlowExecutionContinuation)) {
|
||||
if (!(o instanceof SerializedFlowExecutionSnapshot)) {
|
||||
return false;
|
||||
}
|
||||
SerializedFlowExecutionContinuation c = (SerializedFlowExecutionContinuation) o;
|
||||
SerializedFlowExecutionSnapshot c = (SerializedFlowExecutionSnapshot) o;
|
||||
return Arrays.equals(flowExecutionData, c.flowExecutionData);
|
||||
}
|
||||
|
||||
@@ -156,7 +149,7 @@ class SerializedFlowExecutionContinuation extends FlowExecutionContinuation impl
|
||||
compressed = in.readBoolean();
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
// subclassing hooks
|
||||
|
||||
/**
|
||||
* Return the flow execution data in its raw byte[] form. Will decompress if necessary.
|
||||
@@ -22,49 +22,48 @@ import java.io.ObjectInputStream;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
|
||||
/**
|
||||
* A factory that creates new instances of flow execution continuations based on standard Java serialization.
|
||||
* A factory that creates new instances of flow execution snapshots based on standard Java serialization.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public class SerializedFlowExecutionContinuationFactory implements FlowExecutionContinuationFactory {
|
||||
public class SerializedFlowExecutionSnapshotFactory implements FlowExecutionSnapshotFactory {
|
||||
|
||||
/**
|
||||
* Flag to toggle continuation compression; compression is on by default.
|
||||
* Flag to toggle snapshot compression; compression is on by default.
|
||||
*/
|
||||
private boolean compress = true;
|
||||
|
||||
/**
|
||||
* Returns whether or not the continuations should be compressed.
|
||||
* Returns whether or not the snapshots should be compressed.
|
||||
*/
|
||||
public boolean getCompress() {
|
||||
return compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the continuations should be compressed.
|
||||
* Set whether or not the snapshots should be compressed.
|
||||
*/
|
||||
public void setCompress(boolean compress) {
|
||||
this.compress = compress;
|
||||
}
|
||||
|
||||
public FlowExecutionContinuation createContinuation(FlowExecution flowExecution)
|
||||
throws ContinuationCreationException {
|
||||
return new SerializedFlowExecutionContinuation(flowExecution, compress);
|
||||
public FlowExecutionSnapshot createSnapshot(FlowExecution flowExecution) throws SnapshotCreationException {
|
||||
return new SerializedFlowExecutionSnapshot(flowExecution, compress);
|
||||
}
|
||||
|
||||
public FlowExecutionContinuation restoreContinuation(byte[] bytes) throws ContinuationUnmarshalException {
|
||||
public FlowExecutionSnapshot restoreSnapshot(byte[] bytes) throws SnapshotUnmarshalException {
|
||||
try {
|
||||
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
|
||||
try {
|
||||
return (FlowExecutionContinuation) ois.readObject();
|
||||
return (FlowExecutionSnapshot) ois.readObject();
|
||||
} finally {
|
||||
ois.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ContinuationUnmarshalException("IO problem while creating a flow execution continuation", e);
|
||||
throw new SnapshotUnmarshalException("IO problem while creating the flow execution snapshot", e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ContinuationUnmarshalException("Class not found while creating a flow execution continuation", e);
|
||||
throw new SnapshotUnmarshalException("Class not found while creating the flow execution snapshot", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import org.springframework.webflow.execution.repository.FlowExecutionRepositoryE
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class ContinuationCreationException extends FlowExecutionRepositoryException {
|
||||
public class SnapshotCreationException extends FlowExecutionRepositoryException {
|
||||
|
||||
/**
|
||||
* The flow execution that could not be snapshotted.
|
||||
@@ -31,12 +31,12 @@ public class ContinuationCreationException extends FlowExecutionRepositoryExcept
|
||||
private FlowExecution flowExecution;
|
||||
|
||||
/**
|
||||
* Creates a new continuation creation exception.
|
||||
* Creates a new snapshot creation exception.
|
||||
* @param flowExecution the flow execution
|
||||
* @param message a descriptive message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public ContinuationCreationException(FlowExecution flowExecution, String message, Throwable cause) {
|
||||
public SnapshotCreationException(FlowExecution flowExecution, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.flowExecution = flowExecution;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2004-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.execution.repository.continuation;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
|
||||
|
||||
/**
|
||||
* Thrown when a flow execution snapshot cannot be found This usually occurs when the client references a snapshot that
|
||||
* has since been removed.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public class SnapshotNotFoundException extends FlowExecutionRepositoryException {
|
||||
|
||||
private Serializable snapshotId;
|
||||
|
||||
/**
|
||||
* Creates a snapshot not found exception.
|
||||
* @param snapshotId the snapshot id that could not be found
|
||||
*/
|
||||
public SnapshotNotFoundException(Serializable snapshotId) {
|
||||
super("No flow execution snapshot could be found with id '" + snapshotId
|
||||
+ "'; perhaps the snapshot has been removed? ");
|
||||
this.snapshotId = snapshotId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of the snapshot that was not found.
|
||||
*/
|
||||
public Serializable getSnapshotId() {
|
||||
return snapshotId;
|
||||
}
|
||||
}
|
||||
@@ -21,19 +21,19 @@ import org.springframework.webflow.execution.repository.FlowExecutionRepositoryE
|
||||
/**
|
||||
* Thrown when a FlowExecutionContinuation could not be deserialized into a FlowExecution.
|
||||
*
|
||||
* @see FlowExecutionContinuation
|
||||
* @see FlowExecutionSnapshot
|
||||
* @see FlowExecution
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class ContinuationUnmarshalException extends FlowExecutionRepositoryException {
|
||||
public class SnapshotUnmarshalException extends FlowExecutionRepositoryException {
|
||||
|
||||
/**
|
||||
* Creates a new flow execution unmarshalling exception.
|
||||
* @param message the exception message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public ContinuationUnmarshalException(String message, Throwable cause) {
|
||||
public SnapshotUnmarshalException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -20,94 +20,87 @@ import org.springframework.webflow.conversation.ConversationManager;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
import org.springframework.webflow.execution.FlowExecutionKey;
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException;
|
||||
import org.springframework.webflow.execution.repository.continuation.AbstractFlowExecutionContinuationRepository;
|
||||
import org.springframework.webflow.execution.repository.continuation.ContinuationNotFoundException;
|
||||
import org.springframework.webflow.execution.repository.continuation.ContinuationUnmarshalException;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionContinuation;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionContinuationFactory;
|
||||
import org.springframework.webflow.execution.repository.continuation.SerializedFlowExecutionContinuationFactory;
|
||||
import org.springframework.webflow.execution.repository.continuation.AbstractSnapshottingFlowExecutionRepository;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionSnapshot;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionSnapshotFactory;
|
||||
import org.springframework.webflow.execution.repository.continuation.SerializedFlowExecutionSnapshotFactory;
|
||||
import org.springframework.webflow.execution.repository.continuation.SnapshotNotFoundException;
|
||||
import org.springframework.webflow.execution.repository.continuation.SnapshotUnmarshalException;
|
||||
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
|
||||
|
||||
/**
|
||||
* Stores <i>one to many</i> flow execution continuations (snapshots) per conversation, where each continuation
|
||||
* represents a paused, restorable view-state of a flow execution snapshotted at a point in time.
|
||||
* The default flow execution repository implementation. Takes <i>one to {@link #getMaxSnapshots() max}</i> flow
|
||||
* execution snapshots, where each snapshot represents a copy of a {@link FlowExecution} taken at a point in time.
|
||||
* <p>
|
||||
* The set of active user conversations are managed by a {@link ConversationManager} implementation, which this
|
||||
* repository delegates to.
|
||||
* The set of active flow executions are managed by a {@link ConversationManager} implementation, which this repository
|
||||
* delegates to.
|
||||
* <p>
|
||||
* This repository is responsible for:
|
||||
* <ul>
|
||||
* <li>Beginning a new conversation when a new flow execution is made persistent. Each conversation is assigned a
|
||||
* unique conversation id which forms one part of the flow execution key.
|
||||
* <li>Associating a flow execution with that conversation by adding a {@link FlowExecutionContinuation} to a
|
||||
* continuation group.<br>
|
||||
* When a flow execution is placed in this repository a new continuation snapshot is created, assigned an id, and added
|
||||
* to the group. Each continuation logically represents a state of the conversation at a point in time <i>that can be
|
||||
* restored and continued</i>. These continuations can be restored to support users going back in their browser to
|
||||
* continue a conversation from a previous point.
|
||||
* <li>Ending existing conversations when persistent flow executions end, as part of a repository removal operation.
|
||||
* <li>Beginning a new {@link Conversation} when a {@link FlowExecution} is assigned a persistent key. Each
|
||||
* conversation is assigned a unique conversation id which forms one part of the flow execution key.
|
||||
* <li>Taking {@link FlowExecutionSnapshot execution snapshots} to persist flow execution state. A snapshot is a copy
|
||||
* of the execution created at a point in time <i>that can be restored and continued</i>. Snapshotting supports users
|
||||
* going back in their browser to continue their flow execution from a previoius point.
|
||||
* <li>Ending conversations when flow executions end.
|
||||
* </ul>
|
||||
* <p>
|
||||
* This repository implementation also provides support for <i>conversation invalidation after completion</i>, where
|
||||
* once a logical conversation completes (by one of its FlowExecution's reaching an end state), the entire conversation
|
||||
* (including all continuations) is invalidated. This prevents the possibility of duplicate submission after completion.
|
||||
* <p>
|
||||
* This repository implementation should be considered when you do have to support browser navigational button use, e.g.
|
||||
* you cannot lock down the browser and require that all navigational events to be routed explicitly through Spring Web
|
||||
* Flow.
|
||||
* This repository implementation also provides support for <i>execution invalidation after completion</i>, where once
|
||||
* a logical flow execution completes, it and all of its snapshots are removed. This cleans up memory and prevents the
|
||||
* possibility of duplicate submission after completion.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinuationRepository {
|
||||
public class DefaultFlowExecutionRepository extends AbstractSnapshottingFlowExecutionRepository {
|
||||
|
||||
/**
|
||||
* The conversation attribute that stores the "continuation group".
|
||||
* The conversation attribute that stores the group of flow execution snapshots.
|
||||
*/
|
||||
private static final String CONTINUATION_GROUP_ATTRIBUTE = "continuationGroup";
|
||||
private static final String SNAPSHOT_GROUP_ATTRIBUTE = "flowExecutionSnapshotGroup";
|
||||
|
||||
/**
|
||||
* The maximum number of continuations that can be active per conversation. The default is 30, which is high enough
|
||||
* not to interfere with the user experience of normal users using the back button, but low enough to avoid
|
||||
* excessive resource usage or easy denial of service attacks.
|
||||
* The maximum number of snapshots that can be taken per execution. The default is 30, which is generally high
|
||||
* enough not to interfere with the user experience of normal users using the back button, but low enough to avoid
|
||||
* excessive resource usage or denial of service attacks.
|
||||
*/
|
||||
private int maxContinuations = 30;
|
||||
private int maxSnapshots = 30;
|
||||
|
||||
/**
|
||||
* Create a new continuation based flow execution repository using the given state restorer and conversation
|
||||
* manager. Defaults to a {@link SerializedFlowExecutionContinuationFactory}.
|
||||
* 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 SerializedFlowExecutionContinuationFactory());
|
||||
super(conversationManager, executionStateRestorer, new SerializedFlowExecutionSnapshotFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new continuation based flow execution repository using the given state restorer, conversation manager,
|
||||
* and continuation factory.
|
||||
* 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 continuationFactory the continuation factory to use
|
||||
* @param executionSnapshotFactory the flow execution snapshot factory to use
|
||||
*/
|
||||
public DefaultFlowExecutionRepository(ConversationManager conversationManager,
|
||||
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionContinuationFactory continuationFactory) {
|
||||
super(conversationManager, executionStateRestorer, continuationFactory);
|
||||
FlowExecutionStateRestorer executionStateRestorer, FlowExecutionSnapshotFactory executionSnapshotFactory) {
|
||||
super(conversationManager, executionStateRestorer, executionSnapshotFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the max number of continuations allowed per conversation by this repository.
|
||||
* Returns the max number of snapshots allowed per flow execution by this repository.
|
||||
*/
|
||||
public int getMaxContinuations() {
|
||||
return maxContinuations;
|
||||
public int getMaxSnapshots() {
|
||||
return maxSnapshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of continuations allowed per conversation by this repository. Use -1 for unlimited. The
|
||||
* Sets the maximum number of snapshots allowed per flow execution by this repository. Use -1 for unlimited. The
|
||||
* default is 30.
|
||||
*/
|
||||
public void setMaxContinuations(int maxContinuations) {
|
||||
this.maxContinuations = maxContinuations;
|
||||
public void setMaxSnapshots(int maxSnapshots) {
|
||||
this.maxSnapshots = maxSnapshots;
|
||||
}
|
||||
|
||||
// implementing flow execution repository
|
||||
@@ -117,16 +110,16 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
logger.debug("Getting flow execution with key '" + key + "'");
|
||||
}
|
||||
Conversation conversation = getConversation(key);
|
||||
FlowExecutionContinuation snapshot;
|
||||
FlowExecutionSnapshot snapshot;
|
||||
try {
|
||||
snapshot = getContinuationGroup(conversation).get(getContinuationId(key));
|
||||
} catch (ContinuationNotFoundException e) {
|
||||
snapshot = getSnapshotGroup(conversation).getSnapshot(getSnapshotId(key));
|
||||
} catch (SnapshotNotFoundException e) {
|
||||
throw new FlowExecutionRestorationFailureException(key, e);
|
||||
}
|
||||
try {
|
||||
FlowExecution execution = snapshot.unmarshal();
|
||||
return restoreTransientState(execution, key, conversation);
|
||||
} catch (ContinuationUnmarshalException e) {
|
||||
} catch (SnapshotUnmarshalException e) {
|
||||
throw new FlowExecutionRestorationFailureException(key, e);
|
||||
}
|
||||
}
|
||||
@@ -138,12 +131,12 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
}
|
||||
FlowExecutionKey key = flowExecution.getKey();
|
||||
Conversation conversation = getConversation(key);
|
||||
FlowExecutionContinuationGroup continuationGroup = getContinuationGroup(conversation);
|
||||
FlowExecutionContinuation snapshot = snapshot(flowExecution);
|
||||
FlowExecutionSnapshotGroup snapshotGroup = getSnapshotGroup(conversation);
|
||||
FlowExecutionSnapshot snapshot = snapshot(flowExecution);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Adding new snapshot to group with id " + getContinuationId(key));
|
||||
logger.debug("Adding new snapshot to group with id " + getSnapshotId(key));
|
||||
}
|
||||
continuationGroup.add(getContinuationId(key), snapshot);
|
||||
snapshotGroup.addSnapshot(getSnapshotId(key), snapshot);
|
||||
putConversationScope(flowExecution, conversation);
|
||||
}
|
||||
|
||||
@@ -151,38 +144,40 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
|
||||
public void removeAllFlowExecutionSnapshots(FlowExecution execution) {
|
||||
Conversation conversation = getConversation(execution.getKey());
|
||||
getContinuationGroup(conversation).removeAllContinuations();
|
||||
getSnapshotGroup(conversation).removeAllSnapshots();
|
||||
}
|
||||
|
||||
public void removeFlowExecutionSnapshot(FlowExecution execution) {
|
||||
FlowExecutionKey key = execution.getKey();
|
||||
Conversation conversation = getConversation(key);
|
||||
getContinuationGroup(conversation).removeContinuation(getContinuationId(key));
|
||||
getSnapshotGroup(conversation).removeSnapshot(getSnapshotId(key));
|
||||
}
|
||||
|
||||
public void updateFlowExecutionSnapshot(FlowExecution execution) {
|
||||
FlowExecutionKey key = execution.getKey();
|
||||
Conversation conversation = getConversation(key);
|
||||
getContinuationGroup(conversation).updateContinuation(getContinuationId(key), snapshot(execution));
|
||||
getSnapshotGroup(conversation).updateSnapshot(getSnapshotId(key), snapshot(execution));
|
||||
}
|
||||
|
||||
// hooks for subclassing
|
||||
|
||||
protected FlowExecutionContinuationGroup createFlowExecutionContinuationGroup() {
|
||||
return new FlowExecutionContinuationGroup(maxContinuations);
|
||||
protected FlowExecutionSnapshotGroup createFlowExecutionSnapshotGroup() {
|
||||
SimpleFlowExecutionSnapshotGroup group = new SimpleFlowExecutionSnapshotGroup();
|
||||
group.setMaxSnapshots(maxSnapshots);
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuation group associated with the governing conversation.
|
||||
* @param conversation the conversation where the continuation group is stored
|
||||
* @return the continuation group
|
||||
* Returns the snapshot group associated with the governing conversation.
|
||||
* @param conversation the conversation where the snapshot group is stored
|
||||
* @return the snapshot group
|
||||
*/
|
||||
protected FlowExecutionContinuationGroup getContinuationGroup(Conversation conversation) {
|
||||
FlowExecutionContinuationGroup group = (FlowExecutionContinuationGroup) conversation
|
||||
.getAttribute(CONTINUATION_GROUP_ATTRIBUTE);
|
||||
protected FlowExecutionSnapshotGroup getSnapshotGroup(Conversation conversation) {
|
||||
FlowExecutionSnapshotGroup group = (FlowExecutionSnapshotGroup) conversation
|
||||
.getAttribute(SNAPSHOT_GROUP_ATTRIBUTE);
|
||||
if (group == null) {
|
||||
group = createFlowExecutionContinuationGroup();
|
||||
conversation.putAttribute(CONTINUATION_GROUP_ATTRIBUTE, group);
|
||||
group = createFlowExecutionSnapshotGroup();
|
||||
conversation.putAttribute(SNAPSHOT_GROUP_ATTRIBUTE, group);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -1,144 +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.execution.repository.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.webflow.execution.repository.continuation.ContinuationNotFoundException;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionContinuation;
|
||||
|
||||
/**
|
||||
* A group of flow execution continuations. Simple typed data structure backed by a map and linked list. Supports
|
||||
* expelling the oldest continuation once a maximum group size is met.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
class FlowExecutionContinuationGroup implements Serializable {
|
||||
|
||||
/**
|
||||
* A map of continuations; the key is the continuation id, and the value is the {@link FlowExecutionContinuation}
|
||||
* object.
|
||||
*/
|
||||
private Map continuations = new HashMap();
|
||||
|
||||
/**
|
||||
* An ordered list of continuation ids. Each continuation id represents an pointer to a continuation in the map. The
|
||||
* first element is the oldest continuation and the last is the youngest.
|
||||
*/
|
||||
private LinkedList continuationIds = new LinkedList();
|
||||
|
||||
/**
|
||||
* The maximum number of continuations allowed in this group.
|
||||
*/
|
||||
private int maxContinuations = -1;
|
||||
|
||||
/**
|
||||
* Creates a new flow execution continuation group.
|
||||
* @param maxContinuations the maximum number of continuations that can be stored in this group, -1 for unlimited
|
||||
*/
|
||||
public FlowExecutionContinuationGroup(int maxContinuations) {
|
||||
this.maxContinuations = maxContinuations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of continuations in this group.
|
||||
*/
|
||||
public int getContinuationCount() {
|
||||
return continuationIds.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuation with the provided <code>id</code>, or <code>null</code> if no such continuation
|
||||
* exists with that id.
|
||||
* @param continuationId the continuation id
|
||||
* @return the continuation
|
||||
* @throws ContinuationNotFoundException if the id does not match a continuation in this group
|
||||
*/
|
||||
public FlowExecutionContinuation get(Serializable continuationId) throws ContinuationNotFoundException {
|
||||
FlowExecutionContinuation continuation = (FlowExecutionContinuation) continuations.get(continuationId);
|
||||
if (continuation == null) {
|
||||
throw new ContinuationNotFoundException(continuationId);
|
||||
}
|
||||
return continuation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a flow execution continuation with given id to this group.
|
||||
* @param continuationId the continuation id
|
||||
* @param continuation the continuation
|
||||
*/
|
||||
public void add(Serializable continuationId, FlowExecutionContinuation continuation) {
|
||||
continuations.put(continuationId, continuation);
|
||||
if (continuationIds.contains(continuationId)) {
|
||||
continuationIds.remove(continuationId);
|
||||
}
|
||||
continuationIds.add(continuationId);
|
||||
// remove the oldest continuation if the maximium number of
|
||||
// continuations has been exceeded
|
||||
if (maxExceeded()) {
|
||||
removeOldestContinuation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the continuation with the given id.
|
||||
* @param continuationId the continuation id
|
||||
* @param continuation thew new continuation
|
||||
* @throws ContinuationNotFoundException if there was no previous continuation to update
|
||||
*/
|
||||
public void updateContinuation(Serializable continuationId, FlowExecutionContinuation continuation)
|
||||
throws ContinuationNotFoundException {
|
||||
if (!continuations.containsKey(continuationId)) {
|
||||
throw new ContinuationNotFoundException(continuationId);
|
||||
}
|
||||
continuations.put(continuationId, continuation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the continuation with the given id.
|
||||
* @param continuationId the continuation id
|
||||
*/
|
||||
public void removeContinuation(Serializable continuationId) {
|
||||
continuations.remove(continuationId);
|
||||
continuationIds.remove(continuationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all continuations in this group.
|
||||
*/
|
||||
public void removeAllContinuations() {
|
||||
continuations.clear();
|
||||
continuationIds.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the maximum number of allowed continuations in this group been exceeded?
|
||||
*/
|
||||
private boolean maxExceeded() {
|
||||
return maxContinuations > 0 && continuationIds.size() > maxContinuations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the olders continuation from this group.
|
||||
*/
|
||||
private void removeOldestContinuation() {
|
||||
continuations.remove(continuationIds.removeFirst());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.springframework.webflow.execution.repository.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionSnapshot;
|
||||
import org.springframework.webflow.execution.repository.continuation.SnapshotNotFoundException;
|
||||
|
||||
/**
|
||||
* A group of flow execution snapshots.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public interface FlowExecutionSnapshotGroup {
|
||||
|
||||
/**
|
||||
* Returns the snapshot with the provided <code>id</code>, or <code>null</code> if no such snapshot exists with
|
||||
* that id.
|
||||
* @param snapshotId the snapshot id
|
||||
* @return the continuation
|
||||
* @throws SnapshotNotFoundException if the id does not match a continuation in this group
|
||||
*/
|
||||
public FlowExecutionSnapshot getSnapshot(Serializable snapshotId) throws SnapshotNotFoundException;
|
||||
|
||||
/**
|
||||
* Add a flow execution snapshot with given id to this group.
|
||||
* @param snapshotId the snapshot id
|
||||
* @param snapshot the snapshot
|
||||
*/
|
||||
public void addSnapshot(Serializable snapshotId, FlowExecutionSnapshot snapshot);
|
||||
|
||||
/**
|
||||
* Update the snapshot with the given id.
|
||||
* @param snapshotId the snapshot id
|
||||
* @param snapshot the new snapshot
|
||||
* @throws SnapshotNotFoundException if there was no previous snapshot to update
|
||||
*/
|
||||
public void updateSnapshot(Serializable snapshotId, FlowExecutionSnapshot snapshot)
|
||||
throws SnapshotNotFoundException;
|
||||
|
||||
/**
|
||||
* Remove the snapshot with the given id.
|
||||
* @param snapshotId the continuation id
|
||||
*/
|
||||
public void removeSnapshot(Serializable snapshotId);
|
||||
|
||||
/**
|
||||
* Remove all snapshots in this group.
|
||||
*/
|
||||
public void removeAllSnapshots();
|
||||
|
||||
/**
|
||||
* Returns the count of snapshots in this group.
|
||||
*/
|
||||
public int getSnapshotCount();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2004-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.webflow.execution.repository.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionSnapshot;
|
||||
import org.springframework.webflow.execution.repository.continuation.SnapshotNotFoundException;
|
||||
|
||||
/**
|
||||
* A group of flow execution snapshots. Simple typed data structure backed by a map and linked list. Supports expelling
|
||||
* the oldest snapshot if the maximum size is met.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
class SimpleFlowExecutionSnapshotGroup implements FlowExecutionSnapshotGroup, Serializable {
|
||||
|
||||
/**
|
||||
* The snapshot map; the key is a snapshot id, and the value is a {@link FlowExecutionSnapshot} object.
|
||||
*/
|
||||
private Map snapshots = new HashMap();
|
||||
|
||||
/**
|
||||
* An ordered list of snapshot ids. Each snapshot id represents an pointer to a {@link FlowExecutionSnapshot} in the
|
||||
* map. The first element is the oldest snapshot and the last is the youngest.
|
||||
*/
|
||||
private LinkedList snapshotIds = new LinkedList();
|
||||
|
||||
/**
|
||||
* The maximum number of snapshots allowed in this group.
|
||||
*/
|
||||
private int maxSnapshots = -1;
|
||||
|
||||
/**
|
||||
* Returns the maximum number of snapshots allowed in this group.
|
||||
*/
|
||||
public int getMaxSnapshots() {
|
||||
return maxSnapshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of snapshots allowed in this group.
|
||||
* @param maxSnapshots them max number of snapshots
|
||||
*/
|
||||
public void setMaxSnapshots(int maxSnapshots) {
|
||||
this.maxSnapshots = maxSnapshots;
|
||||
}
|
||||
|
||||
public FlowExecutionSnapshot getSnapshot(Serializable snapshotId) throws SnapshotNotFoundException {
|
||||
FlowExecutionSnapshot snapshot = (FlowExecutionSnapshot) snapshots.get(snapshotId);
|
||||
if (snapshot == null) {
|
||||
throw new SnapshotNotFoundException(snapshotId);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public void addSnapshot(Serializable snapshotId, FlowExecutionSnapshot snapshot) {
|
||||
snapshots.put(snapshotId, snapshot);
|
||||
if (snapshotIds.contains(snapshotId)) {
|
||||
snapshotIds.remove(snapshotId);
|
||||
}
|
||||
snapshotIds.add(snapshotId);
|
||||
if (maxExceeded()) {
|
||||
removeOldestSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSnapshot(Serializable snapshotId, FlowExecutionSnapshot snapshot)
|
||||
throws SnapshotNotFoundException {
|
||||
if (!snapshots.containsKey(snapshotId)) {
|
||||
throw new SnapshotNotFoundException(snapshotId);
|
||||
}
|
||||
snapshots.put(snapshotId, snapshot);
|
||||
}
|
||||
|
||||
public void removeSnapshot(Serializable snapshotId) {
|
||||
snapshots.remove(snapshotId);
|
||||
snapshotIds.remove(snapshotId);
|
||||
}
|
||||
|
||||
public void removeAllSnapshots() {
|
||||
snapshots.clear();
|
||||
snapshotIds.clear();
|
||||
}
|
||||
|
||||
public int getSnapshotCount() {
|
||||
return snapshotIds.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the maximum number of snapshots in this group been exceeded?
|
||||
*/
|
||||
private boolean maxExceeded() {
|
||||
return maxSnapshots > 0 && snapshotIds.size() > maxSnapshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the olders snapshot from this group.
|
||||
*/
|
||||
private void removeOldestSnapshot() {
|
||||
snapshots.remove(snapshotIds.removeFirst());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import org.springframework.webflow.execution.repository.NoSuchFlowExecutionExcep
|
||||
* rehydrate a flow execution after it has been obtained from storage from resume.
|
||||
* <p>
|
||||
* The configured {@link FlowExecutionStateRestorer} should be compatible with the chosen {@link FlowExecution}
|
||||
* implementation and is configuration as done by a {@link FlowExecutionFactory} (listeners, execution attributes, ...).
|
||||
* implementation and its {@link FlowExecutionFactory}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
@@ -122,14 +122,9 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
"The string-encoded flow execution key is required");
|
||||
}
|
||||
String[] keyParts = CompositeFlowExecutionKey.keyParts(encodedKey);
|
||||
ConversationId conversationId;
|
||||
try {
|
||||
conversationId = conversationManager.parseConversationId(keyParts[0]);
|
||||
} catch (ConversationException e) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CompositeFlowExecutionKey.getFormat(), e);
|
||||
}
|
||||
Serializable continuationId = parseContinuationId(keyParts[1], encodedKey);
|
||||
return new CompositeFlowExecutionKey(conversationId, continuationId);
|
||||
Serializable executionId = parseExecutionId(keyParts[0], encodedKey);
|
||||
Serializable snapshotId = parseSnapshotId(keyParts[1], encodedKey);
|
||||
return new CompositeFlowExecutionKey(executionId, snapshotId);
|
||||
}
|
||||
|
||||
public FlowExecutionLock getLock(FlowExecutionKey key) throws FlowExecutionRepositoryException {
|
||||
@@ -170,10 +165,9 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
*/
|
||||
protected FlowExecutionKey getNextKey(FlowExecution execution) {
|
||||
if (alwaysGenerateNewNextKey) {
|
||||
CompositeFlowExecutionKey key = (CompositeFlowExecutionKey) execution.getKey();
|
||||
Integer continuationId = (Integer) key.getContinuationId();
|
||||
Integer nextId = nextContinuationId(continuationId);
|
||||
return new CompositeFlowExecutionKey(key.getConversationId(), nextId);
|
||||
CompositeFlowExecutionKey currentKey = (CompositeFlowExecutionKey) execution.getKey();
|
||||
Integer currentSnapshotId = (Integer) currentKey.getSnapshotId();
|
||||
return new CompositeFlowExecutionKey(currentKey.getExecutionId(), nextSnapshotId(currentSnapshotId));
|
||||
} else {
|
||||
return execution.getKey();
|
||||
}
|
||||
@@ -187,28 +181,13 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
*/
|
||||
protected Conversation getConversation(FlowExecutionKey key) throws NoSuchFlowExecutionException {
|
||||
try {
|
||||
return conversationManager.getConversation(getConversationId(key));
|
||||
ConversationId conversationId = (ConversationId) ((CompositeFlowExecutionKey) key).getExecutionId();
|
||||
return conversationManager.getConversation(conversationId);
|
||||
} catch (NoSuchConversationException e) {
|
||||
throw new NoSuchFlowExecutionException(key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversationId portion of the flow execution key.
|
||||
* @param key the execution key
|
||||
*/
|
||||
protected ConversationId getConversationId(FlowExecutionKey key) {
|
||||
return ((CompositeFlowExecutionKey) key).getConversationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuationId portion of the flow execution key.
|
||||
* @param key the execution key
|
||||
*/
|
||||
protected Serializable getContinuationId(FlowExecutionKey key) {
|
||||
return ((CompositeFlowExecutionKey) key).getContinuationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transient state of the flow execution after potential deserialization.
|
||||
* @param execution the flow execution
|
||||
@@ -251,15 +230,25 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private Integer nextContinuationId(Integer continuationId) {
|
||||
private Integer nextSnapshotId(Integer currentSnapshotId) {
|
||||
if (JdkVersion.isAtLeastJava15()) {
|
||||
return Integer.valueOf(continuationId.intValue() + 1);
|
||||
return Integer.valueOf(currentSnapshotId.intValue() + 1);
|
||||
} else {
|
||||
return new Integer(continuationId.intValue() + 1);
|
||||
return new Integer(currentSnapshotId.intValue() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private Serializable parseContinuationId(String encodedId, String encodedKey) {
|
||||
private ConversationId parseExecutionId(String encodedId, String encodedKey)
|
||||
throws BadlyFormattedFlowExecutionKeyException {
|
||||
try {
|
||||
return conversationManager.parseConversationId(encodedId);
|
||||
} catch (ConversationException e) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CompositeFlowExecutionKey.getFormat(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Serializable parseSnapshotId(String encodedId, String encodedKey)
|
||||
throws BadlyFormattedFlowExecutionKeyException {
|
||||
try {
|
||||
return Integer.valueOf(encodedId);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
@@ -18,79 +18,54 @@ package org.springframework.webflow.execution.repository.support;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.webflow.conversation.ConversationId;
|
||||
import org.springframework.webflow.conversation.ConversationManager;
|
||||
import org.springframework.webflow.execution.FlowExecution;
|
||||
import org.springframework.webflow.execution.FlowExecutionKey;
|
||||
import org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException;
|
||||
import org.springframework.webflow.execution.repository.continuation.FlowExecutionContinuation;
|
||||
|
||||
/**
|
||||
* A flow execution key consisting of two parts:
|
||||
* A flow execution key that consists of two parts:
|
||||
* <ol>
|
||||
* <li>A <i>conversationId</i>, identifying an active conversation managed by a {@link ConversationManager}.
|
||||
* <li>A <i>continuationId</i>, identifying a restorable {@link FlowExecutionContinuation} within a continuation group
|
||||
* governed by that conversation.
|
||||
* <li>A <i>executionId</i>, identifying a logical {@link FlowExecution} that is running.
|
||||
* <li>A <i>snapshotId</i>, identifying a physical flow execution snapshot that can be restored.
|
||||
* </ol>
|
||||
* <p>
|
||||
* This key is used to restore a FlowExecution from a conversation-service backed store.
|
||||
*
|
||||
* @see ConversationManager
|
||||
* @see FlowExecutionContinuation
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class CompositeFlowExecutionKey extends FlowExecutionKey {
|
||||
|
||||
/**
|
||||
* The default conversation id prefix delimiter.
|
||||
*/
|
||||
private static final String CONVERSATION_ID_PREFIX = "c";
|
||||
private static final String EXECUTION_ID_PREFIX = "e";
|
||||
|
||||
/**
|
||||
* The default continuation id prefix delimiter.
|
||||
*/
|
||||
private static final String CONTINUATION_ID_PREFIX = "v";
|
||||
private static final String SNAPSHOT_ID_PREFIX = "s";
|
||||
|
||||
/**
|
||||
* The format of the default string-encoded form, as returned by toString().
|
||||
*/
|
||||
private static final String FORMAT = CONVERSATION_ID_PREFIX + "<conversationId>" + CONTINUATION_ID_PREFIX
|
||||
+ "<continuationId>";
|
||||
private static final String FORMAT = EXECUTION_ID_PREFIX + "<executionId>" + SNAPSHOT_ID_PREFIX + "<snapshotId>";
|
||||
|
||||
/**
|
||||
* The conversation id.
|
||||
*/
|
||||
private ConversationId conversationId;
|
||||
private Serializable executionId;
|
||||
|
||||
/**
|
||||
* The continuation id.
|
||||
*/
|
||||
private Serializable continuationId;
|
||||
private Serializable snapshotId;
|
||||
|
||||
/**
|
||||
* Create a new composite flow execution key given the composing parts.
|
||||
* @param conversationId the conversation id
|
||||
* @param continuationId the continuation id
|
||||
* @param executionId the execution id
|
||||
* @param snapshotId the snapshot id
|
||||
*/
|
||||
public CompositeFlowExecutionKey(ConversationId conversationId, Serializable continuationId) {
|
||||
Assert.notNull(conversationId, "The conversation id is required");
|
||||
Assert.notNull(continuationId, "The continuation id is required");
|
||||
this.conversationId = conversationId;
|
||||
this.continuationId = continuationId;
|
||||
public CompositeFlowExecutionKey(Serializable executionId, Serializable snapshotId) {
|
||||
Assert.notNull(executionId, "The execution id is required");
|
||||
Assert.notNull(snapshotId, "The snapshot id is required");
|
||||
this.executionId = executionId;
|
||||
this.snapshotId = snapshotId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversation id.
|
||||
* Returns the execution id part of this key.
|
||||
*/
|
||||
public ConversationId getConversationId() {
|
||||
return conversationId;
|
||||
public Serializable getExecutionId() {
|
||||
return executionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuation id.
|
||||
* Returns the snapshot id part of this key.
|
||||
*/
|
||||
public Serializable getContinuationId() {
|
||||
return continuationId;
|
||||
public Serializable getSnapshotId() {
|
||||
return snapshotId;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
@@ -98,16 +73,16 @@ public class CompositeFlowExecutionKey extends FlowExecutionKey {
|
||||
return false;
|
||||
}
|
||||
CompositeFlowExecutionKey other = (CompositeFlowExecutionKey) obj;
|
||||
return conversationId.equals(other.conversationId) && continuationId.equals(other.continuationId);
|
||||
return executionId.equals(other.executionId) && snapshotId.equals(other.snapshotId);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return conversationId.hashCode() + continuationId.hashCode();
|
||||
return executionId.hashCode() + snapshotId.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new StringBuffer().append(CONVERSATION_ID_PREFIX).append(getConversationId()).append(
|
||||
CONTINUATION_ID_PREFIX).append(getContinuationId()).toString();
|
||||
return new StringBuffer().append(EXECUTION_ID_PREFIX).append(executionId).append(SNAPSHOT_ID_PREFIX).append(
|
||||
snapshotId).toString();
|
||||
}
|
||||
|
||||
// static helpers
|
||||
@@ -123,18 +98,18 @@ public class CompositeFlowExecutionKey extends FlowExecutionKey {
|
||||
* Helper that splits the string-form of an instance of this class into its "parts" so the parts can be easily
|
||||
* parsed.
|
||||
* @param encodedKey the string-encoded composite flow execution key
|
||||
* @return the composite key parts as a String array (conversationId = 0, continuationId = 1)
|
||||
* @return the composite key parts as a String array (executionId = 0, snapshotId = 1)
|
||||
*/
|
||||
public static String[] keyParts(String encodedKey) throws BadlyFormattedFlowExecutionKeyException {
|
||||
if (!encodedKey.startsWith(CONVERSATION_ID_PREFIX)) {
|
||||
if (!encodedKey.startsWith(EXECUTION_ID_PREFIX)) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, FORMAT);
|
||||
}
|
||||
int continuationStart = encodedKey.indexOf(CONTINUATION_ID_PREFIX, CONVERSATION_ID_PREFIX.length());
|
||||
if (continuationStart == -1) {
|
||||
int snapshotStart = encodedKey.indexOf(SNAPSHOT_ID_PREFIX, EXECUTION_ID_PREFIX.length());
|
||||
if (snapshotStart == -1) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, FORMAT);
|
||||
}
|
||||
String conversationId = encodedKey.substring(CONVERSATION_ID_PREFIX.length(), continuationStart);
|
||||
String continuationId = encodedKey.substring(continuationStart + CONTINUATION_ID_PREFIX.length());
|
||||
return new String[] { conversationId, continuationId };
|
||||
String executionId = encodedKey.substring(EXECUTION_ID_PREFIX.length(), snapshotStart);
|
||||
String snapshotId = encodedKey.substring(snapshotStart + SNAPSHOT_ID_PREFIX.length());
|
||||
return new String[] { executionId, snapshotId };
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import org.springframework.webflow.test.MockFlowExecutionKeyFactory;
|
||||
|
||||
public class SerializedFlowExecutionContinuationFactoryTests extends TestCase {
|
||||
private Flow flow;
|
||||
private SerializedFlowExecutionContinuationFactory factory;
|
||||
private SerializedFlowExecutionSnapshotFactory factory;
|
||||
private FlowExecutionStateRestorer stateRestorer;
|
||||
private FlowExecutionKeyFactory executionKeyFactory;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class SerializedFlowExecutionContinuationFactoryTests extends TestCase {
|
||||
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
|
||||
}
|
||||
};
|
||||
factory = new SerializedFlowExecutionContinuationFactory();
|
||||
factory = new SerializedFlowExecutionSnapshotFactory();
|
||||
stateRestorer = new FlowExecutionImplStateRestorer(new FlowDefinitionLocator() {
|
||||
public FlowDefinition getFlowDefinition(String flowId) throws NoSuchFlowDefinitionException,
|
||||
FlowDefinitionConstructionException {
|
||||
@@ -45,7 +45,7 @@ public class SerializedFlowExecutionContinuationFactoryTests extends TestCase {
|
||||
FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow);
|
||||
flowExecution.start(null, new MockExternalContext());
|
||||
flowExecution.getActiveSession().getScope().put("foo", "bar");
|
||||
FlowExecutionContinuation continuation = factory.createContinuation(flowExecution);
|
||||
FlowExecutionSnapshot continuation = factory.createSnapshot(flowExecution);
|
||||
FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) continuation.unmarshal();
|
||||
assertNotSame(flowExecution, flowExecution2);
|
||||
stateRestorer.restoreState(flowExecution2, null, flowExecution.getConversationScope(), executionKeyFactory);
|
||||
@@ -60,9 +60,9 @@ public class SerializedFlowExecutionContinuationFactoryTests extends TestCase {
|
||||
FlowExecution flowExecution = new FlowExecutionImplFactory().createFlowExecution(flow);
|
||||
flowExecution.start(null, new MockExternalContext());
|
||||
flowExecution.getActiveSession().getScope().put("foo", "bar");
|
||||
FlowExecutionContinuation continuation = factory.createContinuation(flowExecution);
|
||||
FlowExecutionSnapshot continuation = factory.createSnapshot(flowExecution);
|
||||
byte[] bytes = continuation.toByteArray();
|
||||
FlowExecutionContinuation continuation2 = factory.restoreContinuation(bytes);
|
||||
FlowExecutionSnapshot continuation2 = factory.restoreSnapshot(bytes);
|
||||
assertEquals(continuation, continuation2);
|
||||
FlowExecutionImpl flowExecution2 = (FlowExecutionImpl) continuation2.unmarshal();
|
||||
assertNotSame(flowExecution, flowExecution2);
|
||||
|
||||
@@ -11,6 +11,7 @@ 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.conversation.impl.BadlyFormattedConversationIdException;
|
||||
import org.springframework.webflow.conversation.impl.SimpleConversationId;
|
||||
import org.springframework.webflow.definition.FlowDefinition;
|
||||
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
|
||||
@@ -53,18 +54,18 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testParseFlowExecutionKey() {
|
||||
String key = "c12345v54321";
|
||||
String key = "e12345s54321";
|
||||
FlowExecutionKey k = repository.parseFlowExecutionKey(key);
|
||||
assertEquals(key, k.toString());
|
||||
}
|
||||
|
||||
public void testParseBadlyFormattedFlowExecutionKey() {
|
||||
String key = "c12345";
|
||||
String key = "e12345";
|
||||
try {
|
||||
repository.parseFlowExecutionKey(key);
|
||||
fail("Should have failed");
|
||||
} catch (BadlyFormattedFlowExecutionKeyException e) {
|
||||
assertEquals("c12345", e.getInvalidKey());
|
||||
assertEquals("e12345", e.getInvalidKey());
|
||||
assertNotNull(e.getFormat());
|
||||
}
|
||||
}
|
||||
@@ -81,19 +82,19 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testGetLock() {
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("c12345v54321");
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("e12345s54321");
|
||||
FlowExecutionLock lock = repository.getLock(key);
|
||||
assertNotNull(lock);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
public void testGetLockNoSuchFlowExecution() {
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("cbogusv54321");
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("e99999s54321");
|
||||
try {
|
||||
repository.getLock(key);
|
||||
fail("should have failed");
|
||||
} catch (NoSuchFlowExecutionException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,12 +185,16 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
|
||||
}
|
||||
|
||||
public ConversationId parseConversationId(String encodedId) throws ConversationException {
|
||||
return new SimpleConversationId(encodedId);
|
||||
try {
|
||||
return new SimpleConversationId(new Integer(Integer.parseInt(encodedId)));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadlyFormattedConversationIdException(encodedId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StubConversation implements Conversation {
|
||||
|
||||
private final ConversationId ID = new SimpleConversationId("12345");
|
||||
private final ConversationId ID = new SimpleConversationId(new Integer(12345));
|
||||
|
||||
private boolean locked;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public class CompositeFlowExecutionKeyTests extends TestCase {
|
||||
|
||||
public void testToString() {
|
||||
CompositeFlowExecutionKey key = new CompositeFlowExecutionKey(new SimpleConversationId("1"), "1");
|
||||
assertEquals("c1v1", key.toString());
|
||||
assertEquals("e1s1", key.toString());
|
||||
}
|
||||
|
||||
public void testEquals() {
|
||||
|
||||
Reference in New Issue
Block a user