2

Is there a way to configure common external transition for multiple events? i.e.

  1. For all states X,Y,Z -> CANCELED on CANCEL event. Where X,Y,Z - sources, CANCELED - target state
  2. For CANCELED state run common internal transition on all events

I hame more than 20 states to apply this transition to and it is not convenient to manually configure all of them.

I am using EnumStateMachineConfigurerAdapter and @EnableStateMachineFactory to configure state machine, my configuration looks like this

@Configuration
@EnableStateMachineFactory
public class ContentOnlyStateMachineConfig extends EnumStateMachineConfigurerAdapter<TaskState, WorkflowEvent> {

private final Action<TaskState, WorkflowEvent> someAction;
private final Action<TaskState, WorkflowEvent> anotherAction;

@Autowired
public ContentOnlyStateMachineConfig(
        final Action<TaskState, WorkflowEvent> someAction,
        final Action<TaskState, WorkflowEvent> anotherAction) {
    this.someAction = someAction;
    this.anotherAction = anotherAction;
}




@Override
public void configure(final StateMachineStateConfigurer<TaskState, WorkflowEvent> states)
        throws Exception {

    states
            .withStates()
            .initial(TaskState.CONTENT)
            .end(TaskState.COMPLETE)
            .state(TaskState.CONTENT, someAction)
            .state(TaskState.COMPLETE, anotherAction);

}

@Override
public void configure(
        final StateMachineTransitionConfigurer<TaskState, WorkflowEvent> transitions)
        throws Exception {

    transitions
            .withExternal()
            .source(TaskState.CONTENT).target(TaskState.SOME_STATE)
            .event(WorkflowEvent.EXCEPTION)
            .and()
            .withExternal()
            .source(TaskState.EXCEPTION).target(TaskState.CANCELED)
            .event(WorkflowEvent.CANCEL)
            .and()
            .withExternal()
            .source(TaskState.SOME_STATE_2).target(TaskState.SOME_STATE_1)
            .event(WorkflowEvent.SOME_EVENT_3)
            .and()
            .withExternal()
            .source(TaskState.SOME_STATE).target(TaskState.SOME_STATE_2)
            .event(WorkflowEvent.SOME_EVENT_2)
            .and()
            .withExternal()
            .source(TaskState.SOME_STATE).target(TaskState.COMPLETE)
            .event(WorkflowEvent.ADMIN_ACCEPT)
            .and()
            .withExternal()
            .source(TaskState.SOME_STATE).target(TaskState.CONTENT)
            .event(WorkflowEvent.SOME_EVENT);
}
}
Aliaksei Stadnik
  • 1,692
  • 3
  • 15
  • 32

2 Answers2

4

There's not shortcuts to allow user to create a spaghetti ;). Having said that, if your statechart after you have drawn it in a paper looks like spaghetti rather that clear statechart, I'd argue you're doing something wrong.

Keeping everything in a one flat machine easily creates this kind of state explosion whey configuration itself is starting to look difficult to understand. Usually this is a moment when machine designer needs to start thinking about use of nested substates. If you have multiple states where same event should take machine to same state, you already have a shared functionality in those states.

Putting those states into substates would then allow one single transition from their parent state to transit away. This is usually a model how things are done in machines.

Janne Valkealahti
  • 2,552
  • 1
  • 14
  • 12
  • 2
    Thank you for your answer! I was wondering: Let's say you have very simple SM - A->B->C->D and E is the "there was error" terminal state. The following logic - if error happens in the actions of any state (A,B,C,D), then transition to E. Is there a way to do that, without describing all transitions? Imagine there's a well designed SM with 10 states and you want to apply the same requirement - if any error, transition to a common error state. This easily leads to spaghetti diagram. How is this meant to be handled? I can create a full question for this. – hovanessyan Feb 17 '19 at 08:52
  • @HarshulPandav describing all transitions for all states. – hovanessyan Oct 19 '19 at 11:09
1

It's not supported out-of-the-box a way to configure a transition with multiple sources and a single target.

Need to do this explicitly.
For example:

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {

    transitions.withExternal()
    // normal transitions configuration ..

    // common error transitions
    for (var state : EnumSet.of(States.A, States,B, States.C)) { 
        transitions.withExternal().source(state).target(States.ERROR).event(ERROR_EVENT);
    }
}

(Can't use Stream.of() since withExternal() throws a checked exception..)

Omer Vertman
  • 171
  • 1
  • 3