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?
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.