Yunus
Yunus

Reputation: 731

Spring StateMachineFactory getStateMachine returns currents state as initial state

I working with Spring State Machines. I followed the documentation and other stuff. I need to keep different state machines for each employee. But when called factory to get state machine each time returns a state machine whose state is initial.

public enum EmployeeEvent {
    CREATED, CHECKSTARTED, APPROVE, ACTIVATED
}

public enum EmployeeState {
    ADDED, INCHECK, APPROVED, ACTIVE
}

States are events like above. Configuration is below

@Configuration
@EnableStateMachineFactory
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<EmployeeState, EmployeeEvent> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<EmployeeState, EmployeeEvent> config)
            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<EmployeeState, EmployeeEvent> states)
            throws Exception {
        states
                .withStates()
                .initial(EmployeeState.ADDED)
                .states(EnumSet.allOf(EmployeeState.class))
                .end(EmployeeState.ACTIVE);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<EmployeeState, EmployeeEvent> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(EmployeeState.ADDED).target(EmployeeState.INCHECK).event(EmployeeEvent.CHECKSTARTED)
                .and()
                .withExternal()
                .source(EmployeeState.INCHECK).target(EmployeeState.APPROVED).event(EmployeeEvent.APPROVE)
                .and()
                .withExternal()
                .source(EmployeeState.APPROVED).target(EmployeeState.ACTIVE).event(EmployeeEvent.ACTIVATED);
    }

    @Bean
    public StateMachineListener<EmployeeState, EmployeeEvent> listener() {
        return new StateMachineListenerAdapter<EmployeeState, EmployeeEvent>() {
            @Override
            public void stateChanged(State<EmployeeState, EmployeeEvent> from, State<EmployeeState, EmployeeEvent> to) {
                System.out.println("State change to " + to.getId());
            }
        };
    }
}

factory build code like below

 private StateMachine<EmployeeState, EmployeeEvent> build(Long employeeId){
        Optional<Employee> byId = employeeRepository.findById(employeeId);
        Employee employee = byId.get();
        StateMachine<EmployeeState, EmployeeEvent> stateMachine = stateMachineFactory.getStateMachine(Long.toString(employee.getId()));
        stateMachine.stop();
        stateMachine.getStateMachineAccessor().doWithAllRegions(sma -> {
            sma.addStateMachineInterceptor(new StateMachineInterceptorAdapter<>() {
                @Override
                public void preStateChange(State<EmployeeState, EmployeeEvent> state, Message<EmployeeEvent> message, Transition<EmployeeState, EmployeeEvent> transition, StateMachine<EmployeeState, EmployeeEvent> stateMachine, StateMachine<EmployeeState, EmployeeEvent> stateMachine1) {
                    Optional.ofNullable(message).ifPresent(msg -> {
                        Long employeeId = Long.class.cast(msg.getHeaders().getOrDefault("EMPLOYEE_ID", -1));
                        Employee employee = employeeRepository.getById(employeeId);
                        employee.setState(state.getId());
                        employeeRepository.save(employee);
                    });
                }
            });
            sma.resetStateMachine(new DefaultStateMachineContext<>(EmployeeState.valueOf(employee.getState().name()), null, null,  null));
        });
        stateMachine.start();
        return stateMachine;
    }

But I recognized that when I called build multiple times, it returns a state machine whose state is initial. Then I wrote tests like below.

    @Test
    public  void sameUuid(){
        UUID uuid = UUID.randomUUID();
        StateMachine<EmployeeState, EmployeeEvent> stateMachine = factory.getStateMachine(uuid);
        stateMachine.sendEvent(EmployeeEvent.CHECKSTARTED);
        StateMachine<EmployeeState, EmployeeEvent> stateMachine2 = factory.getStateMachine(uuid);
        assertEquals(stateMachine.getUuid(), stateMachine2.getUuid());
        assertEquals(stateMachine.getState().getId(), stateMachine2.getState().getId());
    }

Uuids are the same but states are different.`

    @Test
    public  void sameId(){
        StateMachine<EmployeeState, EmployeeEvent> stateMachine = factory.getStateMachine("1");
        stateMachine.sendEvent(EmployeeEvent.CHECKSTARTED);
        StateMachine<EmployeeState, EmployeeEvent> stateMachine2 = factory.getStateMachine("1");
        assertEquals(stateMachine.getId(), stateMachine2.getId());
        assertEquals(stateMachine.getUuid(), stateMachine2.getUuid());
        assertEquals(stateMachine.getState().getId(), stateMachine2.getState().getId());
    }

Ids are the same but UUIDs are different.

I need one state machine for my one employee object. How can be solved?

Upvotes: 0

Views: 1672

Answers (2)

Dauren D
Dauren D

Reputation: 169

You need to change the way you retrieving state machines.

The @EnableStateMachineFactory annotation is used properly, but to store and restore the state machine better to use SpringStateMachineService that checks if there is SM with the given ID and restore it, or creates a new one.

See the detailed answer on how to persist state machine and how to restore them from storage here

Upvotes: 0

Dmitrii B
Dmitrii B

Reputation: 2860

The reason is "@EnableStateMachineFactory" When you use it you will get the new instance of the machine from context each time. For getting singleton bean of the machine need you @EnableStateMachine annotation.

see more here

Upvotes: 1

Related Questions