Grigori
Grigori

Reputation: 45

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?

Upvotes: 3

Views: 4457

Answers (2)

hovanessyan
hovanessyan

Reputation: 31443

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.

Upvotes: 4

Grigori
Grigori

Reputation: 45

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!

Upvotes: 1

Related Questions