/* * Copyright 2004-2012 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.engine.impl; import junit.framework.TestCase; import org.springframework.binding.message.MessageBuilder; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.FlowExecutionExceptionHandler; import org.springframework.webflow.engine.RequestControlContext; import org.springframework.webflow.engine.State; import org.springframework.webflow.engine.StubViewFactory; import org.springframework.webflow.engine.Transition; import org.springframework.webflow.engine.ViewState; import org.springframework.webflow.engine.support.DefaultTargetStateResolver; import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.FlowExecutionListener; import org.springframework.webflow.execution.FlowExecutionListenerAdapter; import org.springframework.webflow.execution.FlowSession; import org.springframework.webflow.execution.MockFlowExecutionListener; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.springframework.webflow.test.MockExternalContext; import org.springframework.webflow.test.MockFlowExecutionKeyFactory; /** * General flow execution tests. * * @author Keith Donald * @author Erwin Vervaet * @author Ben Hale * @author Jeremy Grelle */ public class FlowExecutionImplTests extends TestCase { public void testStartAndEnd() { Flow flow = new Flow("flow"); new EndState(flow, "end"); MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); execution.start(null, context); assertTrue(execution.hasStarted()); assertFalse(execution.isActive()); assertTrue(execution.hasEnded()); try { execution.getActiveSession(); fail("should have failed"); } catch (IllegalStateException e) { } assertEquals(1, mockListener.getRequestsSubmittedCount()); assertEquals(1, mockListener.getRequestsProcessedCount()); assertEquals(1, mockListener.getSessionCreatingCount()); assertEquals(1, mockListener.getSessionStartingCount()); assertEquals(1, mockListener.getSessionStartedCount()); assertEquals(1, mockListener.getStateEnteringCount()); assertEquals(1, mockListener.getStateEnteredCount()); assertEquals(1, mockListener.getSessionEndingCount()); assertEquals(1, mockListener.getSessionEndedCount()); assertEquals(0, mockListener.getEventSignaledCount()); assertEquals(0, mockListener.getTransitionExecutingCount()); assertEquals(0, mockListener.getPausedCount()); assertEquals(0, mockListener.getResumingCount()); assertEquals(0, mockListener.getExceptionThrownCount()); assertEquals(0, mockListener.getFlowNestingLevel()); } public void testStartAndEndSavedMessages() { Flow flow = new Flow("flow"); new EndState(flow, "end"); MockFlowExecutionListener mockListener = new MockFlowExecutionListener() { public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap<?> input) { super.sessionStarting(context, session, input); context.getMessageContext().addMessage(new MessageBuilder().source("foo").defaultText("bar").build()); } }; FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); execution.start(null, context); assertTrue(execution.hasStarted()); assertFalse(execution.isActive()); assertTrue(execution.hasEnded()); assertNotNull(execution.getFlashScope().get("messagesMemento")); } public void testStartAndPause() { Flow flow = new Flow("flow"); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { // no op } }; MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); execution.start(null, context); assertTrue(execution.isActive()); assertEquals(1, mockListener.getPausedCount()); } public void testStartWithNullInputMap() { Flow flow = new Flow("flow"); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { // no op } }; MockFlowExecutionListener mockListener = new MockFlowExecutionListener() { public void sessionStarting(RequestContext context, FlowSession session, MutableAttributeMap<?> input) { super.sessionStarting(context, session, input); assertNotNull(input); } }; FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); execution.start(null, context); assertTrue(execution.isActive()); assertEquals(1, mockListener.getPausedCount()); } public void testStartExceptionThrownBeforeFirstSessionCreated() { Flow flow = new Flow("flow"); flow.getExceptionHandlerSet().add(new FlowExecutionExceptionHandler() { public boolean canHandle(FlowExecutionException exception) { return true; } public void handle(FlowExecutionException exception, RequestControlContext context) { throw new UnsupportedOperationException("Should not be called"); } }); new EndState(flow, "end"); FlowExecutionListener mockListener = new FlowExecutionListenerAdapter() { public void sessionCreating(RequestContext context, FlowDefinition definition) { assertFalse(context.getFlowExecutionContext().isActive()); throw new IllegalStateException("Oops"); } }; FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); assertNull(e.getStateId()); assertTrue(e.getCause() instanceof IllegalStateException); e.printStackTrace(); assertTrue(e.getCause().getMessage().equals("Oops")); } } public void testStartExceptionThrownByState() { Flow flow = new Flow("flow"); State state = new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { throw new IllegalStateException("Oops"); } }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); assertEquals(state.getId(), e.getStateId()); } } public void testStartFlowExecutionExceptionThrownByState() { Flow flow = new Flow("flow"); final FlowExecutionException e = new FlowExecutionException("flow", "state", "Oops"); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { throw e; } }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); try { execution.start(null, context); fail("Should have failed"); } catch (FlowExecutionException ex) { assertSame(e, ex); } } public void testStartExceptionThrownByStateHandledByFlowExceptionHandler() { Flow flow = new Flow("flow"); StubFlowExecutionExceptionHandler exceptionHandler = new StubFlowExecutionExceptionHandler(); flow.getExceptionHandlerSet().add(exceptionHandler); final FlowExecutionException e = new FlowExecutionException("flow", "state", "Oops"); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { throw e; } }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); execution.start(null, context); assertTrue(exceptionHandler.getHandled()); } public void testStartExceptionThrownByStateHandledByStateExceptionHandler() { Flow flow = new Flow("flow"); flow.getExceptionHandlerSet().add(new StubFlowExecutionExceptionHandler()); final FlowExecutionException e = new FlowExecutionException("flow", "state", "Oops"); State s = new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { throw e; } }; StubFlowExecutionExceptionHandler exceptionHandler = new StubFlowExecutionExceptionHandler(); s.getExceptionHandlerSet().add(exceptionHandler); FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); execution.start(null, context); assertTrue(exceptionHandler.getHandled()); } public void testExceptionHandledByNestedExceptionHandler() { Flow flow = new Flow("flow"); ExceptionThrowingExceptionHandler exceptionHandler = new ExceptionThrowingExceptionHandler(true); flow.getExceptionHandlerSet().add(exceptionHandler); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { throw new FlowExecutionException("flow", "state", "Oops"); } }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); assertFalse(execution.hasStarted()); execution.start(null, context); assertEquals(2, exceptionHandler.getHandleCount()); } public void testStartCannotCallTwice() { Flow flow = new Flow("flow"); new EndState(flow, "end"); FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); execution.start(null, context); try { execution.start(null, context); fail("Should've failed"); } catch (IllegalStateException e) { } } public void testResume() { Flow flow = new Flow("flow"); new ViewState(flow, "view", new StubViewFactory()); MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); execution.start(null, context); context = new MockExternalContext(); execution.resume(context); assertEquals(1, mockListener.getResumingCount()); assertEquals(2, mockListener.getPausedCount()); } public void testResumeNotAViewState() { Flow flow = new Flow("flow"); new State(flow, "state") { protected void doEnter(RequestControlContext context) throws FlowExecutionException { // no-op } }; MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); MockExternalContext context = new MockExternalContext(); execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); assertEquals(1, mockListener.getResumingCount()); fail("Should have failed"); } catch (FlowExecutionException e) { } } public void testResumeAfterEnding() { Flow flow = new Flow("flow"); new EndState(flow, "end"); FlowExecutionImpl execution = new FlowExecutionImpl(flow); MockExternalContext context = new MockExternalContext(); execution.start(null, context); try { execution.resume(context); fail("Should've failed"); } catch (IllegalStateException e) { } } public void testResumeException() { Flow flow = new Flow("flow"); ViewState state = new ViewState(flow, "view", new StubViewFactory()) { public void resume(RequestControlContext context) { throw new IllegalStateException("Oops"); } }; MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); assertEquals(state.getId(), e.getStateId()); assertEquals(1, mockListener.getResumingCount()); assertEquals(2, mockListener.getPausedCount()); } } public void testResumeFlowExecutionException() { Flow flow = new Flow("flow"); ViewState state = new ViewState(flow, "view", new StubViewFactory()) { public void resume(RequestControlContext context) { throw new FlowExecutionException("flow", "view", "oops"); } }; MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); execution.start(null, context); context = new MockExternalContext(); try { execution.resume(context); } catch (FlowExecutionException e) { assertEquals(flow.getId(), e.getFlowId()); assertEquals(state.getId(), e.getStateId()); assertEquals(1, mockListener.getResumingCount()); assertEquals(2, mockListener.getPausedCount()); } } public void testExecuteTransition() { Flow flow = new Flow("flow"); ViewState state = new ViewState(flow, "view", new StubViewFactory()) { public void resume(RequestControlContext context) { context.execute(getRequiredTransition(context)); } }; state.getTransitionSet().add(new Transition(new DefaultTargetStateResolver("finish"))); new EndState(flow, "finish"); MockFlowExecutionListener mockListener = new MockFlowExecutionListener(); FlowExecutionListener[] listeners = new FlowExecutionListener[] { mockListener }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setListeners(listeners); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); execution.start(null, context); assertEquals(0, mockListener.getTransitionExecutingCount()); execution.resume(context); assertTrue(execution.hasEnded()); assertEquals(1, mockListener.getTransitionExecutingCount()); } public void testRequestContextManagedOnStartAndResume() { Flow flow = new Flow("flow"); new ViewState(flow, "view", new StubViewFactory()) { public void resume(RequestControlContext context) { assertSame(context, RequestContextHolder.getRequestContext()); } }; FlowExecutionImpl execution = new FlowExecutionImpl(flow); execution.setKeyFactory(new MockFlowExecutionKeyFactory()); MockExternalContext context = new MockExternalContext(); execution.start(null, context); assertNull("RequestContext was not released", RequestContextHolder.getRequestContext()); context = new MockExternalContext(); execution.resume(context); assertNull("RequestContext was not released", RequestContextHolder.getRequestContext()); } private static class StubFlowExecutionExceptionHandler implements FlowExecutionExceptionHandler { private boolean handled; public boolean getHandled() { return handled; } public boolean canHandle(FlowExecutionException exception) { return true; } public void handle(FlowExecutionException exception, RequestControlContext context) { handled = true; } } private static class ExceptionThrowingExceptionHandler implements FlowExecutionExceptionHandler { private boolean throwOnlyOnce = true; private int handleCount; public ExceptionThrowingExceptionHandler(boolean throwOnlyOnce) { this.throwOnlyOnce = throwOnlyOnce; } public int getHandleCount() { return handleCount; } public boolean canHandle(FlowExecutionException exception) { return true; } public void handle(FlowExecutionException exception, RequestControlContext context) { this.handleCount++; if (throwOnlyOnce && "nested exception".equals(exception.getMessage())) { // No more exceptions } else { throw new FlowExecutionException(exception.getFlowId(), exception.getStateId(), "nested exception"); } } } }