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(); + } + } + }