Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 380
Grigori Avatar asked Jan 31 '26 13:01

Grigori


2 Answers

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.

like image 174
hovanessyan Avatar answered Feb 03 '26 03:02

hovanessyan


Solved! As it often happens, solution was too simple for me to observe it :(. So the method which sends an event to SM has boolean as return parameter. And if event was handled and processed it returns true, and false otherwise.

That's it - Just check return value!

like image 37
Grigori Avatar answered Feb 03 '26 02:02

Grigori



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!