2

I did all the setup for error handling

@PostConstruct
public void addStateMachineInterceptor() {
    stateMachine.getStateMachineAccessor().withRegion().addStateMachineInterceptor(interceptor);
    stateMachine.getStateMachineAccessor().doWithRegion(errorinterceptor);
}

created interceptor to handle error:

@Service
public class OrderStateMachineFunction<T> implements StateMachineFunction<StateMachineAccess<String, String>> {
    @Override
    public void apply(StateMachineAccess<String, String> stringStringStateMachineAccess) {
        stringStringStateMachineAccess.addStateMachineInterceptor(
                new StateMachineInterceptorAdapter<String, String>() {
                    @Override
                    public Exception stateMachineError(StateMachine<String, String> stateMachine,
                                                       Exception exception) {
                        // return null indicating handled error
                        return exception;
                    }
                });
    }
}

But I can't see the call going into OrderStateMachineFunction, when we throw the exception from the action.

And after that state machine behave some wired way, like it stops calling preStateChange method after this.stateMachine.sendEvent(eventData);. It seems state machine breaks down after you throw the exception from the action.

    @Service
    public class OrderStateMachineInterceptor extends StateMachineInterceptorAdapter {

        @Override
        public void preStateChange(State newState, Message message, Transition transition, StateMachine stateMachine) {
            System.out.println("Manish");
    }
}

After trying few bit, I have seen that if I comment the resetStateMachine, it works as expected, but without that I am not able to inform the currentstate to state machine:

    public boolean fireEvent(Object data, String previousState, String event) {
        Message<String> eventData = MessageBuilder.withPayload(event)
                .setHeader(DATA_KEY, data)
                .build();
        this.stateMachine.stop();

//        this.stateMachine
//                .getStateMachineAccessor()
//                .withRegion()
//                .resetStateMachine(new DefaultStateMachineContext(previousState, event, eventData.getHeaders(), null));

        this.stateMachine.start();
        return this.stateMachine.sendEvent(eventData);
    }
krmanish007
  • 6,749
  • 16
  • 58
  • 100
  • It seems that error handling is currently not an option, see https://github.com/spring-projects/spring-statemachine/issues/183. At least, I've never been able to get it to work. – Paul Jun 21 '16 at 20:58
  • This issue seems to be fixed at https://github.com/spring-projects/spring-statemachine/issues/240 – krmanish007 Oct 02 '17 at 08:52

2 Answers2

2

Not sure if you still need this. But I bumped into similar issue. I wanted to propagate exception from state machine to the caller. I implemented StateMachineInterceptor. And inside the state machine transition functions I am setting:

   try
        {
            ..
        }
        catch (WhateverException e)
        {
            stateMachine.setStateMachineError(e);
            throw e;
        }

Then inside the interceptor's stateMachineError method, I have added the Exception in the extendedState map:

public Exception stateMachineError(StateMachine<States, Events> stateMachine, Exception exception)
{ 
stateMachine.getExtendedState().getVariables().put("ERROR", exception);
logger.error("StateMachineError", exception);
return exception;
}

Inside resetStateMachine I have added the interceptor to the statemachine.

a.addStateMachineInterceptor(new LoggingStateMachineInterceptor());

Then when I am calling the sendEvent method, I am doing this:

 if (stateMachine.hasStateMachineError())
        {
            throw (Exception) svtStateMachine.getExtendedState().getVariables().get("ERROR");
        }

This is returning the WhateverException right to the caller. Which in my case is a RestController.

theAntonym
  • 39
  • 1
  • 8
0

The approach I'm taking here is combining the extended state to store errors with an error action.

If an expected exception happens in your action and any class inside of it, I include it in the extended state context

context.getExtendedState().getVariables().put("error", MyBussinessException);

then, on my error action (configured like this)

        .withExternal()
        .source(State.INIT)
        .target(State.STARTED)
        .action(action, errorAction)
        .event(Events.INIT)

Outside machine context, I always check if that field is present or not, and translate it to proper response code.

If any exception is thrown from action, error action will be triggered. There you can check known errors (and let them bubble up), or include a new errors (if that was unexpected)

public class ErrorAction implements Action<States, Events> {

    @Override
    public void execute(StateContext<States, Events> context) {
       if(!context.getExtendedState().getVariables().containsKey("error")
        context.getExtendedState().getVariables().put("error", new GenericException());
    }
}