How to persist Spring State Machine with other machineId

28 Views Asked by At

I use Spring State Machine framework in my project. I need to utilize state machines with different configurations loaded from database. Also I need to persist into database information about state machines with it's context.

I utilize Data Multi Persist example with such configuration loaded via JSON:

[
  {
    "@id": "10",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
    "spel": "T(System).out.println('Hello! Exit state New')"
  },
  {
    "@id": "11",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
    "spel": "T(System).out.println('Hello! Exit state Active')"
  },
  {
    "@id": "12",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
    "spel": "T(System).out.println('Hello! Exit state Locked')"
  },
  {
    "@id": "13",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
    "spel": "T(System).out.println('Hello! Exit state Dismissed')"
  },
   {
    "@id": "1",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "AdminStatesChain",
    "initial": true,
    "state": "New",
    "stateActions": ["10"]
  },
  {
    "@id": "2",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "AdminStatesChain",
    "initial": false,
    "state": "Dismissed",
    "stateActions": ["13"]
  },
  {
    "@id": "3",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "SupervisorStatesChain",
    "initial": true,
    "state": "New",
    "stateActions": ["10"]
  },
  {
    "@id": "4",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "SupervisorStatesChain",
    "initial": false,
    "state": "Active",
    "stateActions": ["11"]
  },
  {
    "@id": "5",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "SupervisorStatesChain",
    "initial": false,
    "state": "Dismissed",
    "stateActions": ["13"]
  },
  {
    "@id": "6",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "RegularUserStatesChain",
    "initial": true,
    "state": "New",
    "stateActions": ["10"]
  },
  {
    "@id": "7",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "RegularUserStatesChain",
    "initial": false,
    "state": "Active",
    "stateActions": ["11"]
  },
  {
    "@id": "8",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "RegularUserStatesChain",
    "initial": false,
    "state": "Locked",
    "stateActions": ["12"]
  },
  {
    "@id": "9",
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
    "machineId": "RegularUserStatesChain",
    "initial": false,
    "state": "Dismissed",
    "stateActions": ["13"]
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "AdminStatesChain",
    "source": "1",
    "target": "2",
    "event": "E1",
    "kind": "EXTERNAL"
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "SupervisorStatesChain",
    "source": "3",
    "target": "4",
    "event": "E1",
    "kind": "EXTERNAL"
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "SupervisorStatesChain",
    "source": "4",
    "target": "5",
    "event": "E2",
    "kind": "EXTERNAL"
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "RegularUserStatesChain",
    "source": "6",
    "target": "7",
    "event": "E1",
    "kind": "EXTERNAL"
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "RegularUserStatesChain",
    "source": "7",
    "target": "8",
    "event": "E2",
    "kind": "EXTERNAL"
  },
  {
    "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
    "machineId": "RegularUserStatesChain",
    "source": "8",
    "target": "9",
    "event": "E3",
    "kind": "EXTERNAL"
  }
]

This is configuration file:

@Slf4j
@Configuration
@RequiredArgsConstructor
public class StateMachineConfig {

    @Bean
    public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister(
            JpaStateMachineRepository jpaStateMachineRepository) {
        return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
    }

    @Bean
    public StateMachineService<String, String> stateMachineService(
            StateMachineFactory<String, String> stateMachineFactory,
            StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister) {
        return new DefaultStateMachineService<String, String>(stateMachineFactory, stateMachineRuntimePersister);
    }

    @Bean
    public StateMachinePersister<String, String, String> persister(
            StateMachinePersist<String, String, String> defaultPersist) {

        return new DefaultStateMachinePersister<>(defaultPersist);
    }

    @Configuration
    @EnableStateMachineFactory(name = "RegularUserFactory")
    public static class Config extends StateMachineConfigurerAdapter<String, String> {

        @Autowired
        private StateRepository<? extends RepositoryState> stateRepository;

        @Autowired
        private TransitionRepository<? extends RepositoryTransition> transitionRepository;

        @Autowired
        private StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister;

        @Override
        public void configure(StateMachineConfigurationConfigurer<String, String> config)
                throws Exception {

            StateMachineLogListener listener = new StateMachineLogListener();

            config.withConfiguration()
                    .autoStartup(true)
                    .listener(listener);

            config
                    .withPersistence()
                    .runtimePersister(stateMachineRuntimePersister);
        }

        @Override
        public void configure(StateMachineModelConfigurer<String, String> model)
                throws Exception {
            model
                    .withModel()
                    .factory(modelFactory());
        }

        @Bean
        public StateMachineModelFactory<String, String> modelFactory() {

            return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
        }
    }
}

I test my configuration via REST controller:

@RestController
@RequestMapping("sm/users")
@RequiredArgsConstructor
public class SMUserController {

    @Qualifier("RegularUserFactory")
    private final StateMachineFactory<String, String> regularUserStateMachineFactory;

    private final StateMachinePersister<String, String, String> persister;

    @GetMapping("{userId}/{chainId}/{event}")
    public String feedAndGetStates(@PathVariable(value = "userId") Long userId,
                                   @PathVariable(value = "chainId") String chainId,
                                   @PathVariable(value = "event") String event) throws Exception {
        StringBuilder resultBuilder = new StringBuilder();

        String stateMachineId = chainId + userId;

        StateMachine<String, String> stateMachine = regularUserStateMachineFactory.getStateMachine(chainId);

        resultBuilder
                .append("State machine Id: ").append(stateMachine.getId())
                .append(". State before event ").append(event).append(": ");
        resultBuilder.append(stateMachine.getState().getId()).append("\n");
        stateMachine
                .sendEvent(Mono.just(MessageBuilder
                        .withPayload(event).build()))
                .blockLast();
        persister.persist(stateMachine, stateMachineId);
        resultBuilder.append("State machine Id: ").append(stateMachine.getId())
                .append(". State after event: ").append(stateMachine.getState().getId());
        return resultBuilder.toString();
    }

The problem is that persiter saves stateMachine with id = chainId but I want to save stateMachine with id = stateMachineId separate for every userId. Other possible solution is to create StateMachineFactories with different names and configurations but I can't force load configuration from database for a specific machineId (f.e. RegularUserStatesChain).

0

There are 0 best solutions below