21. Listening State Machine Events

There are use cases where you just want to know what is happening with a state machine, react to something or simply get logging for debugging purposes. SSM provides interfaces for adding listeners which then gives an option to get callback when various state changes, actions, etc are happening.

You basically have two options, either to listen Spring application context events or directly attach listener to a state machine. Both of these basically will provide same information where one is producing events as event classes and other producing callbacks via a listener interface. Both of these have pros and cons which will be discussed later.

21.1 Application Context Events

Application context events classes are OnTransitionStartEvent, OnTransitionEvent, OnTransitionEndEvent, OnStateExitEvent, OnStateEntryEvent, OnStateChangedEvent, OnStateMachineStart and OnStateMachineStop and others which extends base event class StateMachineEvent These can be used as is with spring typed ApplicationListener.

StateMachine will send context events via StateMachineEventPublisher it’s set. Default implementation is automatically created if @Configuration class is annotated with @EnableStateMachine.

public class StateMachineApplicationEventListener
        implements ApplicationListener<StateMachineEvent> {

    @Override
    public void onApplicationEvent(StateMachineEvent event) {
    }
}

@Configuration
public class ListenerConfig {

    @Bean
    public StateMachineApplicationEventListener contextListener() {
        return new StateMachineApplicationEventListener();
    }
}

Context events are also automatically enabled via @EnableStateMachine with machine builder StateMachine registered as a bean as shown below.

@Configuration
@EnableStateMachine
public class ManualBuilderConfig {

    @Bean
    public StateMachine<String, String> stateMachine() throws Exception {

        Builder<String, String> builder = StateMachineBuilder.builder();
        builder.configureStates()
            .withStates()
                .initial("S1")
                .state("S2");
        builder.configureTransitions()
            .withExternal()
                .source("S1")
                .target("S2")
                .event("E1");
        return builder.build();
    }
}

21.2 State Machine Listener

Using StateMachineListener you can either extend it and implement all callback methods or use StateMachineListenerAdapter class which contains stub method implementations and choose which ones to override.

public class StateMachineEventListener
        extends StateMachineListenerAdapter<States, Events> {

    @Override
    public void stateChanged(State<States, Events> from, State<States, Events> to) {
    }

    @Override
    public void stateEntered(State<States, Events> state) {
    }

    @Override
    public void stateExited(State<States, Events> state) {
    }

    @Override
    public void transition(Transition<States, Events> transition) {
    }

    @Override
    public void transitionStarted(Transition<States, Events> transition) {
    }

    @Override
    public void transitionEnded(Transition<States, Events> transition) {
    }

    @Override
    public void stateMachineStarted(StateMachine<States, Events> stateMachine) {
    }

    @Override
    public void stateMachineStopped(StateMachine<States, Events> stateMachine) {
    }

    @Override
    public void eventNotAccepted(Message<Events> event) {
    }

    @Override
    public void extendedStateChanged(Object key, Object value) {
    }

    @Override
    public void stateMachineError(StateMachine<States, Events> stateMachine, Exception exception) {
    }

    @Override
    public void stateContext(StateContext<States, Events> stateContext) {
    }
}

In above example we simply created our own listener class StateMachineEventListener which extends StateMachineListenerAdapter.

Listener method stateContext gives an access to various StateContext changes on a different stages. More about about it in section Chapter 19, Using StateContext.

Once you have your own listener defined, it can be registered into a state machine via its interface as shown below. It’s just a matter of flavour if it’s hooked up within a spring configuration or done manually at any time of application life-cycle.

public class Config7 {

    @Autowired
    StateMachine<States, Events> stateMachine;

    @Bean
    public StateMachineEventListener stateMachineEventListener() {
        StateMachineEventListener listener = new StateMachineEventListener();
        stateMachine.addStateListener(listener);
        return listener;
    }

}

21.3 Limitations and Problems

Spring application context is not a fastest eventbus out there so it is advised to give some thought what is a rate of events state machine is sending. For better performance it may be better to use StateMachineListener interface. For this specific reason it is possible to use contextEvents flag with @EnableStateMachine and @EnableStateMachineFactory to disable Spring application context events as shown above.

@Configuration
@EnableStateMachine(contextEvents = false)
public class Config8
        extends EnumStateMachineConfigurerAdapter<States, Events> {
}

@Configuration
@EnableStateMachineFactory(contextEvents = false)
public class Config9
        extends EnumStateMachineConfigurerAdapter<States, Events> {
}