javaspringspring-statemachine

How to handle events that were not processed by spring state machine


Let's say we've got the following state machine configuration:

transitions.withExternal()
    .source(FIRST)
    .target(SECOND)
    .event(STEP_EVENT)

    .and()

    .source(SECOND)
    .target(EXIT)
    .event(EXIT_EVENT)

Events list: STEP_EVENT, EXIT_EVENT, UNUSED_EVENT

stateMachine.init(); 
// FIRST state

stateMachine.sendEvent(STEP_EVENT); 
/* state moves to SECOND 
because there is a transition with current state as a source 
and STEP_EVENT as transition event */

stateMachine.sendEvent(UNUSED_EVENT); 
/* no state change. 
This will trigger "eventNotAccepted(Message<Events> event)" 
in state machine listener, 
because UNUSED_EVENT is never mentioned in SM config */

stateMachine.sendEvent(STEP_EVENT); 
/* nothing will happen!!! 
No state change, as there is no transition 
which has current state (SECOND) as source 
and STEP_EVENT as transition event, 
and no eventNotAccepted call. 
But I need it, I want to fail here! */

stateMachine.sendEvent(EXIT_EVENT); 
// state will move to EXIT

The issue is that when I sent an event which is part of configuration but is not applicable for current state, nothing happens.

I don't know whether state didn't change because of a guard or because there is no transition with current state and my event.

Is there any way to handle such cases?


Solution

  • To log events which are not applicable for your current state you can use a StateMachine listener. There's a method called each time an event, which does not satisfy the defined transitions and events, is passed in the State Machine.

    In the state machine Configuration you need to override:

    public void configure(StateMachineConfigurationConfigurer<State, Event> config) {
      config.withConfiguration()
         .listener(customListener());
    }
    

    and implement your own Listener - the easiest way is to use the StateMachineListenerAdapter and override the eventNotAccepted(Message event) method:

    private StateMachineListenerAdapter<State, Event> customListener() {
      return new StateMachineEventListenerAdapter<State, Event>() {
    
        @Override
        public void eventNotAccepted(Message event) {
          //LOG which event was not accepted etc.
        }
      }
    }
    

    For logging results of guards - use log messages in the guards themselves.

    If you want to expose the reason outside of guards, you can construct a key-value pair and use the StateMachine's extended context to record the guard name and the reason why an event was rejected. The context can be used to construct a custom exception or communicate to the caller code what happened.