Initial import
This commit is contained in:
@@ -8,6 +8,8 @@
|
|||||||
<property file="${common.build.dir}/project.properties" />
|
<property file="${common.build.dir}/project.properties" />
|
||||||
<property file="${user.home}/build.properties" />
|
<property file="${user.home}/build.properties" />
|
||||||
|
|
||||||
|
<property name="build.web" value="true"/>
|
||||||
|
|
||||||
<property name="project.title" value="Spring Web Flow Sandbox" />
|
<property name="project.title" value="Spring Web Flow Sandbox" />
|
||||||
<property name="project.package" value="org.springframework.webflow" />
|
<property name="project.package" value="org.springframework.webflow" />
|
||||||
<property name="project.copyright" value="Copyright © 2004-2007. All Rights Reserved."/>
|
<property name="project.copyright" value="Copyright © 2004-2007. All Rights Reserved."/>
|
||||||
|
|||||||
@@ -15,7 +15,18 @@
|
|||||||
<dependency org="taglibs" name="standard" rev="1.1.2"/>
|
<dependency org="taglibs" name="standard" rev="1.1.2"/>
|
||||||
<dependency org="jstl" name="jstl" rev="1.1.2"/>
|
<dependency org="jstl" name="jstl" rev="1.1.2"/>
|
||||||
<dependency org="org.springframework" name="spring-webflow" rev="latest.integration"/>
|
<dependency org="org.springframework" name="spring-webflow" rev="latest.integration"/>
|
||||||
|
<dependency org="org.springframework" name="spring-aop" rev="2.0.2"/>
|
||||||
|
<dependency org="org.springframework" name="spring-dao" rev="2.0.2"/>
|
||||||
|
<dependency org="org.springframework" name="spring-jdbc" rev="2.0.2"/>
|
||||||
|
<dependency org="org.springframework" name="spring-jpa" rev="2.0.2"/>
|
||||||
|
<dependency org="javax.servlet" name="servlet-api" rev="2.4"/>
|
||||||
|
<dependency org="javax.persistence" name="persistence-api" rev="1.0"/>
|
||||||
|
<dependency org="org.hibernate" name="hibernate" rev="3.2.1.ga"/>
|
||||||
|
<dependency org="org.hibernate" name="hibernate-annotations" rev="3.2.1.ga"/>
|
||||||
|
<dependency org="org.hibernate" name="hibernate-entitymanager" rev="3.2.1.ga"/>
|
||||||
|
|
||||||
|
<dependency org="hsqldb" name="hsqldb" rev="1.8.0.7"/>
|
||||||
|
<dependency org="aopalliance" name="aopalliance" rev="1.0"/>
|
||||||
<!-- test-time only dependencies -->
|
<!-- test-time only dependencies -->
|
||||||
<dependency org="junit" name="junit" rev="3.8.1" conf="test->default"/>
|
<dependency org="junit" name="junit" rev="3.8.1" conf="test->default"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# properties defined in this file are overridable by a local build.properties in this project dir
|
# properties defined in this file are overridable by a local build.properties in this project dir
|
||||||
|
|
||||||
# The location of the common build system
|
# The location of the common build system
|
||||||
common.build.dir=${basedir}/../../common-build
|
common.build.dir=${basedir}/../common-build
|
||||||
|
|
||||||
javac.source=1.5
|
javac.source=1.5
|
||||||
javac.target=1.5
|
javac.target=1.5
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
|||||||
|
|
||||||
log4j.category.org.springframework.webflow=DEBUG
|
log4j.category.org.springframework.webflow=DEBUG
|
||||||
log4j.category.org.springframework.binding=DEBUG
|
log4j.category.org.springframework.binding=DEBUG
|
||||||
#Just for test
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.springframework.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-only resource holder.
|
||||||
|
*
|
||||||
|
* @author Maxim Petrashev
|
||||||
|
*/
|
||||||
|
public abstract class AbstractReadOnlyResourceHolder<E> extends AbstractResourceHolder<E> {
|
||||||
|
public final void set(E aObject) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<E> implements ResourceHolder<E>
|
||||||
|
, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.springframework.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for object holder concept.
|
||||||
|
*
|
||||||
|
* @author Maxim Petrashev
|
||||||
|
*/
|
||||||
|
public interface ResourceHolder<E> {
|
||||||
|
E get();
|
||||||
|
void set(E aObject);
|
||||||
|
}
|
||||||
@@ -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. <p/> 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. <p/> 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. <p/> 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. <p/> 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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
* </p>
|
||||||
|
* Adapted from Alex Wolfe's post at <a
|
||||||
|
* href="http://forum.springframework.org/showthread.php?t=17633">
|
||||||
|
* http://forum.springframework.org/showthread.php?t=17633</a>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @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. <p/> 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 <code>true</code> if an event has already been signaled during
|
||||||
|
* the current request, otherwise <code>false</code>
|
||||||
|
*/
|
||||||
|
private boolean firstEventSignaled(RequestContext aContext) {
|
||||||
|
return aContext.getRequestScope().get(FIRST_EVENT_SIGNALED).equals(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
public void resumed(RequestContext context) {
|
||||||
|
signalAction(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.springframework.webflow.jpa;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifycycle controller that hide vendor specific routines for entity manager like:
|
||||||
|
* <ul>
|
||||||
|
* <li>Application transaction commit implementation. See, for example,
|
||||||
|
* {@link org.hibernate.annotations.FlushModeType.MANUAL}</li>
|
||||||
|
* <li>Binding/Unbinding persistence context resources for current thread</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
* </p>
|
||||||
|
* 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:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <bean id="openEntityManagerFlowListener"
|
||||||
|
* class="org.springframework.webflow.jpa.OpenEntityManagerPerConversationFlowListener">
|
||||||
|
* <constructor ref="_lifecycleController"/>
|
||||||
|
* </bean>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Adapted from Alex Wolfe's post at <a
|
||||||
|
* href="http://forum.springframework.org/showthread.php?t=17633">
|
||||||
|
* http://forum.springframework.org/showthread.php?t=17633</a>
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
}
|
||||||
@@ -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<EntityManager> {
|
||||||
|
public EntityManager get() {
|
||||||
|
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource( _key );
|
||||||
|
return emHolder.getEntityManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransactionSynchronizationManagerEnityManagerHolder(Object aKey) {
|
||||||
|
_key = aKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object _key;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Class> getEntityClasses( EntityManager aEntityManager ) {
|
||||||
|
SessionFactory sessionFactory = getSession(aEntityManager).getSessionFactory();
|
||||||
|
Map<String, ClassMetadata> metadataMap = sessionFactory.getAllClassMetadata();
|
||||||
|
List<Class> retVal = new LinkedList<Class>();
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
|
||||||
|
version="1.0">
|
||||||
|
<persistence-unit name="sellitem" transaction-type="RESOURCE_LOCAL"/>
|
||||||
|
</persistence>
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
|
||||||
|
|
||||||
|
<bean id="saleProcessor" class="org.springframework.webflow.samples.sellitem.jpa.JpaSaleProcessor">
|
||||||
|
<replaced-method name="getEntityManager" replacer="entityManagerHolder" />
|
||||||
|
</bean>
|
||||||
|
<tx:annotation-driven/>
|
||||||
|
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||||
|
<property name="dataSource" ref="dataSource"/>
|
||||||
|
</bean>
|
||||||
|
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||||
|
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
|
||||||
|
<property name="url" value="jdbc:hsqldb:mem:sellItem"/>
|
||||||
|
<property name="username" value="sa"/>
|
||||||
|
</bean>
|
||||||
|
<bean id="entityManagerLifecycleController" class="org.springframework.webflow.jpa.hibernate.EntityManagerLifecycleController">
|
||||||
|
<constructor-arg ref="entityManagerFactory"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="entityManagerHolder" class="org.springframework.webflow.jpa.TransactionSynchronizationManagerEnityManagerHolder">
|
||||||
|
<constructor-arg ref="entityManagerFactory"/>
|
||||||
|
</bean>
|
||||||
|
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
|
||||||
|
<property name="persistenceUnitName" value="sellitem"/>
|
||||||
|
<property name="jpaDialect">
|
||||||
|
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
|
||||||
|
</property>
|
||||||
|
<property name="jpaVendorAdapter">
|
||||||
|
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
|
||||||
|
<property name="database" value="HSQL"/>
|
||||||
|
<property name="showSql" value="true"/>
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
<property name="jpaProperties">
|
||||||
|
<props>
|
||||||
|
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
|
||||||
|
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
|
||||||
|
<prop key="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</prop>
|
||||||
|
<prop key="hibernate.connection.url">jdbc:hsqldb:mem:sellItem</prop>
|
||||||
|
<prop key="hibernate.connection.release_mode">after_transaction</prop>
|
||||||
|
<prop key="hibernate.generate_statistics">true</prop>
|
||||||
|
<prop key="hibernate.current_session_context_class">org.hibernate.context.ManagedSessionContext</prop>
|
||||||
|
</props>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
|
|
||||||
|
<!-- Manages setting up, binding input to, and validating a Sale "backing wizard form object" -->
|
||||||
|
<bean id="formAction" class="org.springframework.webflow.action.FormAction">
|
||||||
|
<property name="formObjectClass" value="org.springframework.webflow.samples.sellitem.Sale"/>
|
||||||
|
<property name="formObjectScope" value="CONVERSATION"/>
|
||||||
|
<property name="formErrorsScope" value="CONVERSATION"/>
|
||||||
|
<property name="validator">
|
||||||
|
<bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/>
|
||||||
|
</property>
|
||||||
|
<!-- Installs property editors used to format non-String fields like 'shipDate' -->
|
||||||
|
<property name="propertyEditorRegistrar">
|
||||||
|
<bean class="org.springframework.webflow.samples.sellitem.SellItemPropertyEditorRegistrar"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||||
|
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
|
||||||
|
|
||||||
|
<start-actions>
|
||||||
|
<!-- create the backing form object and initialize a empty errors collection -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</start-actions>
|
||||||
|
|
||||||
|
<start-state idref="enterPriceAndItemCount"/>
|
||||||
|
|
||||||
|
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
|
||||||
|
<transition on="submit" to="enterCategory">
|
||||||
|
<action bean="formAction" method="bindAndValidate">
|
||||||
|
<attribute name="validatorMethod" value="validatePriceAndItemCount"/>
|
||||||
|
</action>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<view-state id="enterCategory" view="categoryForm">
|
||||||
|
<transition on="submit" to="requiresShipping">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<decision-state id="requiresShipping">
|
||||||
|
<if test="${conversationScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
|
||||||
|
</decision-state>
|
||||||
|
|
||||||
|
<subflow-state id="enterShippingDetails" flow="shipping-conversation-scope-flow">
|
||||||
|
<transition on="finish" to="processSale"/>
|
||||||
|
</subflow-state>
|
||||||
|
|
||||||
|
<action-state id="processSale">
|
||||||
|
<bean-action bean="saleProcessor" method="process">
|
||||||
|
<method-arguments>
|
||||||
|
<argument expression="conversationScope.sale"/>
|
||||||
|
</method-arguments>
|
||||||
|
</bean-action>
|
||||||
|
<transition on="success" to="finish"/>
|
||||||
|
</action-state>
|
||||||
|
|
||||||
|
<end-state id="finish" view="costOverview">
|
||||||
|
<entry-actions>
|
||||||
|
<!-- force reinstall of property editors so costOverview can render formatted Sale values -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</entry-actions>
|
||||||
|
</end-state>
|
||||||
|
|
||||||
|
<import resource="sellitem-beans.xml"/>
|
||||||
|
|
||||||
|
</flow>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||||
|
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
|
||||||
|
|
||||||
|
<start-state idref="enterShippingDetails"/>
|
||||||
|
|
||||||
|
<view-state id="enterShippingDetails" view="shippingDetailsForm">
|
||||||
|
<transition on="submit" to="finish">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<end-state id="finish"/>
|
||||||
|
|
||||||
|
<import resource="sellitem-beans.xml"/>
|
||||||
|
|
||||||
|
</flow>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||||
|
|
||||||
|
<!-- Manages setting up, binding input to, and validating a Sale "backing wizard form object" -->
|
||||||
|
<bean id="formAction" class="org.springframework.webflow.action.FormAction">
|
||||||
|
<property name="formObjectClass" value="org.springframework.webflow.samples.sellitem.Sale"/>
|
||||||
|
<property name="validator">
|
||||||
|
<bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/>
|
||||||
|
</property>
|
||||||
|
<!-- Installs property editors used to format non-String fields like 'shipDate' -->
|
||||||
|
<property name="propertyEditorRegistrar">
|
||||||
|
<bean class="org.springframework.webflow.samples.sellitem.SellItemPropertyEditorRegistrar"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||||
|
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
|
||||||
|
|
||||||
|
<start-actions>
|
||||||
|
<!-- create the backing form object and initialize a empty errors collection -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</start-actions>
|
||||||
|
|
||||||
|
<start-state idref="enterPriceAndItemCount"/>
|
||||||
|
|
||||||
|
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
|
||||||
|
<transition on="submit" to="enterCategory">
|
||||||
|
<action bean="formAction" method="bindAndValidate">
|
||||||
|
<attribute name="validatorMethod" value="validatePriceAndItemCount"/>
|
||||||
|
</action>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<view-state id="enterCategory" view="categoryForm">
|
||||||
|
<transition on="submit" to="requiresShipping">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<decision-state id="requiresShipping">
|
||||||
|
<if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
|
||||||
|
</decision-state>
|
||||||
|
|
||||||
|
<subflow-state id="enterShippingDetails" flow="shipping-flow">
|
||||||
|
<attribute-mapper>
|
||||||
|
<input-mapper>
|
||||||
|
<input-attribute name="sale"/>
|
||||||
|
</input-mapper>
|
||||||
|
</attribute-mapper>
|
||||||
|
<transition on="finish" to="processSale"/>
|
||||||
|
</subflow-state>
|
||||||
|
|
||||||
|
<action-state id="processSale">
|
||||||
|
<bean-action bean="saleProcessor" method="process">
|
||||||
|
<method-arguments>
|
||||||
|
<argument expression="flowScope.sale"/>
|
||||||
|
</method-arguments>
|
||||||
|
</bean-action>
|
||||||
|
<transition on="success" to="finish"/>
|
||||||
|
</action-state>
|
||||||
|
|
||||||
|
<end-state id="finish" view="costOverview">
|
||||||
|
<entry-actions>
|
||||||
|
<!-- force reinstall of property editors so costOverview can render formatted Sale values -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</entry-actions>
|
||||||
|
</end-state>
|
||||||
|
|
||||||
|
<import resource="sellitem-beans.xml"/>
|
||||||
|
|
||||||
|
</flow>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||||
|
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
|
||||||
|
|
||||||
|
<input-mapper>
|
||||||
|
<input-attribute name="sale"/>
|
||||||
|
</input-mapper>
|
||||||
|
|
||||||
|
<start-state idref="enterShippingDetails"/>
|
||||||
|
|
||||||
|
<view-state id="enterShippingDetails" view="shippingDetailsForm">
|
||||||
|
<transition on="submit" to="finish">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<end-state id="finish"/>
|
||||||
|
|
||||||
|
<import resource="sellitem-beans.xml"/>
|
||||||
|
|
||||||
|
</flow>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<flow xmlns="http://www.springframework.org/schema/webflow"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/webflow
|
||||||
|
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
|
||||||
|
|
||||||
|
<start-actions>
|
||||||
|
<!-- create the backing form object and initialize a empty errors collection -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</start-actions>
|
||||||
|
|
||||||
|
<start-state idref="enterPriceAndItemCount"/>
|
||||||
|
|
||||||
|
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
|
||||||
|
<transition on="submit" to="enterCategory">
|
||||||
|
<action bean="formAction" method="bindAndValidate">
|
||||||
|
<attribute name="validatorMethod" value="validatePriceAndItemCount"/>
|
||||||
|
</action>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<view-state id="enterCategory" view="categoryForm">
|
||||||
|
<transition on="submit" to="requiresShipping">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<decision-state id="requiresShipping">
|
||||||
|
<if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
|
||||||
|
</decision-state>
|
||||||
|
|
||||||
|
<view-state id="enterShippingDetails" view="shippingDetailsForm">
|
||||||
|
<transition on="submit" to="processSale">
|
||||||
|
<action bean="formAction" method="bind"/>
|
||||||
|
</transition>
|
||||||
|
</view-state>
|
||||||
|
|
||||||
|
<action-state id="processSale">
|
||||||
|
<bean-action bean="saleProcessor" method="process">
|
||||||
|
<method-arguments>
|
||||||
|
<argument expression="flowScope.sale"/>
|
||||||
|
</method-arguments>
|
||||||
|
</bean-action>
|
||||||
|
<transition on="success" to="finish"/>
|
||||||
|
</action-state>
|
||||||
|
|
||||||
|
<end-state id="finish" view="costOverview">
|
||||||
|
<entry-actions>
|
||||||
|
<!-- force reinstall of property editors so costOverview can render formatted Sale values -->
|
||||||
|
<action bean="formAction" method="setupForm"/>
|
||||||
|
</entry-actions>
|
||||||
|
</end-state>
|
||||||
|
|
||||||
|
<import resource="../sellitem-beans.xml"/>
|
||||||
|
|
||||||
|
</flow>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<%@ include file="includeTop.jsp" %>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="insert"><img src="images/webflow-logo.jpg"/></div>
|
||||||
|
<h2>Select category</h2>
|
||||||
|
<table>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Price:</td><td>${sale.price}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Item count:</td><td>${sale.itemCount}</td>
|
||||||
|
</tr>
|
||||||
|
<form:form commandName="sale" method="post">
|
||||||
|
<tr>
|
||||||
|
<td>Category:</td>
|
||||||
|
<td>
|
||||||
|
<spring:bind path="sale.category">
|
||||||
|
<select name="${status.expression}">
|
||||||
|
<option value="" <c:if test="${status.value ==''}">selected</c:if>>
|
||||||
|
None (0.02 discount rate)
|
||||||
|
</option>
|
||||||
|
<option value="A" <c:if test="${status.value =='A'}">selected</c:if>>
|
||||||
|
Cat. A (0.1 discount rate when more than 100 items)
|
||||||
|
</option>
|
||||||
|
<option value="B" <c:if test="${status.value =='B'}">selected</c:if>>
|
||||||
|
Cat. B (0.2 discount rate when more than 200 items)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</spring:bind>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Is shipping required?:</td>
|
||||||
|
<td>
|
||||||
|
<form:checkbox path="shipping"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="buttonBar">
|
||||||
|
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}">
|
||||||
|
<input type="submit" class="button" name="_eventId_submit" value="Next">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</form:form>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%@ include file="includeBottom.jsp" %>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<%@ include file="includeTop.jsp" %>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="insert"><img src="images/webflow-logo.jpg"/></div>
|
||||||
|
<h2>Purchase cost overview</h2>
|
||||||
|
<hr>
|
||||||
|
<table>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Price:</td><td>${sale.price}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Item count:</td><td>${sale.itemCount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Category:</td><td>${sale.category}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td valign="top">Shipping Info:</td>
|
||||||
|
<td>
|
||||||
|
<c:choose>
|
||||||
|
<c:when test="${sale.shipping}">
|
||||||
|
<table>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Type:</td>
|
||||||
|
<td>${sale.shippingType}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Date:</td>
|
||||||
|
<td>
|
||||||
|
<spring:bind path="sale.shipDate">
|
||||||
|
${status.value}
|
||||||
|
</spring:bind>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</c:when>
|
||||||
|
<c:otherwise>
|
||||||
|
No shipping required: you're picking up the items
|
||||||
|
</c:otherwise>
|
||||||
|
</c:choose>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Base amount:</td><td>${sale.amount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Delivery cost:</td><td>${sale.deliveryCost}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Discount:</td><td>${sale.savings} (Discount rate: ${sale.discountRate})</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><hr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Total cost</b>:</td><td>${sale.totalCost}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="buttonBar">
|
||||||
|
<form action="<c:url value="/index.jsp"/>">
|
||||||
|
<input type="submit" class="button" value="Home">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%@ include file="includeBottom.jsp" %>
|
||||||
17
spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/error.jsp
Normal file
17
spring-webflow-sandbox/src/main/webapp/WEB-INF/jsp/error.jsp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<%@ include file="includeTop.jsp" %>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="insert">
|
||||||
|
<img src="images/webflow-logo.jpg"/>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<span class="error">
|
||||||
|
Duplicate submit of the same transaction not allowed!
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<A href="pos.htm?_flowId=sellitem">Sell a new item</A>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%@ include file="includeBottom.jsp" %>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div id="copyright">
|
||||||
|
<p>© Copyright 2004-2007, <a href="http://www.springframework.org">www.springframework.org</a>, under the terms of the Apache 2.0 software license.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<%@ page contentType="text/html" %>
|
||||||
|
<%@ page session="false" %>
|
||||||
|
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||||
|
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
|
||||||
|
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
|
||||||
|
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Sell an item</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||||
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="logo">
|
||||||
|
<img src="images/spring-logo.jpg" alt="Logo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="navigation">
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<%@ include file="includeTop.jsp" %>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="insert"><img src="images/webflow-logo.jpg"/></div>
|
||||||
|
<h2>Enter price and item count</h2>
|
||||||
|
<hr>
|
||||||
|
<table>
|
||||||
|
<form:form commandName="sale" method="post">
|
||||||
|
<tr>
|
||||||
|
<td>Price:</td>
|
||||||
|
<td><form:input path="price" /></td><td><form:errors path="price" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Item count:</td>
|
||||||
|
<td><form:input path="itemCount" /></td><td><form:errors path="itemCount" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="buttonBar">
|
||||||
|
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}">
|
||||||
|
<input type="submit" class="button" name="_eventId_submit" value="Next">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</form:form>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%@ include file="includeBottom.jsp" %>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<%@ include file="includeTop.jsp" %>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="insert"><img src="images/webflow-logo.jpg"/></div>
|
||||||
|
<h2>Enter shipping information</h2>
|
||||||
|
<hr>
|
||||||
|
<table>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Price:</td><td>${sale.price}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Item count:</td><td>${sale.itemCount}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Category:</td><td>${sale.category}</td>
|
||||||
|
<tr class="readOnly">
|
||||||
|
<td>Shipping:</td><td>${sale.shipping}</td>
|
||||||
|
</tr>
|
||||||
|
<form:form commandName="sale" method="post">
|
||||||
|
<tr>
|
||||||
|
<td>Shipping type:</td>
|
||||||
|
<td>
|
||||||
|
<spring:bind path="sale.shippingType">
|
||||||
|
<select name="${status.expression}">
|
||||||
|
<option value="S" <c:if test="${status.value=='S'}">selected</c:if>>
|
||||||
|
Standard (10 extra cost)
|
||||||
|
</option>
|
||||||
|
<option value="E" <c:if test="${status.value=='E'}">selected</c:if>>
|
||||||
|
Express (20 extra cost)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</spring:bind>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Ship date (DD/MM/YYYY):</td>
|
||||||
|
<td>
|
||||||
|
<form:input path="shipDate" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="buttonBar">
|
||||||
|
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}">
|
||||||
|
<input type="submit" class="button" name="_eventId_submit" value="Next">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</form:form>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%@ include file="includeBottom.jsp" %>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
A general purpose controller for the entire "Point of Sale (POS)" application,
|
||||||
|
exposed at the /pos.htm URL. The id of a flow to launch should be passed
|
||||||
|
in using the "_flowId" request parameter: e.g. /pos.htm?_flowId=sellitem-flow
|
||||||
|
-->
|
||||||
|
<bean name="/pos.htm" class="org.springframework.webflow.executor.mvc.FlowController">
|
||||||
|
<property name="flowExecutor" ref="flowExecutor" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- Maps flow view-state view names to JSP templates -->
|
||||||
|
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
|
||||||
|
<property name="prefix" value="/WEB-INF/jsp/" />
|
||||||
|
<property name="suffix" value=".jsp" />
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:flow="http://www.springframework.org/schema/webflow-config"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||||
|
http://www.springframework.org/schema/webflow-config
|
||||||
|
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Launches new flow executions and resumes existing executions -->
|
||||||
|
<flow:executor id="flowExecutor" registry-ref="flowRegistry">
|
||||||
|
<flow:execution-listeners>
|
||||||
|
<flow:listener ref="entityManagerManager" />
|
||||||
|
<flow:listener ref="listener" criteria="sellitem-flow" />
|
||||||
|
</flow:execution-listeners>
|
||||||
|
</flow:executor>
|
||||||
|
|
||||||
|
<!-- Creates the registry of flow definitions for this application -->
|
||||||
|
<flow:registry id="flowRegistry">
|
||||||
|
<flow:location path="/WEB-INF/flows/**/*-flow.xml" />
|
||||||
|
</flow:registry>
|
||||||
|
|
||||||
|
<!-- Observes the lifecycle of sellitem-flow executions -->
|
||||||
|
<bean id="listener"
|
||||||
|
class="org.springframework.webflow.samples.sellitem.SellItemFlowExecutionListener" />
|
||||||
|
<bean id="entityManagerManager" class="org.springframework.webflow.execution.ExtendedFlowExecutionListenerInterceptor">
|
||||||
|
<constructor-arg>
|
||||||
|
<bean class="org.springframework.webflow.jpa.OpenEntityManagerPerConversationFlowListener">
|
||||||
|
<constructor-arg ref="entityManagerLifecycleController"/>
|
||||||
|
</bean>
|
||||||
|
</constructor-arg>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
||||||
54
spring-webflow-sandbox/src/main/webapp/WEB-INF/web.xml
Normal file
54
spring-webflow-sandbox/src/main/webapp/WEB-INF/web.xml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
|
||||||
|
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
|
||||||
|
version="2.4">
|
||||||
|
|
||||||
|
<context-param>
|
||||||
|
<param-name>webAppRootKey</param-name>
|
||||||
|
<param-value>swf-sellitem.root</param-value>
|
||||||
|
</context-param>
|
||||||
|
|
||||||
|
<context-param>
|
||||||
|
<param-name>contextConfigLocation</param-name>
|
||||||
|
<param-value>
|
||||||
|
classpath:org/springframework/webflow/samples/sellitem/services-config.xml
|
||||||
|
</param-value>
|
||||||
|
</context-param>
|
||||||
|
|
||||||
|
<context-param>
|
||||||
|
<param-name>log4jConfigLocation</param-name>
|
||||||
|
<param-value>/WEB-INF/classes/log4j.properties</param-value>
|
||||||
|
</context-param>
|
||||||
|
|
||||||
|
<listener>
|
||||||
|
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
|
<listener>
|
||||||
|
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>sellitem</servlet-name>
|
||||||
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||||
|
<init-param>
|
||||||
|
<param-name>contextConfigLocation</param-name>
|
||||||
|
<param-value>
|
||||||
|
/WEB-INF/sellitem-servlet-config.xml
|
||||||
|
/WEB-INF/sellitem-webflow-config.xml
|
||||||
|
</param-value>
|
||||||
|
</init-param>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>sellitem</servlet-name>
|
||||||
|
<url-pattern>*.htm</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<welcome-file-list>
|
||||||
|
<welcome-file>index.jsp</welcome-file>
|
||||||
|
</welcome-file-list>
|
||||||
|
|
||||||
|
</web-app>
|
||||||
BIN
spring-webflow-sandbox/src/main/webapp/images/spring-logo.jpg
Normal file
BIN
spring-webflow-sandbox/src/main/webapp/images/spring-logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
spring-webflow-sandbox/src/main/webapp/images/webflow-logo.jpg
Normal file
BIN
spring-webflow-sandbox/src/main/webapp/images/webflow-logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
58
spring-webflow-sandbox/src/main/webapp/index.jsp
Normal file
58
spring-webflow-sandbox/src/main/webapp/index.jsp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<%@ page session="true" %> <%-- make sure we have a session --%>
|
||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
<DIV align="left">Sell Item - A Spring Web Flow Sample</DIV>
|
||||||
|
|
||||||
|
<HR>
|
||||||
|
|
||||||
|
<DIV align="left">
|
||||||
|
<P>
|
||||||
|
<A href="pos.htm?_flowId=sellitem-flow">Sell Item</A>
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<P>
|
||||||
|
This Spring Web Flow sample application implements the example application
|
||||||
|
discussed in the article
|
||||||
|
<A href="http://www-128.ibm.com/developerworks/java/library/j-contin.html">
|
||||||
|
Use continuations to develop complex Web applications</A>. It illustrates
|
||||||
|
the following concepts:
|
||||||
|
<UL>
|
||||||
|
<LI>
|
||||||
|
Using the "_flowId" request parameter to let the view tell the web
|
||||||
|
flow controller which flow needs to be started.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Implementing a wizard using web flows.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Use of the FormAction to perform form processing, including the
|
||||||
|
FormAction's "setupForm" method to install custom property editors for
|
||||||
|
formatting text field values (shipDate).
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Using continuations to make the flow completely stable, no matter
|
||||||
|
how browser navigation buttons are used.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Using "conversation invalidation after completion" to prevent duplicate submits
|
||||||
|
of the same sale while taking advantage of continuations to allow back button
|
||||||
|
usage while the application transaction is in process.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
"Always redirect on pause" to benefit from the POST+REDIRECT+GET pattern with no special coding.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Using <A href="http://www.ognl.org/">OGNL</A> based conditional expressions.
|
||||||
|
</LI>
|
||||||
|
<LI>
|
||||||
|
Use of subflows to compose a multi-step business process from independently reusable modules.
|
||||||
|
</LI>
|
||||||
|
</UL>
|
||||||
|
</P>
|
||||||
|
</DIV>
|
||||||
|
|
||||||
|
<HR>
|
||||||
|
|
||||||
|
<DIV align="right"></DIV>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
58
spring-webflow-sandbox/src/main/webapp/style.css
Normal file
58
spring-webflow-sandbox/src/main/webapp/style.css
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
body {
|
||||||
|
width: 720px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#logo {
|
||||||
|
width: 720px;
|
||||||
|
height: 73px;
|
||||||
|
background: #86AEA5;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#navigation {
|
||||||
|
width: 720px;
|
||||||
|
height: 15px;
|
||||||
|
background: #E2F3B8;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#content {
|
||||||
|
width: 720px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#insert {
|
||||||
|
width: 120;
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonBar {
|
||||||
|
height: 1.5em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#copyright {
|
||||||
|
width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#copyright p {
|
||||||
|
text-align: center;
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
font-size: 75%;
|
||||||
|
color: div#336633;
|
||||||
|
margin-left: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readOnly {
|
||||||
|
color: rgb(192, 192, 192);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user