diff --git a/spring-webflow-sandbox/build.xml b/spring-webflow-sandbox/build.xml
index 3973c36d..f8d80e0b 100644
--- a/spring-webflow-sandbox/build.xml
+++ b/spring-webflow-sandbox/build.xml
@@ -8,6 +8,8 @@
+
+
diff --git a/spring-webflow-sandbox/ivy.xml b/spring-webflow-sandbox/ivy.xml
index 3803246d..822fb458 100644
--- a/spring-webflow-sandbox/ivy.xml
+++ b/spring-webflow-sandbox/ivy.xml
@@ -15,7 +15,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-webflow-sandbox/project.properties b/spring-webflow-sandbox/project.properties
index f80ab3a7..55ed07f5 100644
--- a/spring-webflow-sandbox/project.properties
+++ b/spring-webflow-sandbox/project.properties
@@ -1,7 +1,7 @@
# properties defined in this file are overridable by a local build.properties in this project dir
# The location of the common build system
-common.build.dir=${basedir}/../../common-build
+common.build.dir=${basedir}/../common-build
javac.source=1.5
javac.target=1.5
diff --git a/spring-webflow-sandbox/src/etc/test-resources/log4j.properties b/spring-webflow-sandbox/src/etc/test-resources/log4j.properties
index 0b8127b5..b10ab1a5 100644
--- a/spring-webflow-sandbox/src/etc/test-resources/log4j.properties
+++ b/spring-webflow-sandbox/src/etc/test-resources/log4j.properties
@@ -16,4 +16,3 @@ log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.category.org.springframework.webflow=DEBUG
log4j.category.org.springframework.binding=DEBUG
-#Just for test
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractReadOnlyResourceHolder.java b/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractReadOnlyResourceHolder.java
new file mode 100644
index 00000000..5823badb
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractReadOnlyResourceHolder.java
@@ -0,0 +1,12 @@
+package org.springframework.util;
+
+/**
+ * Read-only resource holder.
+ *
+ * @author Maxim Petrashev
+ */
+public abstract class AbstractReadOnlyResourceHolder extends AbstractResourceHolder {
+ public final void set(E aObject) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractResourceHolder.java b/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractResourceHolder.java
new file mode 100644
index 00000000..5fce9a33
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/util/AbstractResourceHolder.java
@@ -0,0 +1,23 @@
+package org.springframework.util;
+
+import org.springframework.beans.factory.support.MethodReplacer;
+
+import java.lang.reflect.Method;
+
+/**
+ * Base abstract implementation of resource holder that implement method replacment logic.
+ *
+ * @author Maxim Petrashev
+ */
+public abstract class AbstractResourceHolder implements ResourceHolder
+ , MethodReplacer {//todo replace on injector in config
+ public Object reimplement(Object aObj, Method aMethod, Object[] aArgs) throws Throwable {
+ Object retVal = null;
+ if( aArgs.length == 0 ){
+ retVal = get();
+ } else {
+ set( (E) aArgs[0] );
+ }
+ return retVal;
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/util/ResourceHolder.java b/spring-webflow-sandbox/src/main/java/org/springframework/util/ResourceHolder.java
new file mode 100644
index 00000000..1e9ab9b2
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/util/ResourceHolder.java
@@ -0,0 +1,11 @@
+package org.springframework.util;
+
+/**
+ * Base interface for object holder concept.
+ *
+ * @author Maxim Petrashev
+ */
+public interface ResourceHolder {
+ E get();
+ void set(E aObject);
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListener.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListener.java
new file mode 100644
index 00000000..a5b822ba
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListener.java
@@ -0,0 +1,96 @@
+package org.springframework.webflow.execution;
+
+import org.springframework.webflow.definition.FlowDefinition;
+import org.springframework.webflow.core.collection.MutableAttributeMap;
+
+/**
+ * Listener interface for callback notification about phases in during conversation
+ *
+ * @author Maxim Petrashev
+ */
+public interface ConversationLifecycleListener {
+ /**
+ * Invoked when a flow is launched. The launching flow is not active.
+ *
+ * @param aNewFlow The launching flow
+ * @param aContext The request context
+ */
+
+ void startingFlow(FlowDefinition aNewFlow, RequestContext aContext);
+
+ void flowStarted(RequestContext aContext, MutableAttributeMap aInput);
+ /**
+ * Invoked when the root flow session has ended.
+ *
+ * @param aEndedSession The ended session
+ * @param aContext The request context
+ */
+ void flowEnded(FlowSession aEndedSession, RequestContext aContext);
+
+ void endingFlow(RequestContext aContext, MutableAttributeMap aOutput);
+ /**
+ * Invoked in parent flow before spawning in subflow but after input mapping has been happened.
+ * The child flow session is not available to implementations of this method because the flow session for
+ * the child flow has not yet started. If you need to add items to the
+ * subflow scope, put them in aInput.
+ *
+ * @param aParentSession The active parent flow session
+ * @param aChild The child flow
+ * @param aContext The request context
+ * @param aInput The input map
+ */
+ void startingSubflow(FlowSession aParentSession, FlowDefinition aChild, RequestContext aContext
+ , MutableAttributeMap aInput);
+ /**
+ * Invoked when a subflow is launched. The child flow session is not
+ * available to implementations of this method because the flow session for
+ * the child flow has not yet started. If you need to add items to the
+ * subflow scope, put them in aInput.
+ *
+ * @param aContext The request context
+ * @param aInput The input map
+ */
+ void subflowStarted(RequestContext aContext , MutableAttributeMap aInput);
+ /**
+ * Invoked in subflow flow before spawning back to parent flot but after output mapping has been happened.
+ * The parent flow session is not available to implementations of this method because the flow session for
+ * the parent flow has not yet continued. If you need to add items to the
+ * parent flow scope, put them in aOutput.
+ *
+ * @param aContext The request context
+ * @param aOutput The input map
+ */
+ void endingSubflow(RequestContext aContext , MutableAttributeMap aOutput);
+ /**
+ * Invoked when a parent flow is launched back. The child flow session is not
+ * available to implementations of this method because the flow session for
+ * the child flow has not yet started. If you need to add items to the
+ * subflow scope, put them in aInput.
+ *
+ * @param aParentSession The active parent flow session
+ * @param aChild The child flow
+ * @param aContext The request context
+ * @param aOutput The input map
+ */
+ void subflowEnded(FlowSession aParentSession, FlowDefinition aChild, RequestContext aContext
+ , MutableAttributeMap aOutput);
+ /**
+ * The currently executing flow session is active. This occurs after the
+ * first event for the executing request has been signaled or resume event has been happended. Invoked once per
+ * request and provides access to the active flow session prior to any
+ * actions being performed.
+ *
+ * @param aContext The request context
+ */
+ void sessionActive(RequestContext aContext);
+ /**
+ * The currently executing flow session is active. This occurs after the
+ * first event for the executing request has been signaled or resume event has been happended. Invoked once per
+ * request and provides access to the active flow session prior to any
+ * actions being performed.
+ *
+ * @param aContext The request context
+ */
+ void sessionDeactive(RequestContext aContext);
+
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListenerAdapter.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListenerAdapter.java
new file mode 100644
index 00000000..0dbc5137
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ConversationLifecycleListenerAdapter.java
@@ -0,0 +1,44 @@
+package org.springframework.webflow.execution;
+
+import org.springframework.webflow.definition.FlowDefinition;
+import org.springframework.webflow.core.collection.MutableAttributeMap;
+
+/**
+ *
+ * @author Maxim Petrashev
+ */
+public abstract class ConversationLifecycleListenerAdapter implements ConversationLifecycleListener {
+
+ public void startingFlow(FlowDefinition aNewFlow, RequestContext aContext) {
+ }
+
+ public void flowStarted(RequestContext aContext, MutableAttributeMap aInput) {
+ }
+
+ public void flowEnded(FlowSession aEndedSession, RequestContext aContext) {
+ }
+
+ public void endingFlow(RequestContext aContext, MutableAttributeMap aOutput) {
+ }
+
+ public void startingSubflow(FlowSession aParentSession, FlowDefinition aChild, RequestContext aContext, MutableAttributeMap aInput) {
+ }
+
+ public void subflowStarted(RequestContext aContext, MutableAttributeMap aInput) {
+ }
+
+ public void subflowStarted(FlowSession aParentSession, FlowDefinition aChild, RequestContext aContext, MutableAttributeMap aInput) {
+ }
+
+ public void endingSubflow(RequestContext aContext, MutableAttributeMap aOutput) {
+ }
+
+ public void subflowEnded(FlowSession aParentSession, FlowDefinition aChild, RequestContext aContext, MutableAttributeMap aOutput) {
+ }
+
+ public void sessionActive(RequestContext aContext) {
+ }
+
+ public void sessionDeactive(RequestContext aContext) {
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ExtendedFlowExecutionListenerInterceptor.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ExtendedFlowExecutionListenerInterceptor.java
new file mode 100644
index 00000000..01f022f6
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/execution/ExtendedFlowExecutionListenerInterceptor.java
@@ -0,0 +1,181 @@
+package org.springframework.webflow.execution;
+
+import org.springframework.webflow.engine.SubflowState;
+import org.springframework.webflow.core.collection.AttributeMap;
+import org.springframework.webflow.core.collection.MutableAttributeMap;
+import org.springframework.webflow.definition.FlowDefinition;
+import org.springframework.webflow.definition.StateDefinition;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A listener/interceptor whose purpose is to adapt a HandlerInterceptor and
+ * FlowExecutionListener to provide expanded coverage at the beginning and
+ * ending of a request coming into a flow controller. The idea is to signal when
+ * a flow starts/activates, then signal when it has deactivated or ended. In
+ * addition, it should be signaled when a subflow starts/activates and
+ * ends/deactivates.
+ *
+ * Adapted from Alex Wolfe's post at
+ * http://forum.springframework.org/showthread.php?t=17633
+ *
+ *
+ * @author Andrew Ebaugh
+ * @author Maxim Petrashev
+ */
+public class ExtendedFlowExecutionListenerInterceptor extends FlowExecutionListenerAdapter {
+ protected final Log _logger = LogFactory.getLog(getClass());
+
+ private static String FIRST_EVENT_SIGNALED = ExtendedFlowExecutionListenerInterceptor.class.getName() + ".FIRST_EVENT_SIGNALED";
+ private static String CURRENT_SESSION_ENDED = ExtendedFlowExecutionListenerInterceptor.class.getName() + ".CURRENT_SESSION_ENDED";
+
+
+ public ExtendedFlowExecutionListenerInterceptor(ConversationLifecycleListener aConversationLifecycleListener) {
+ _conversationLifecycleListener = aConversationLifecycleListener;
+ }
+
+ private ConversationLifecycleListener _conversationLifecycleListener;
+
+ /**
+ * Called when any client request is submitted to manipulate this flow
+ * execution. Sets a flag in the request scope that is activated when the
+ * first state is entered during this request. This flag is required in
+ * order to trigger execution of the {@link ConversationLifecycleListener#sessionActive(RequestContext)}
+ * method.
+ *
+ * @param aContext The request aContext
+ */
+ public final void requestSubmitted(RequestContext aContext) {
+ aContext.getRequestScope().put(FIRST_EVENT_SIGNALED, Boolean.FALSE);
+ aContext.getRequestScope().put(CURRENT_SESSION_ENDED, Boolean.FALSE);
+ }
+
+ public final void eventSignaled(RequestContext aContext, Event aEvent) {
+ signalAction(aContext);
+ }
+
+ public final void stateEntered(RequestContext aContext, StateDefinition aPreviousState, StateDefinition aState) {
+ signalAction(aContext);
+ }
+
+ /**
+ * Called when an event is signaled, or a state is entered, but before any
+ * potential transition or actions occurs. If the action signaled is the
+ * first for the request, then the {@link ConversationLifecycleListener#sessionActive(RequestContext)}
+ * method is invoked. It is necessary that this be called prior to any state
+ * transitions to provide listeners coverage of any state
+ * exit/transition/entry actions.
+ *
+ * @param aContext The request aContext
+ */
+ private void signalAction(RequestContext aContext) {
+ if (!firstEventSignaled(aContext)) {
+ aContext.getRequestScope().put(FIRST_EVENT_SIGNALED, Boolean.TRUE);
+ _conversationLifecycleListener.sessionActive(aContext);
+ }
+ }
+
+ /**
+ * A aFlow session is starting. This method invokes
+ * {@link ConversationLifecycleListener#startingFlow(FlowDefinition, RequestContext)} if the launching aFlow
+ * session is the root aFlow. Otherwise, the
+ * {@link ConversationLifecycleListener#startingSubflow(FlowSession, FlowDefinition, RequestContext,MutableAttributeMap)} method is
+ * invoked.
+ *
+ * @param aContext The request aContext
+ * @param aInput
+ * @throws EnterStateVetoException
+ * The start state transition was not allowed
+ * @param aFlowDefinition
+ */
+ public final void sessionStarting(RequestContext aContext, FlowDefinition aFlowDefinition, MutableAttributeMap aInput) {
+ aContext.getRequestScope().put(FIRST_EVENT_SIGNALED, Boolean.TRUE);
+ aContext.getRequestScope().put(CURRENT_SESSION_ENDED, Boolean.FALSE);
+ FlowExecutionContext executionContext = aContext.getFlowExecutionContext();
+ FlowSession activeSession = executionContext.isActive() ? executionContext .getActiveSession() : null;
+ // when starting in default subflow start state?
+ if (activeSession != null
+ && SubflowState.class.isInstance(activeSession.getState())) {
+ aFlowDefinition = ((SubflowState) activeSession.getState()).getSubflow();
+ }
+
+ if (activeSession == null) {
+ _conversationLifecycleListener.startingFlow(aFlowDefinition, aContext);
+ } else {
+ _conversationLifecycleListener.startingSubflow(activeSession, aFlowDefinition, aContext, null);
+ }
+ }
+
+ public void sessionStarted(RequestContext aContext, FlowSession aSession) {
+ if( aSession.isRoot() ) {
+ _conversationLifecycleListener.flowStarted(aContext, null );
+ } else {
+ _conversationLifecycleListener.subflowStarted(aContext,null);
+ }
+ }
+
+ public void sessionEnding(RequestContext aContext, FlowSession aSession, MutableAttributeMap aOutput) {
+ if( aSession.isRoot() ) {
+ _conversationLifecycleListener.endingFlow(aContext, null );
+ } else {
+ _conversationLifecycleListener.endingSubflow(aContext,null);
+ }
+ }
+
+ public void requestProcessed(RequestContext aContext) {
+ FlowExecutionContext executionContext = aContext.getFlowExecutionContext();
+ if( executionContext.isActive() ) {
+ _conversationLifecycleListener.sessionDeactive(aContext );
+ }
+ }
+
+ /**
+ * Called when a flow execution session ends. If the ended session was the
+ * root session of the flow execution, the
+ * {@link ConversationLifecycleListener#flowEnded(FlowSession, RequestContext)} method is
+ * invoked. If the ended session was not the root session, then the
+ * {@link ConversationLifecycleListener#subflowEnded(FlowSession, FlowDefinition, RequestContext, MutableAttributeMap)} }
+ * method is invoked. Prior to this, and in either case, the
+ * {@link ConversationLifecycleListener#sessionDeactive(RequestContext)} method is invoked.
+ *
+ * @param aContext The source of the event
+ * @param aEndedSession The ended FlowSession
+ */
+ public final void sessionEnded(RequestContext aContext, FlowSession aEndedSession, AttributeMap aSessionOutput) {
+ FlowExecutionContext exeCtx = aContext.getFlowExecutionContext();
+ FlowSession newSession = exeCtx.isActive() ? exeCtx.getActiveSession() : null;
+ if (aEndedSession != null) {
+ try{
+ if (aEndedSession.isRoot()) {
+ _conversationLifecycleListener.flowEnded(aEndedSession, aContext);
+ } else {
+ _conversationLifecycleListener.subflowEnded(newSession, aEndedSession.getDefinition(), aContext, null);
+ }
+ } catch( RuntimeException e ){ //todo to think which base exception need wrap
+ String message = "Can't end session";
+ if( _logger.isWarnEnabled() ) {
+ _logger.warn(message,e);
+ }
+ throw new FlowExecutionException( exeCtx.getDefinition().getId()
+ , newSession != null ? newSession.getState().getId() : aEndedSession.getState().getId()
+ , message, e);
+ }
+ }
+ aContext.getRequestScope().put(CURRENT_SESSION_ENDED, Boolean.TRUE);
+ }
+
+ /**
+ * Determine whether the current request has handled an event.
+ *
+ * @param aContext The request context
+ * @return true if an event has already been signaled during
+ * the current request, otherwise false
+ */
+ private boolean firstEventSignaled(RequestContext aContext) {
+ return aContext.getRequestScope().get(FIRST_EVENT_SIGNALED).equals(Boolean.TRUE);
+ }
+ public void resumed(RequestContext context) {
+ signalAction(context);
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/DefaultEntityManagerLifecycleController.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/DefaultEntityManagerLifecycleController.java
new file mode 100644
index 00000000..0d46b356
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/DefaultEntityManagerLifecycleController.java
@@ -0,0 +1,71 @@
+package org.springframework.webflow.jpa;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.Assert;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.orm.jpa.EntityManagerHolder;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+/**
+ * Default abstract JPA implementation for {@link EntityManagerLifecycleController}.
+ *
+ * @author Maxim Petrashev
+ */
+public abstract class DefaultEntityManagerLifecycleController implements EntityManagerLifecycleController {
+ private EntityManagerFactory _entityManagerFactory;
+
+ public DefaultEntityManagerLifecycleController(EntityManagerFactory aEntityManagerFactory) {
+ _entityManagerFactory = aEntityManagerFactory;
+ }
+
+ public void close(EntityManager aEntityManager) {
+ Assert.isTrue(aEntityManager.isOpen(), "Entity manager was already closed");
+ aEntityManager.close();
+ }
+
+ public void deactivate(EntityManager aEntityManager) {
+ unbind(aEntityManager);
+ if (_log.isDebugEnabled()) {
+ _log.debug("Entity manager unbinded: " + aEntityManager.isOpen());
+ }
+ }
+
+ public EntityManager create() {
+ return _entityManagerFactory.createEntityManager();
+ }
+
+ public void flush(EntityManager aEntityManager) {
+ try {
+ aEntityManager.flush();
+ } catch (RuntimeException e) {//todo review this code
+ aEntityManager.getTransaction().rollback();
+ throw e;
+ }
+ }
+
+ public void activate(EntityManager aEntityManager) {
+ bind(aEntityManager);
+ if (_log.isDebugEnabled()) {
+ _log.debug("Session activate: " + aEntityManager.isOpen());
+ }
+ }
+ protected void bind(EntityManager aEntityManager) {
+ TransactionSynchronizationManager.bindResource(_entityManagerFactory, new EntityManagerHolder(aEntityManager));
+ }
+
+ protected void unbind( EntityManager aEntityManager ) {
+ synchronized(_entityManagerFactory){//todo is this need?
+ Assert.isTrue( TransactionSynchronizationManager.hasResource(_entityManagerFactory) ); //todo remove this code. Resource must be already present.
+ TransactionSynchronizationManager.unbindResource(_entityManagerFactory);
+ }
+ }
+
+ protected EntityManagerFactory getEntityManagerFactory() {
+ return _entityManagerFactory;
+ }
+
+ protected final Log _log = LogFactory.getLog(getClass());
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/EntityManagerLifecycleController.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/EntityManagerLifecycleController.java
new file mode 100644
index 00000000..a5ac5be4
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/EntityManagerLifecycleController.java
@@ -0,0 +1,43 @@
+package org.springframework.webflow.jpa;
+
+import javax.persistence.EntityManager;
+
+/**
+ * Lifycycle controller that hide vendor specific routines for entity manager like:
+ *
+ * - Application transaction commit implementation. See, for example,
+ * {@link org.hibernate.annotations.FlushModeType.MANUAL}
+ * - Binding/Unbinding persistence context resources for current thread
+ *
+ *
+ * @author Maxim Petrashev
+ */
+public interface EntityManagerLifecycleController {
+ /**
+ * Create new entity manager and return wrapper for it with aId id.
+ */
+ EntityManager create();
+
+ /**
+ * Reconnect entity manager and bind to current thread.
+ * @param aEntityManager
+ */
+ void activate(EntityManager aEntityManager);
+ /**
+ * Disconnect current session and unbind from current thread.
+ * @param aEntityManager
+ */
+ void deactivate(EntityManager aEntityManager);
+
+ /**
+ * Commit application transaction.
+ * @param aEntityManager
+ */
+ void flush(EntityManager aEntityManager);
+ /**
+ * Close opened entity manager.
+ * @param aEntityManager
+ */
+ void close(EntityManager aEntityManager);
+
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/OpenEntityManagerPerConversationFlowListener.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/OpenEntityManagerPerConversationFlowListener.java
new file mode 100644
index 00000000..c9ff306b
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/OpenEntityManagerPerConversationFlowListener.java
@@ -0,0 +1,143 @@
+package org.springframework.webflow.jpa;
+
+import org.springframework.webflow.execution.ExtendedFlowExecutionListenerInterceptor;
+import org.springframework.webflow.execution.RequestContext;
+import org.springframework.webflow.execution.FlowSession;
+import org.springframework.webflow.execution.FlowExecutionContext;
+import org.springframework.webflow.execution.ConversationLifecycleListener;
+import org.springframework.webflow.execution.ConversationLifecycleListenerAdapter;
+import org.springframework.webflow.definition.FlowDefinition;
+import org.springframework.webflow.definition.StateDefinition;
+import org.springframework.webflow.engine.EndState;
+import org.springframework.web.context.request.WebRequestInterceptor;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.persistence.EntityManager;
+/**
+ * Intended for those wating a long session model within webflows. A entity
+ * manager is created when the flow begins, and is disconnected and reconnected
+ * as necessary throughout the lifecycle of the flow. In particular, it should
+ * be thought of as both a
+ * {@link org.springframework.webflow.execution.FlowExecutionListener} and
+ * {@link WebRequestInterceptor}
+ * that binds a
+ * entity manager to the current thread for the activate span of a FlowSession.
+ * This implementation shares the entity manager between a parent flow and any
+ * subflows.
+ *
+ * To facilitate the long session idea, objects within the flow will be
+ * reassociated with the jpa persistence context when the flow is
+ * re-activated. Deserialized jpa flow scope
+ * objects lose their association to the persistence context, and thus result in
+ * problems when you attempt to perform persistence operations (run into
+ * non-unique object exceptions, null sessions in persistent collections, and a
+ * host of other weird behaviors). There is a basic facility to apply a
+ * flushMode to new entity managers that are created, but not as
+ * sophisticated as that provided by a HibernateAccessor. Config example:
+ *
+ *
+ * <bean id="openEntityManagerFlowListener"
+ * class="org.springframework.webflow.jpa.OpenEntityManagerPerConversationFlowListener">
+ * <constructor ref="_lifecycleController"/>
+ * </bean>
+ *
+ *
+ *
+ * Adapted from Alex Wolfe's post at
+ * http://forum.springframework.org/showthread.php?t=17633
+ *
+ * @author Maxim Petrashev
+ */
+public class OpenEntityManagerPerConversationFlowListener extends ConversationLifecycleListenerAdapter {
+ protected final Log _logger = LogFactory.getLog( getClass() );
+
+ private final EntityManagerLifecycleController _lifecycleController;
+
+ /**
+ * Attribute name for annotated state which mark end-state and application commit end-state.
+ */
+ public static final String APPLICATION_TRANSACTION_COMMIT_ATTR_NAME = "applicationTransactionCommit";
+ /**
+ * Attribute name under wich in conversation scope will be stored entity manager for conversation.
+ */
+ private static final String ENTITY_MANAGER_ATTR_NAME = OpenEntityManagerPerConversationFlowListener.class.getName() + ".ENTITY_MANAGER";
+
+ protected OpenEntityManagerPerConversationFlowListener(EntityManagerLifecycleController aLifecycleController ) {
+ _lifecycleController = aLifecycleController;
+ }
+
+ protected EntityManager getEntityManager(RequestContext aContext){
+ return (EntityManager) aContext.getConversationScope().get( ENTITY_MANAGER_ATTR_NAME );
+ }
+
+ /**
+ * Create entity manager for new conversation.
+ * @param aNewFlow
+ * @param aContext
+ */
+ public void startingFlow(FlowDefinition aNewFlow, RequestContext aContext) {
+ _logger.debug("Creating entity manager for flow: " + aNewFlow.getId());
+ EntityManager entityManager = _lifecycleController.create();
+ _lifecycleController.activate( entityManager );
+ aContext.getConversationScope().put(ENTITY_MANAGER_ATTR_NAME, entityManager);
+ }
+
+ /**
+ * Try commit application transaction on application transaction commit end state.
+ * Clean also all resources that were allocated for conversation entity manager.
+ * @param aEndedSession
+ * @param aContext
+ */
+ public void flowEnded(FlowSession aEndedSession, RequestContext aContext) {
+ EntityManager entityManager = getEntityManager(aContext);
+ try{
+ if (isApplicationTransactionCommitState(aEndedSession.getState())) {
+ _lifecycleController.flush(entityManager);
+ }
+ }finally{
+ try{
+ _lifecycleController.deactivate(entityManager);
+ }finally{
+ _lifecycleController.close( entityManager );
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sessionActive(RequestContext aContext) {
+ EntityManager entityManager = getEntityManager(aContext);
+ _lifecycleController.activate( entityManager );
+ }
+
+ /**
+ * Deactivate current entity manager on end of request handling process.
+ */
+ public void sessionDeactive(RequestContext aContext) {
+ FlowExecutionContext flowExecutionContext = aContext.getFlowExecutionContext();
+ if( flowExecutionContext.isActive() ) {//todo need CommandManager or request specific lifecycleController
+ EntityManager entityManager = getEntityManager(aContext);
+ _lifecycleController.deactivate( entityManager );
+ } else {
+ //entity manager already was closed in flowEnded method
+ }
+ }
+
+ /**
+ * Return is aState application commit state or not. Returns true if aState is EndState and aState is annotated
+ * by {@link #APPLICATION_TRANSACTION_COMMIT_ATTR_NAME} attribute.
+ */
+ protected boolean isApplicationTransactionCommitState(StateDefinition aState) {
+ boolean retVal = false;
+ if (aState instanceof EndState) {
+ retVal = aState.getAttributes().get(APPLICATION_TRANSACTION_COMMIT_ATTR_NAME, "false").equals("true");
+ }
+ return retVal;
+ }
+
+ public static final String CURRENT_ENTITY_MANAGER_KEY_ATTR_NAME = OpenEntityManagerPerConversationFlowListener.class.getName() + ".CURRENT_ENTITY_MANAGER_KEY";
+ //todo review exceptionThrown
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/TransactionSynchronizationManagerEnityManagerHolder.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/TransactionSynchronizationManagerEnityManagerHolder.java
new file mode 100644
index 00000000..e9a2d7c8
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/TransactionSynchronizationManagerEnityManagerHolder.java
@@ -0,0 +1,25 @@
+package org.springframework.webflow.jpa;
+
+import org.springframework.orm.jpa.EntityManagerHolder;
+import org.springframework.util.AbstractReadOnlyResourceHolder;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import javax.persistence.EntityManager;
+
+/**
+ * Resource holder that is wrapper for TransactionSynchronizationManager.
+ *
+ * @author Maxim Petrashev
+ */
+public class TransactionSynchronizationManagerEnityManagerHolder extends AbstractReadOnlyResourceHolder {
+ public EntityManager get() {
+ EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource( _key );
+ return emHolder.getEntityManager();
+ }
+
+ public TransactionSynchronizationManagerEnityManagerHolder(Object aKey) {
+ _key = aKey;
+ }
+
+ private Object _key;
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/EntityManagerLifecycleController.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/EntityManagerLifecycleController.java
new file mode 100644
index 00000000..65c44c2c
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/EntityManagerLifecycleController.java
@@ -0,0 +1,68 @@
+package org.springframework.webflow.jpa.hibernate;
+
+import org.springframework.webflow.jpa.DefaultEntityManagerLifecycleController;
+import org.hibernate.FlushMode;
+import org.hibernate.SessionFactory;
+import org.hibernate.classic.Session;
+import org.hibernate.context.ManagedSessionContext;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+
+/**
+ * Hibernate specific implementation of EntityManagerLifecycleController interface. Set for each new entity manager
+ * hibernate specific flush mode and starts new JPA transaction on activation, and commit it on deactivation
+ *
+ * @author Maxim Petrashev
+ */
+public class EntityManagerLifecycleController extends DefaultEntityManagerLifecycleController {
+
+ public EntityManagerLifecycleController(EntityManagerFactory aEntityManagerFactory) {
+ super(aEntityManagerFactory);
+ }
+ public EntityManager create() {
+ EntityManager retVal = super.create();
+ Session session = HibernateUtils.getSession(retVal);
+ session.setFlushMode(FlushMode.MANUAL); //todo review this code
+ return retVal;
+ }
+ protected void unbind( EntityManager aEntityManager ) {
+ try{
+ SessionFactory sessionFactory = HibernateUtils.getSessionFactory( getEntityManagerFactory() );
+ ManagedSessionContext.unbind(sessionFactory);
+ }finally{
+ super.unbind( aEntityManager );
+ }
+ }
+
+ protected void bind(EntityManager aEntityManager) {
+ super.bind(aEntityManager);
+ //todo remove this code in hibernate specific class
+ ManagedSessionContext.bind( HibernateUtils.getSession( aEntityManager ) );
+ }
+
+ public void activate(EntityManager aEntityManager) {
+ beginTransaction(aEntityManager);
+ super.activate(aEntityManager);
+ }
+
+ public void deactivate(EntityManager aEntityManager) {
+ try{
+ disconnectSession(aEntityManager);
+ } finally {
+ super.deactivate(aEntityManager);
+ }
+ }
+
+ protected void beginTransaction(EntityManager aEntityManager) {//todo is it method need? May be transaction aspect has to cover it?
+ //begin database transaction for taking available connection
+ aEntityManager.getTransaction().begin();
+ }
+ protected void disconnectSession(EntityManager aEntityManager) {//todo is it method need? May be transaction aspect has to cover it?
+ EntityTransaction transaction = aEntityManager.getTransaction();
+ transaction.commit();
+ //todo is it need ? aSession.disconnect();
+ }
+
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/HibernateUtils.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/HibernateUtils.java
new file mode 100644
index 00000000..029ce1f6
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/jpa/hibernate/HibernateUtils.java
@@ -0,0 +1,43 @@
+package org.springframework.webflow.jpa.hibernate;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.EntityMode;
+import org.hibernate.metadata.ClassMetadata;
+import org.hibernate.classic.Session;
+import org.hibernate.ejb.HibernateEntityManagerFactory;
+import org.hibernate.ejb.HibernateEntityManager;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityManager;
+import java.util.Map;
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * Utils class for common hibernate JPA routines.
+ *
+ * @author Maxim Petrashev
+ */
+public abstract class HibernateUtils {
+ public static SessionFactory getSessionFactory(EntityManagerFactory aEntityManagerFactory) {
+ HibernateEntityManagerFactory hibernateEntityManagerFactory
+ = (HibernateEntityManagerFactory) aEntityManagerFactory;
+ return hibernateEntityManagerFactory.getSessionFactory();
+ }
+ public static Session getSession(EntityManager aEntityManager) {
+ return (Session) ((HibernateEntityManager)aEntityManager).getSession();
+ }
+ @Deprecated public static List getEntityClasses( EntityManager aEntityManager ) {
+ SessionFactory sessionFactory = getSession(aEntityManager).getSessionFactory();
+ Map metadataMap = sessionFactory.getAllClassMetadata();
+ List retVal = new LinkedList();
+ for (ClassMetadata classMetadata : metadataMap.values()) {
+ Class type = classMetadata.getMappedClass(EntityMode.POJO);
+ retVal.add( type );
+ }
+ return retVal;
+ }
+ public static Object getIdentifier(SessionFactory aSessionFactory, Object aEntity) {
+ return aSessionFactory.getCurrentSession().getIdentifier( aEntity );
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/Sale.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/Sale.java
new file mode 100644
index 00000000..01fce276
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/Sale.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2004-2007 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.samples.sellitem;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.springframework.core.style.ToStringCreator;
+import org.hibernate.annotations.Proxy;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Id;
+import javax.persistence.TableGenerator;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Transient;
+
+@Entity
+@Table(name = "T_SALES")
+@Proxy(lazy=true)
+public class Sale implements Serializable {
+
+ @Id
+ @TableGenerator( name="ids" )
+ @GeneratedValue(strategy= GenerationType.TABLE)
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int aId) {
+ id = aId;
+ }
+
+ private int id;
+
+ private double price;
+
+ private int itemCount;
+
+ private String category;
+
+ private boolean shipping;
+
+ private String shippingType;
+
+ private Date shipDate;
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public int getItemCount() {
+ return itemCount;
+ }
+
+ public void setItemCount(int itemCount) {
+ this.itemCount = itemCount;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ public boolean isShipping() {
+ return shipping;
+ }
+
+ public void setShipping(boolean shipping) {
+ this.shipping = shipping;
+ }
+
+ public String getShippingType() {
+ return shippingType;
+ }
+
+ public void setShippingType(String shippingType) {
+ this.shippingType = shippingType;
+ }
+
+ public Date getShipDate() {
+ return shipDate;
+ }
+
+ public void setShipDate(Date shipDate) {
+ this.shipDate = shipDate;
+ }
+
+ // business logic methods
+
+ /**
+ * Returns the base amount of the sale, without discount or delivery costs.
+ */
+ @Transient
+ public double getAmount() {
+ return price * itemCount;
+ }
+
+ /**
+ * Returns the discount rate to apply.
+ */
+ @Transient
+ public double getDiscountRate() {
+ double discount = 0.02;
+ if ("A".equals(category)) {
+ if (itemCount >= 100) {
+ discount = 0.1;
+ }
+ }
+ else if ("B".equals(category)) {
+ if (itemCount >= 200) {
+ discount = 0.2;
+ }
+ }
+ return discount;
+ }
+
+ /**
+ * Returns the savings because of the discount.
+ */
+ @Transient
+ public double getSavings() {
+ return getDiscountRate() * getAmount();
+ }
+
+ /**
+ * Returns the delivery cost.
+ */
+ @Transient
+ public double getDeliveryCost() {
+ double delCost = 0.0;
+ if ("S".equals(shippingType)) {
+ delCost = 10.0;
+ }
+ else if ("E".equals(shippingType)) {
+ delCost = 20.0;
+ }
+ return delCost;
+ }
+
+ /**
+ * Returns the total cost of the sale, including discount and delivery cost.
+ */
+ @Transient
+ public double getTotalCost() {
+ return getAmount() + getDeliveryCost() - getSavings();
+ }
+
+ public String toString() {
+ return new ToStringCreator(this).append("price", price).append("itemCount", itemCount).append("shippingType",
+ shippingType).append("shipDate", shipDate).toString();
+ }
+}
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleProcessor.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleProcessor.java
new file mode 100644
index 00000000..34692766
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleProcessor.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2004-2007 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.samples.sellitem;
+
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+public interface SaleProcessor {
+
+ public void process(Sale sale);
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleValidator.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleValidator.java
new file mode 100644
index 00000000..51cfcef5
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SaleValidator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2004-2007 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.samples.sellitem;
+
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+
+//todo remove dependency on Validator interface
+public class SaleValidator implements Validator {
+
+ public boolean supports(Class clazz) {
+ return Sale.class.equals(clazz);
+ }
+
+ public void validate(Object obj, Errors errors) {
+ Sale sale = (Sale)obj;
+ validatePriceAndItemCount(sale, errors);
+ }
+
+ public void validatePriceAndItemCount(Sale sale, Errors errors) {
+ if (sale.getItemCount() <= 0) {
+ errors.rejectValue("itemCount", "tooLittle", "Item count must be greater than 0");
+ }
+ if (sale.getPrice() <= 0.0) {
+ errors.rejectValue("price", "tooLittle", "Price must be greater than 0.0");
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemFlowExecutionListener.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemFlowExecutionListener.java
new file mode 100644
index 00000000..91467324
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemFlowExecutionListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2004-2007 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.samples.sellitem;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.StringUtils;
+import org.springframework.webflow.context.servlet.ServletExternalContext;
+import org.springframework.webflow.definition.StateDefinition;
+import org.springframework.webflow.execution.EnterStateVetoException;
+import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
+import org.springframework.webflow.execution.RequestContext;
+
+public class SellItemFlowExecutionListener extends FlowExecutionListenerAdapter {
+
+ public void stateEntering(RequestContext context, StateDefinition nextState) throws EnterStateVetoException {
+ String role = nextState.getAttributes().getString("role");
+ if (StringUtils.hasText(role)) {
+ HttpServletRequest request = ((ServletExternalContext)context.getExternalContext()).getRequest();//todo remove dependency on servlet api
+ if (!request.isUserInRole(role)) {
+ throw new EnterStateVetoException(context.getActiveFlow().getId(), context.getCurrentState().getId(),
+ nextState.getId(), "State requires role '" + role
+ + "', but the authenticated user doesn't have it!");
+ }
+ }
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemPropertyEditorRegistrar.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemPropertyEditorRegistrar.java
new file mode 100644
index 00000000..7b2e67d2
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/SellItemPropertyEditorRegistrar.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2004-2007 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.samples.sellitem;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.springframework.beans.PropertyEditorRegistrar;
+import org.springframework.beans.PropertyEditorRegistry;
+import org.springframework.beans.propertyeditors.CustomDateEditor;
+
+public class SellItemPropertyEditorRegistrar implements PropertyEditorRegistrar {
+
+ public void registerCustomEditors(PropertyEditorRegistry registry) {
+ registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true));
+ }
+}
diff --git a/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/jpa/JpaSaleProcessor.java b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/jpa/JpaSaleProcessor.java
new file mode 100644
index 00000000..1e54c3a4
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/java/org/springframework/webflow/samples/sellitem/jpa/JpaSaleProcessor.java
@@ -0,0 +1,14 @@
+package org.springframework.webflow.samples.sellitem.jpa;
+
+import org.springframework.webflow.samples.sellitem.SaleProcessor;
+import org.springframework.webflow.samples.sellitem.Sale;
+
+import javax.persistence.EntityManager;
+
+public abstract class JpaSaleProcessor implements SaleProcessor {
+ public void process(Sale sale) {
+ EntityManager entityManager = getEntityManager();
+ entityManager.persist(sale);
+ }
+ protected abstract EntityManager getEntityManager();
+}
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/META-INF/persistence.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/META-INF/persistence.xml
new file mode 100644
index 00000000..a609ee3c
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/META-INF/persistence.xml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/log4j.properties b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/log4j.properties
new file mode 100644
index 00000000..5ba9222c
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/log4j.properties
@@ -0,0 +1,9 @@
+log4j.rootCategory=WARN, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
+
+# Enable web flow logging
+log4j.category.org.springframework.webflow=DEBUG
+log4j.category.org.springframework.binding=DEBUG
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/org/springframework/webflow/samples/sellitem/services-config.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/org/springframework/webflow/samples/sellitem/services-config.xml
new file mode 100644
index 00000000..a21c5989
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/classes/org/springframework/webflow/samples/sellitem/services-config.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.hibernate.dialect.HSQLDialect
+ create-drop
+ org.hsqldb.jdbcDriver
+ jdbc:hsqldb:mem:sellItem
+ after_transaction
+ true
+ org.hibernate.context.ManagedSessionContext
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-beans.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-beans.xml
new file mode 100644
index 00000000..66a4c1cc
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-beans.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-conversation-scope-flow.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-conversation-scope-flow.xml
new file mode 100644
index 00000000..b6850a3f
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/sellitem-conversation-scope-flow.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/shipping-conversation-scope-flow.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/shipping-conversation-scope-flow.xml
new file mode 100644
index 00000000..2f12345f
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/conversation-scope/shipping-conversation-scope-flow.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-beans.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-beans.xml
new file mode 100644
index 00000000..2b828373
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-beans.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-flow.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-flow.xml
new file mode 100644
index 00000000..a49fb3d2
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/sellitem-flow.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/shipping-flow.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/shipping-flow.xml
new file mode 100644
index 00000000..9bf52e40
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/shipping-flow.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/simple/sellitem-simple-flow.xml b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/simple/sellitem-simple-flow.xml
new file mode 100644
index 00000000..8c06eaa1
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/flows/simple/sellitem-simple-flow.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/categoryForm.jsp b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/categoryForm.jsp
new file mode 100644
index 00000000..6d132f90
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/categoryForm.jsp
@@ -0,0 +1,48 @@
+<%@ include file="includeTop.jsp" %>
+
+
+
+<%@ include file="includeBottom.jsp" %>
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/costOverview.jsp b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/costOverview.jsp
new file mode 100644
index 00000000..8dd43e98
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/costOverview.jsp
@@ -0,0 +1,71 @@
+<%@ include file="includeTop.jsp" %>
+
+
+
+
Purchase cost overview
+
+
+
+
+<%@ include file="includeBottom.jsp" %>
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/error.jsp b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/error.jsp
new file mode 100644
index 00000000..65d8ab8c
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/error.jsp
@@ -0,0 +1,17 @@
+<%@ include file="includeTop.jsp" %>
+
+
+
+

+
+
+
+ Duplicate submit of the same transaction not allowed!
+
+
+
+ Sell a new item
+
+
+
+<%@ include file="includeBottom.jsp" %>
\ No newline at end of file
diff --git a/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/includeBottom.jsp b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/includeBottom.jsp
new file mode 100644
index 00000000..dfe41438
--- /dev/null
+++ b/spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/includeBottom.jsp
@@ -0,0 +1,5 @@
+
+