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 + + + +