diff --git a/spring-webflow/.classpath b/spring-webflow/.classpath
index bf7da9a7..d6f7beee 100644
--- a/spring-webflow/.classpath
+++ b/spring-webflow/.classpath
@@ -35,8 +35,9 @@
-
+
+
diff --git a/spring-webflow/ivy.xml b/spring-webflow/ivy.xml
index db726492..27d1fb0f 100644
--- a/spring-webflow/ivy.xml
+++ b/spring-webflow/ivy.xml
@@ -60,9 +60,7 @@
-
-
@@ -72,7 +70,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListener.java b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListener.java
index c25a0df1..9589ddee 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListener.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListener.java
@@ -16,6 +16,7 @@
package org.springframework.webflow.support.persistence;
import org.hibernate.FlushMode;
+import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.SessionHolder;
@@ -72,12 +73,14 @@ import org.springframework.webflow.execution.ViewSelection;
*/
public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter {
- private static final String HIBERNATE_SESSION_ATTRIBUTE = "hibernate.session";
+ private static final String HIBERNATE_SESSION_ATTRIBUTE = "session";
private TransactionTemplate transactionTemplate;
private SessionFactory sessionFactory;
+ private Interceptor entityInterceptor;
+
/**
* Create a new Session-per-Conversation listener using giving Hibernate session factory.
* @param sessionFactory the session factory to use
@@ -87,6 +90,14 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
+ /**
+ * Sets the entity interceptor to attach to sessions opened by this listener.
+ * @param entityInterceptor the entity interceptor
+ */
+ public void setEntityInterceptor(Interceptor entityInterceptor) {
+ this.entityInterceptor = entityInterceptor;
+ }
+
public void sessionCreated(RequestContext context, FlowSession session) {
if (session.isRoot() && session.getDefinition().getAttributes().contains("persistenceContext")) {
Session hibernateSession = createSession(context);
@@ -130,7 +141,8 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter
// internal helpers
private Session createSession(RequestContext context) {
- Session session = sessionFactory.openSession();
+ Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor) : sessionFactory
+ .openSession());
session.setFlushMode(FlushMode.MANUAL);
return session;
}
@@ -144,6 +156,8 @@ public class HibernateFlowExecutionListener extends FlowExecutionListenerAdapter
}
private void unbind(Session session, RequestContext context) {
- TransactionSynchronizationManager.unbindResource(sessionFactory);
+ if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
+ TransactionSynchronizationManager.unbindResource(sessionFactory);
+ }
}
}
\ No newline at end of file
diff --git a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListener.java b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListener.java
index 4c2f488a..ca832fd9 100644
--- a/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListener.java
+++ b/spring-webflow/src/main/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListener.java
@@ -74,7 +74,7 @@ import org.springframework.webflow.execution.ViewSelection;
*/
public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter {
- private static final String ENTITY_MANAGER_ATTRIBUTE = "jpa.entityManager";
+ private static final String ENTITY_MANAGER_ATTRIBUTE = "entityManager";
private EntityManagerFactory entityManagerFactory;
@@ -114,8 +114,12 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter {
// this is a commit end state - start a new transaction that quickly commits
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
- // nothing to do - a flush will happen on commit automatically as this is a read-write
- // transaction
+ // necessary for JTA to enlist the entity manager in the transaction
+ try {
+ em.joinTransaction();
+ } catch (IllegalStateException e) {
+ // won't be necessary once Spring 2.0.7 is released
+ }
}
});
}
@@ -139,6 +143,8 @@ public class JpaFlowExecutionListener extends FlowExecutionListenerAdapter {
}
private void unbind(EntityManager em) {
- TransactionSynchronizationManager.unbindResource(entityManagerFactory);
+ if (TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
+ TransactionSynchronizationManager.unbindResource(entityManagerFactory);
+ }
}
}
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java
index 81d1584c..3e216f86 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/FlowExecutionImplTests.java
@@ -68,304 +68,311 @@ import org.springframework.webflow.test.MockFlowServiceLocator;
* @author Ben Hale
*/
public class FlowExecutionImplTests extends TestCase {
-
- public void testExceptionHandlingWithEvaluateAction() {
- FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("fooFlow.xml", getClass()));
- Flow flow = new FlowAssembler("fooFlow", flowBuilder).assembleFlow();
- FlowExecution flowExecution = new FlowExecutionImpl(flow);
- ViewSelection view = flowExecution.start(null, new MockExternalContext());
- assertEquals("showFooException", ((ApplicationView)view).getViewName());
- assertFalse(flowExecution.isActive());
- }
-
- public void testExceptionWhileHandlingException() {
- MockFlowServiceLocator serviceLocator = new MockFlowServiceLocator();
- serviceLocator.registerBean("testAction", new ExceptionThrowingAction());
- XmlFlowBuilder flowBuilder =
- new XmlFlowBuilder(new ClassPathResource("exceptionHandlingFlow.xml", this.getClass()));
- flowBuilder.setFlowServiceLocator(serviceLocator);
- Flow flow = new FlowAssembler("flow", flowBuilder).assembleFlow();
- FlowExecution flowExecution = new FlowExecutionImpl(flow);
- ViewSelection view = flowExecution.start(null, new MockExternalContext());
- assertEquals("failed", ((ApplicationView)view).getViewName());
- assertFalse(flowExecution.isActive());
+
+ public void testExceptionHandlingWithGlobalTransitionExceptionHandler() {
+ FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("globalTransitionExceptionHandler-flow.xml",
+ getClass()));
+ Flow flow = new FlowAssembler("test", flowBuilder).assembleFlow();
+ FlowExecution flowExecution = new FlowExecutionImpl(flow);
+ ViewSelection view = flowExecution.start(null, new MockExternalContext());
+ assertEquals("showFooException", ((ApplicationView) view).getViewName());
+ assertFalse(flowExecution.isActive());
+ }
+
+ public void testExceptionHandlingWithEvaluateAction() {
+ FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("fooFlow.xml", getClass()));
+ Flow flow = new FlowAssembler("fooFlow", flowBuilder).assembleFlow();
+ FlowExecution flowExecution = new FlowExecutionImpl(flow);
+ ViewSelection view = flowExecution.start(null, new MockExternalContext());
+ assertEquals("showFooException", ((ApplicationView) view).getViewName());
+ assertFalse(flowExecution.isActive());
+ }
+
+ public void testExceptionWhileHandlingException() {
+ MockFlowServiceLocator serviceLocator = new MockFlowServiceLocator();
+ serviceLocator.registerBean("testAction", new ExceptionThrowingAction());
+ XmlFlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("exceptionHandlingFlow.xml", this
+ .getClass()));
+ flowBuilder.setFlowServiceLocator(serviceLocator);
+ Flow flow = new FlowAssembler("flow", flowBuilder).assembleFlow();
+ FlowExecution flowExecution = new FlowExecutionImpl(flow);
+ ViewSelection view = flowExecution.start(null, new MockExternalContext());
+ assertEquals("failed", ((ApplicationView) view).getViewName());
+ assertFalse(flowExecution.isActive());
+ }
+
+ public void testFlowExecutionListener() {
+ Flow flow = new Flow("myFlow");
+ DefaultAttributeMapper inputMapper = new DefaultAttributeMapper();
+ MappingBuilder mapping = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser());
+ inputMapper.addMapping(mapping.source("name").target("flowScope.name").value());
+ flow.setInputMapper(inputMapper);
+ ActionState actionState = new ActionState(flow, "actionState");
+ actionState.getActionList().add(new TestAction());
+ actionState.getTransitionSet().add(new Transition(onEvent("success"), toState("viewState")));
+
+ ViewState viewState = new ViewState(flow, "viewState");
+ viewState.setViewSelector(selectView("myView"));
+ viewState.getTransitionSet().add(new Transition(onEvent("submit"), toState("subFlowState")));
+
+ Flow subFlow = new Flow("mySubFlow");
+ ViewState state1 = new ViewState(subFlow, "subFlowViewState");
+ state1.setViewSelector(selectView("mySubFlowViewName"));
+ state1.getTransitionSet().add(new Transition(onEvent("submit"), toState("finish")));
+ new EndState(subFlow, "finish");
+
+ SubflowState subflowState = new SubflowState(flow, "subFlowState", subFlow);
+ subflowState.getTransitionSet().add(new Transition(onEvent("finish"), toState("finish")));
+
+ EndState endState = new EndState(flow, "finish");
+ endState.getEntryActionList().add(new AbstractAction() {
+ protected Event doExecute(RequestContext context) throws Exception {
+ throw new IllegalStateException("Whoops!");
+ }
+ });
+ TransitionExecutingStateExceptionHandler handler = new TransitionExecutingStateExceptionHandler();
+ handler.add(Exception.class, "error");
+ endState.getExceptionHandlerSet().add(handler);
+
+ new EndState(flow, "error");
+
+ MockFlowExecutionListener flowExecutionListener = new MockFlowExecutionListener();
+ FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow,
+ new FlowExecutionListener[] { flowExecutionListener }, null);
+ LocalAttributeMap input = new LocalAttributeMap();
+ input.put("name", "value");
+ assertTrue(!flowExecutionListener.isStarted());
+ flowExecution.start(input, new MockExternalContext());
+ assertTrue(flowExecutionListener.isStarted());
+ assertTrue(flowExecutionListener.isPaused());
+ assertTrue(!flowExecutionListener.isExecuting());
+ assertEquals(1, flowExecutionListener.getEventsSignaledCount());
+ assertEquals(0, flowExecutionListener.getFlowNestingLevel());
+ assertEquals(2, flowExecutionListener.getTransitionCount());
+ assertEquals("value", flowExecution.getActiveSession().getScope().getString("name"));
+ flowExecution.signalEvent("submit", new MockExternalContext());
+ assertTrue(!flowExecutionListener.isExecuting());
+ assertEquals(2, flowExecutionListener.getEventsSignaledCount());
+ assertEquals(1, flowExecutionListener.getFlowNestingLevel());
+ assertEquals(4, flowExecutionListener.getTransitionCount());
+ flowExecution.signalEvent("submit", new MockExternalContext());
+ assertTrue(!flowExecutionListener.isExecuting());
+ assertEquals(0, flowExecutionListener.getFlowNestingLevel());
+ assertEquals(4, flowExecutionListener.getEventsSignaledCount());
+ assertEquals(7, flowExecutionListener.getTransitionCount());
+ assertEquals(1, flowExecutionListener.getExceptionsThrown());
+ assertTrue(!flowExecution.isActive());
+ }
+
+ public void testLoopInFlow() throws Exception {
+ AbstractFlowBuilder builder = new AbstractFlowBuilder() {
+ public void buildStates() throws FlowBuilderException {
+ addViewState("viewState", "viewName", new Transition[] { transition(on(submit()), to("viewState")),
+ transition(on(finish()), to("endState")) });
+ addEndState("endState");
+ }
+ };
+ Flow flow = new FlowAssembler("flow", builder).assembleFlow();
+ FlowExecution flowExecution = new FlowExecutionImpl(flow);
+ ApplicationView view = (ApplicationView) flowExecution.start(null, new MockExternalContext());
+ assertNotNull(view);
+ assertEquals("viewName", view.getViewName());
+ for (int i = 0; i < 10; i++) {
+ view = (ApplicationView) flowExecution.signalEvent("submit", new MockExternalContext());
+ assertEquals("viewName", view.getViewName());
}
+ assertTrue(flowExecution.isActive());
+ flowExecution.signalEvent("finish", new MockExternalContext());
+ assertFalse(flowExecution.isActive());
+ }
- public void testFlowExecutionListener() {
- Flow flow = new Flow("myFlow");
- DefaultAttributeMapper inputMapper = new DefaultAttributeMapper();
- MappingBuilder mapping = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser());
- inputMapper.addMapping(mapping.source("name").target("flowScope.name").value());
- flow.setInputMapper(inputMapper);
- ActionState actionState = new ActionState(flow, "actionState");
- actionState.getActionList().add(new TestAction());
- actionState.getTransitionSet().add(new Transition(onEvent("success"), toState("viewState")));
+ public void testLoopInFlowWithSubFlow() throws Exception {
+ AbstractFlowBuilder childBuilder = new AbstractFlowBuilder() {
+ public void buildStates() throws FlowBuilderException {
+ addActionState("doOtherStuff", new AbstractAction() {
+ private int executionCount = 0;
- ViewState viewState = new ViewState(flow, "viewState");
- viewState.setViewSelector(selectView("myView"));
- viewState.getTransitionSet().add(new Transition(onEvent("submit"), toState("subFlowState")));
-
- Flow subFlow = new Flow("mySubFlow");
- ViewState state1 = new ViewState(subFlow, "subFlowViewState");
- state1.setViewSelector(selectView("mySubFlowViewName"));
- state1.getTransitionSet().add(new Transition(onEvent("submit"), toState("finish")));
- new EndState(subFlow, "finish");
-
- SubflowState subflowState = new SubflowState(flow, "subFlowState", subFlow);
- subflowState.getTransitionSet().add(new Transition(onEvent("finish"), toState("finish")));
-
- EndState endState = new EndState(flow, "finish");
- endState.getEntryActionList().add(new AbstractAction() {
- protected Event doExecute(RequestContext context) throws Exception {
- throw new IllegalStateException("Whoops!");
+ protected Event doExecute(RequestContext context) throws Exception {
+ executionCount++;
+ if (executionCount < 2) {
+ return success();
}
- });
- TransitionExecutingStateExceptionHandler handler = new TransitionExecutingStateExceptionHandler();
- handler.add(Exception.class, "error");
- endState.getExceptionHandlerSet().add(handler);
+ return error();
+ }
+ },
+ new Transition[] { transition(on(success()), to(finish())),
+ transition(on(error()), to("stopTest")) });
+ addEndState(finish());
+ addEndState("stopTest");
+ }
+ };
+ final Flow childFlow = new FlowAssembler("flow", childBuilder).assembleFlow();
+ AbstractFlowBuilder parentBuilder = new AbstractFlowBuilder() {
+ public void buildStates() throws FlowBuilderException {
+ addActionState("doStuff", new AbstractAction() {
+ protected Event doExecute(RequestContext context) throws Exception {
+ return success();
+ }
+ }, transition(on(success()), to("startSubFlow")));
+ addSubflowState("startSubFlow", childFlow, null, new Transition[] {
+ transition(on(finish()), to("startSubFlow")), transition(on("stopTest"), to("stopTest")) });
+ addEndState("stopTest");
+ }
+ };
+ Flow parentFlow = new FlowAssembler("parentFlow", parentBuilder).assembleFlow();
- new EndState(flow, "error");
+ FlowExecution flowExecution = new FlowExecutionImpl(parentFlow);
+ flowExecution.start(null, new MockExternalContext());
+ assertFalse(flowExecution.isActive());
+ }
- MockFlowExecutionListener flowExecutionListener = new MockFlowExecutionListener();
- FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow,
- new FlowExecutionListener[] { flowExecutionListener }, null);
- LocalAttributeMap input = new LocalAttributeMap();
- input.put("name", "value");
- assertTrue(!flowExecutionListener.isStarted());
- flowExecution.start(input, new MockExternalContext());
- assertTrue(flowExecutionListener.isStarted());
- assertTrue(flowExecutionListener.isPaused());
- assertTrue(!flowExecutionListener.isExecuting());
- assertEquals(1, flowExecutionListener.getEventsSignaledCount());
- assertEquals(0, flowExecutionListener.getFlowNestingLevel());
- assertEquals(2, flowExecutionListener.getTransitionCount());
- assertEquals("value", flowExecution.getActiveSession().getScope().getString("name"));
- flowExecution.signalEvent("submit", new MockExternalContext());
- assertTrue(!flowExecutionListener.isExecuting());
- assertEquals(2, flowExecutionListener.getEventsSignaledCount());
- assertEquals(1, flowExecutionListener.getFlowNestingLevel());
- assertEquals(4, flowExecutionListener.getTransitionCount());
- flowExecution.signalEvent("submit", new MockExternalContext());
- assertTrue(!flowExecutionListener.isExecuting());
- assertEquals(0, flowExecutionListener.getFlowNestingLevel());
- assertEquals(4, flowExecutionListener.getEventsSignaledCount());
- assertEquals(7, flowExecutionListener.getTransitionCount());
- assertEquals(1, flowExecutionListener.getExceptionsThrown());
- assertTrue(!flowExecution.isActive());
+ public void testExtensiveFlowNavigationScenario1() {
+ XmlFlowBuilder builder = new XmlFlowBuilder(new ClassPathResource("testFlow1.xml", XmlFlowBuilderTests.class),
+ new TestFlowServiceLocator());
+ FlowAssembler assembler = new FlowAssembler("testFlow1", builder);
+ FlowExecution execution = new FlowExecutionImpl(assembler.assembleFlow());
+ MockExternalContext context = new MockExternalContext();
+ execution.start(null, context);
+ assertEquals("viewState1", execution.getActiveSession().getState().getId());
+ assertNotNull(execution.getActiveSession().getScope().get("items"));
+ execution.signalEvent("event1", context);
+ assertTrue(!execution.isActive());
+ }
+
+ public void testExtensiveFlowNavigationScenario2() {
+ XmlFlowBuilder builder = new XmlFlowBuilder(new ClassPathResource("testFlow1.xml", XmlFlowBuilderTests.class),
+ new TestFlowServiceLocator());
+ LocalAttributeMap attributes = new LocalAttributeMap();
+ attributes.put("scenario2", Boolean.TRUE);
+ FlowAssembler assembler = new FlowAssembler("testFlow1", attributes, builder);
+ FlowExecution execution = new FlowExecutionImpl(assembler.assembleFlow());
+ MockExternalContext context = new MockExternalContext();
+ execution.start(null, context);
+ assertEquals("viewState2", execution.getActiveSession().getState().getId());
+ assertNotNull(execution.getActiveSession().getScope().get("items"));
+ execution.signalEvent("event2", context);
+ assertTrue(!execution.isActive());
+ }
+
+ public void testFlashScope() {
+ FlowExecution execution = new FlowExecutionImpl(new FlashScopeFlow());
+ MockExternalContext context = new MockExternalContext();
+ execution.start(null, context);
+ execution.refresh(context);
+ execution.refresh(context);
+ execution.signalEvent("view", context);
+ }
+
+ public void testExceptionFromInputMapper() {
+ FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml", getClass()));
+ Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
+ FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
+ try {
+ flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
+ fail("Should have thrown a FlowExecutionException, not any other type");
+ } catch (FlowExecutionException e) {
+ }
+ }
+
+ public void testExceptionWithListener() {
+ FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml", getClass()));
+ Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
+ FlowExceptionListener listener = new FlowExceptionListener();
+ FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
+ flowExecution.setListeners(new FlowExecutionListeners(new FlowExecutionListener[] { listener }));
+ try {
+ flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
+ fail("Should have thrown a FlowExecutionException, not any other type");
+ } catch (FlowExecutionException e) {
}
- public void testLoopInFlow() throws Exception {
- AbstractFlowBuilder builder = new AbstractFlowBuilder() {
- public void buildStates() throws FlowBuilderException {
- addViewState("viewState", "viewName", new Transition[] { transition(on(submit()), to("viewState")),
- transition(on(finish()), to("endState")) });
- addEndState("endState");
- }
- };
- Flow flow = new FlowAssembler("flow", builder).assembleFlow();
- FlowExecution flowExecution = new FlowExecutionImpl(flow);
- ApplicationView view = (ApplicationView)flowExecution.start(null, new MockExternalContext());
- assertNotNull(view);
- assertEquals("viewName", view.getViewName());
- for (int i = 0; i < 10; i++) {
- view = (ApplicationView)flowExecution.signalEvent("submit", new MockExternalContext());
- assertEquals("viewName", view.getViewName());
+ assertTrue("Listener should have been called on exception", listener.getExceptionFired());
+ }
+
+ public void testExceptionWithHandler() {
+ FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml", getClass()));
+ Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
+ FlowExceptionHandler handler = new FlowExceptionHandler();
+ flow.getExceptionHandlerSet().add(handler);
+ FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
+ flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
+ assertTrue("Handler should have been called on exception", handler.getExceptionHandled());
+ }
+
+ public static TransitionCriteria onEvent(String event) {
+ return new EventIdTransitionCriteria(event);
+ }
+
+ protected TargetStateResolver toState(String stateId) {
+ return new DefaultTargetStateResolver(stateId);
+ }
+
+ public static ViewSelector selectView(String viewName) {
+ return new ApplicationViewSelector(new StaticExpression(viewName));
+ }
+
+ private class FlashScopeFlow extends Flow {
+
+ public FlashScopeFlow() {
+ super("flashScopeFlow");
+
+ ActionState state1 = new ActionState(this, "action");
+ state1.getActionList().add(new Action() {
+ public Event execute(RequestContext context) throws Exception {
+ context.getFlashScope().put("flashScopedValue", "flashScopedValue");
+ return new Event(this, "success");
}
- assertTrue(flowExecution.isActive());
- flowExecution.signalEvent("finish", new MockExternalContext());
- assertFalse(flowExecution.isActive());
- }
+ });
+ state1.getTransitionSet().add(new Transition(toState("view")));
- public void testLoopInFlowWithSubFlow() throws Exception {
- AbstractFlowBuilder childBuilder = new AbstractFlowBuilder() {
- public void buildStates() throws FlowBuilderException {
- addActionState("doOtherStuff", new AbstractAction() {
- private int executionCount = 0;
-
- protected Event doExecute(RequestContext context) throws Exception {
- executionCount++;
- if (executionCount < 2) {
- return success();
- }
- return error();
- }
- },
- new Transition[] { transition(on(success()), to(finish())),
- transition(on(error()), to("stopTest")) });
- addEndState(finish());
- addEndState("stopTest");
- }
- };
- final Flow childFlow = new FlowAssembler("flow", childBuilder).assembleFlow();
- AbstractFlowBuilder parentBuilder = new AbstractFlowBuilder() {
- public void buildStates() throws FlowBuilderException {
- addActionState("doStuff", new AbstractAction() {
- protected Event doExecute(RequestContext context) throws Exception {
- return success();
- }
- }, transition(on(success()), to("startSubFlow")));
- addSubflowState("startSubFlow", childFlow, null, new Transition[] {
- transition(on(finish()), to("startSubFlow")), transition(on("stopTest"), to("stopTest")) });
- addEndState("stopTest");
- }
- };
- Flow parentFlow = new FlowAssembler("parentFlow", parentBuilder).assembleFlow();
-
- FlowExecution flowExecution = new FlowExecutionImpl(parentFlow);
- flowExecution.start(null, new MockExternalContext());
- assertFalse(flowExecution.isActive());
- }
-
- public void testExtensiveFlowNavigationScenario1() {
- XmlFlowBuilder builder = new XmlFlowBuilder(new ClassPathResource("testFlow1.xml", XmlFlowBuilderTests.class),
- new TestFlowServiceLocator());
- FlowAssembler assembler = new FlowAssembler("testFlow1", builder);
- FlowExecution execution = new FlowExecutionImpl(assembler.assembleFlow());
- MockExternalContext context = new MockExternalContext();
- execution.start(null, context);
- assertEquals("viewState1", execution.getActiveSession().getState().getId());
- assertNotNull(execution.getActiveSession().getScope().get("items"));
- execution.signalEvent("event1", context);
- assertTrue(!execution.isActive());
- }
-
- public void testExtensiveFlowNavigationScenario2() {
- XmlFlowBuilder builder = new XmlFlowBuilder(new ClassPathResource("testFlow1.xml", XmlFlowBuilderTests.class),
- new TestFlowServiceLocator());
- LocalAttributeMap attributes = new LocalAttributeMap();
- attributes.put("scenario2", Boolean.TRUE);
- FlowAssembler assembler = new FlowAssembler("testFlow1", attributes, builder);
- FlowExecution execution = new FlowExecutionImpl(assembler.assembleFlow());
- MockExternalContext context = new MockExternalContext();
- execution.start(null, context);
- assertEquals("viewState2", execution.getActiveSession().getState().getId());
- assertNotNull(execution.getActiveSession().getScope().get("items"));
- execution.signalEvent("event2", context);
- assertTrue(!execution.isActive());
- }
-
- public void testFlashScope() {
- FlowExecution execution = new FlowExecutionImpl(new FlashScopeFlow());
- MockExternalContext context = new MockExternalContext();
- execution.start(null, context);
- execution.refresh(context);
- execution.refresh(context);
- execution.signalEvent("view", context);
- }
-
- public void testExceptionFromInputMapper() {
- FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
- getClass()));
- Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
- FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
- try {
- flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
- fail("Should have thrown a FlowExecutionException, not any other type");
- } catch (FlowExecutionException e) {
+ ViewState state2 = new ViewState(this, "view");
+ state2.getEntryActionList().add(new Action() {
+ public Event execute(RequestContext context) throws Exception {
+ assertTrue(context.getFlashScope().contains("flashScopedValue"));
+ return new Event(this, "success");
}
- }
-
- public void testExceptionWithListener() {
- FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
- getClass()));
- Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
- FlowExceptionListener listener = new FlowExceptionListener();
- FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
- flowExecution.setListeners(new FlowExecutionListeners(new FlowExecutionListener[] { listener }));
- try {
- flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
- fail("Should have thrown a FlowExecutionException, not any other type");
- } catch (FlowExecutionException e) {
+ });
+ state2.getTransitionSet().add(new Transition(toState("end")));
+
+ EndState state3 = new EndState(this, "end");
+ state3.getEntryActionList().add(new Action() {
+ public Event execute(RequestContext context) throws Exception {
+ assertFalse(context.getFlashScope().contains("flashScopedValue"));
+ return new Event(this, "success");
}
-
- assertTrue("Listener should have been called on exception", listener.getExceptionFired());
+ });
}
-
- public void testExceptionWithHandler() {
- FlowBuilder flowBuilder = new XmlFlowBuilder(new ClassPathResource("runtime-exception.xml",
- getClass()));
- Flow flow = new FlowAssembler("runtime-exception", flowBuilder).assembleFlow();
- FlowExceptionHandler handler = new FlowExceptionHandler();
- flow.getExceptionHandlerSet().add(handler);
- FlowExecutionImpl flowExecution = new FlowExecutionImpl(flow);
- flowExecution.start(new LocalAttributeMap(), new MockExternalContext());
- assertTrue("Handler should have been called on exception", handler.getExceptionHandled());
+ }
+
+ private class FlowExceptionListener extends FlowExecutionListenerAdapter {
+
+ private boolean exceptionFired = false;
+
+ public boolean getExceptionFired() {
+ return exceptionFired;
}
- public static TransitionCriteria onEvent(String event) {
- return new EventIdTransitionCriteria(event);
+ public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
+ exceptionFired = true;
+ }
+ }
+
+ private class FlowExceptionHandler implements FlowExecutionExceptionHandler {
+
+ private boolean exceptionHandled = false;
+
+ public boolean getExceptionHandled() {
+ return exceptionHandled;
}
- protected TargetStateResolver toState(String stateId) {
- return new DefaultTargetStateResolver(stateId);
- }
-
- public static ViewSelector selectView(String viewName) {
- return new ApplicationViewSelector(new StaticExpression(viewName));
+ public ViewSelection handle(FlowExecutionException exception, RequestControlContext context) {
+ exceptionHandled = true;
+ return ViewSelection.NULL_VIEW;
}
- private class FlashScopeFlow extends Flow {
-
- public FlashScopeFlow() {
- super("flashScopeFlow");
-
- ActionState state1 = new ActionState(this, "action");
- state1.getActionList().add(new Action() {
- public Event execute(RequestContext context) throws Exception {
- context.getFlashScope().put("flashScopedValue", "flashScopedValue");
- return new Event(this, "success");
- }
- });
- state1.getTransitionSet().add(new Transition(toState("view")));
-
- ViewState state2 = new ViewState(this, "view");
- state2.getEntryActionList().add(new Action() {
- public Event execute(RequestContext context) throws Exception {
- assertTrue(context.getFlashScope().contains("flashScopedValue"));
- return new Event(this, "success");
- }
- });
- state2.getTransitionSet().add(new Transition(toState("end")));
-
- EndState state3 = new EndState(this, "end");
- state3.getEntryActionList().add(new Action() {
- public Event execute(RequestContext context) throws Exception {
- assertFalse(context.getFlashScope().contains("flashScopedValue"));
- return new Event(this, "success");
- }
- });
- }
+ public boolean handles(FlowExecutionException exception) {
+ return true;
}
- private class FlowExceptionListener extends FlowExecutionListenerAdapter {
-
- private boolean exceptionFired = false;
-
- public boolean getExceptionFired() {
- return exceptionFired;
- }
-
- public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
- exceptionFired = true;
- }
- }
-
- private class FlowExceptionHandler implements FlowExecutionExceptionHandler {
-
- private boolean exceptionHandled = false;
-
- public boolean getExceptionHandled() {
- return exceptionHandled;
- }
-
- public ViewSelection handle(FlowExecutionException exception, RequestControlContext context) {
- exceptionHandled = true;
- return ViewSelection.NULL_VIEW;
- }
-
- public boolean handles(FlowExecutionException exception) {
- return true;
- }
-
- }
+ }
}
\ No newline at end of file
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/fooFlow.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/fooFlow.xml
index 81afca17..5e8e8c4a 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/fooFlow.xml
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/fooFlow.xml
@@ -13,9 +13,9 @@
-
+
-
+
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/globalTransitionExceptionHandler-flow.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/globalTransitionExceptionHandler-flow.xml
new file mode 100644
index 00000000..d187a345
--- /dev/null
+++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/impl/globalTransitionExceptionHandler-flow.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListenerTests.java
index a74cdb95..1fe9e219 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListenerTests.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/HibernateFlowExecutionListenerTests.java
@@ -74,7 +74,7 @@ public class HibernateFlowExecutionListenerTests extends TestCase {
assertSessionBound();
// Session created and bound to conversation
- final Session hibSession = (Session) context.getConversationScope().get("hibernate.session");
+ final Session hibSession = (Session) context.getConversationScope().get("session");
assertNotNull("Should have been populated", hibSession);
listener.paused(context, ViewSelection.NULL_VIEW);
assertSessionNotBound();
@@ -212,6 +212,16 @@ public class HibernateFlowExecutionListenerTests extends TestCase {
}
+ public void testExceptionThrownWithNothingBound() {
+ assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN"));
+ MockRequestContext context = new MockRequestContext();
+ MockFlowSession flowSession = new MockFlowSession();
+ flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true");
+ assertSessionNotBound();
+ listener.exceptionThrown(context, new FlowExecutionException("foo", "bar", "test"));
+ assertSessionNotBound();
+ }
+
private DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListenerTests.java
new file mode 100644
index 00000000..4035e0c6
--- /dev/null
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/JpaFlowExecutionListenerTests.java
@@ -0,0 +1,119 @@
+package org.springframework.webflow.support.persistence;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaTemplate;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.webflow.engine.EndState;
+import org.springframework.webflow.test.MockFlowSession;
+import org.springframework.webflow.test.MockRequestContext;
+
+public class JpaFlowExecutionListenerTests extends TestCase {
+
+ private EntityManagerFactory entityManagerFactory;
+
+ private JpaFlowExecutionListener jpaListener;
+
+ private JdbcTemplate jdbcTemplate;
+
+ private JpaTemplate jpaTemplate;
+
+ protected void setUp() throws Exception {
+ DataSource dataSource = getDataSource();
+ populateDataBase(dataSource);
+ jdbcTemplate = new JdbcTemplate(dataSource);
+ entityManagerFactory = getEntityManagerFactory(dataSource);
+ JpaTransactionManager tm = new JpaTransactionManager(entityManagerFactory);
+ jpaListener = new JpaFlowExecutionListener(entityManagerFactory, tm);
+ jpaTemplate = new JpaTemplate(entityManagerFactory);
+ }
+
+ public void testFlowNotAPersistenceContext() {
+ MockRequestContext context = new MockRequestContext();
+ MockFlowSession flowSession = new MockFlowSession();
+ jpaListener.sessionCreated(context, flowSession);
+ assertSessionNotBound();
+ }
+
+ public void testFlowCommitsInSingleRequest() {
+ assertEquals("Table should only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN"));
+ MockRequestContext context = new MockRequestContext();
+ MockFlowSession flowSession = new MockFlowSession();
+ flowSession.getDefinitionInternal().getAttributeMap().put("persistenceContext", "true");
+ jpaListener.sessionCreated(context, flowSession);
+ assertSessionBound();
+
+ TestBean bean = new TestBean(1, "Keith Donald");
+ jpaTemplate.persist(bean);
+ assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN"));
+
+ EndState endState = new EndState(flowSession.getDefinitionInternal(), "success");
+ endState.getAttributeMap().put("commit", Boolean.TRUE);
+ flowSession.setState(endState);
+
+ jpaListener.sessionEnded(context, flowSession, null);
+ assertEquals("Table should only have two rows", 2, jdbcTemplate.queryForInt("select count(*) from T_BEAN"));
+ assertSessionNotBound();
+ assertFalse(flowSession.getScope().contains("hibernate.session"));
+ }
+
+ private DataSource getDataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
+ dataSource.setUrl("jdbc:hsqldb:mem:jpa");
+ dataSource.setUsername("sa");
+ dataSource.setPassword("");
+ return dataSource;
+ }
+
+ private void populateDataBase(DataSource dataSource) {
+ Connection connection = null;
+ try {
+ connection = dataSource.getConnection();
+ connection.createStatement().execute("drop table T_BEAN if exists;");
+ connection.createStatement().execute(
+ "create table T_BEAN (ID integer primary key, NAME varchar(50) not null);");
+ connection.createStatement().execute("insert into T_BEAN (ID, NAME) values (0, 'Ben Hale');");
+ } catch (SQLException e) {
+ throw new RuntimeException("SQL exception occurred acquiring connection", e);
+ } finally {
+ if (connection != null) {
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ }
+ }
+ }
+ }
+
+ private EntityManagerFactory getEntityManagerFactory(DataSource dataSource) throws Exception {
+ LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
+ factory.setDataSource(dataSource);
+ factory.setPersistenceXmlLocation("classpath:org/springframework/webflow/support/persistence/persistence.xml");
+ TopLinkJpaVendorAdapter toplink = new TopLinkJpaVendorAdapter();
+ factory.setJpaVendorAdapter(toplink);
+ factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
+ factory.afterPropertiesSet();
+ return (EntityManagerFactory) factory.getObject();
+ }
+
+ private void assertSessionNotBound() {
+ assertNull(TransactionSynchronizationManager.getResource(entityManagerFactory));
+ }
+
+ private void assertSessionBound() {
+ assertNotNull(TransactionSynchronizationManager.getResource(entityManagerFactory));
+ }
+}
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.hbm.xml b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.hbm.xml
index 77ccca4f..be154398 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.hbm.xml
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.hbm.xml
@@ -3,7 +3,7 @@
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
-
+
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.java b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.java
index 9db4b94b..083b571a 100644
--- a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.java
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/TestBean.java
@@ -17,14 +17,35 @@ package org.springframework.webflow.support.persistence;
public class TestBean {
- private long entityId;
+ private Long entityId;
private String name;
+ public TestBean() {
+
+ }
+
public TestBean(String name) {
this.name = name;
}
+ public TestBean(long id, String name) {
+ this.entityId = new Long(id);
+ this.name = name;
+ }
+
+ public Long getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(Long entityId) {
+ this.entityId = entityId;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
public String getName() {
return name;
}
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/orm.xml b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/orm.xml
new file mode 100644
index 00000000..2d16749b
--- /dev/null
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/orm.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/persistence.xml b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/persistence.xml
new file mode 100644
index 00000000..6c7f2e18
--- /dev/null
+++ b/spring-webflow/src/test/java/org/springframework/webflow/support/persistence/persistence.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ org/springframework/webflow/support/persistence/orm.xml
+
+
+ true
+
+
+
+