From 8bb852cc7907e8d53dfde2a860b86e0722d2eccf Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Thu, 29 Dec 2016 09:58:04 +0000 Subject: [PATCH] Make autostart smarter - Factory now listens machine event if autostart is requested which should give fully started machine i.e. if asynch executor is used. Bails out after 30 seconds. Further tweaks like allowing user to define this time or conditionally not wait could be added later. - Fix typos. - Fixes #298 --- .../config/AbstractStateMachineFactory.java | 27 +++++++- .../StateMachineFactoryTests.java | 69 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java index 33b057ea..7358fa72 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +55,7 @@ import org.springframework.statemachine.config.model.verifier.CompositeStateMach import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier; import org.springframework.statemachine.ensemble.DistributedStateMachine; import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.monitor.StateMachineMonitor; import org.springframework.statemachine.region.Region; import org.springframework.statemachine.security.StateMachineSecurityInterceptor; @@ -367,7 +370,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS } /** - * Sett state machine monitor. + * Set state machine monitor. * * @param stateMachineMonitor the state machine monitor */ @@ -377,7 +380,16 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS private StateMachine delegateAutoStartup(StateMachine delegate) { if (handleAutostartup && delegate instanceof SmartLifecycle && ((SmartLifecycle) delegate).isAutoStartup()) { + AutostartListener autostartListener = new AutostartListener<>(); + delegate.addStateListener(autostartListener); ((SmartLifecycle)delegate).start(); + try { + autostartListener.latch.await(30, TimeUnit.SECONDS); + } catch (Exception e) { + log.warn("Waited 30 seconds for machine to start as autostart was requested, machine may not be ready"); + } finally { + delegate.removeStateListener(autostartListener); + } } return delegate; } @@ -866,4 +878,17 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS Collection> entryActions, Collection> exitActions, PseudoState pseudoState); + /** + * Simple utility listener waiting machine to get started if + * autostart was requestes. Needed for machine to be ready + * if async executor is used. + */ + private static class AutostartListener extends StateMachineListenerAdapter { + final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void stateMachineStarted(StateMachine stateMachine) { + latch.countDown(); + } + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java index 87aa739d..3a82d4b3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java @@ -20,12 +20,15 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.messaging.support.MessageBuilder; @@ -36,6 +39,8 @@ import org.springframework.statemachine.config.StateMachineFactory; import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; public class StateMachineFactoryTests extends AbstractStateMachineTests { @@ -118,6 +123,29 @@ public class StateMachineFactoryTests extends AbstractStateMachineTests { assertThat(machine2.getState().getIds(), contains(TestStates.S1)); } + @SuppressWarnings({ "unchecked" }) + @Test + public void testMachineFromFactoryWithAsyncExecutorAutoStart() throws Exception { + context.register(Config6.class); + context.refresh(); + + ObjectStateMachineFactory stateMachineFactory = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, ObjectStateMachineFactory.class); + StateMachine machine = stateMachineFactory.getStateMachine(); + + // factory waits machine to get started so we + // should have state immediately + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + + // still need to listen state chance manually before + // checking state as execution happens in a thread + TestStateMachineListener listener = new TestStateMachineListener(); + machine.addStateListener(listener); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(listener.latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); + } + @Configuration @EnableStateMachineFactory static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -255,4 +283,45 @@ public class StateMachineFactoryTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachineFactory + static class Config6 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) throws Exception { + config + .withConfiguration() + .autoStartup(true) + .taskExecutor(new SimpleAsyncTaskExecutor()); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1); + } + } + + static class TestStateMachineListener extends StateMachineListenerAdapter { + + CountDownLatch latch = new CountDownLatch(1); + + @Override + public void stateChanged(State from, State to) { + latch.countDown(); + } + } + }