Can Cinar
Can Cinar

Reputation: 451

Default config for Spring State Machine with JPA

I aim to create a new state machine instance if it does not exist in the database, and store config and context in the database. However, I cannot set the default config in the database. If I don't put the machine ID explicitly in the database when I insert transitions, I get the "Must have at least one transition" exception when I try to create a new state machine.

Here is my configuration:

@Configuration
@RequiredArgsConstructor
@EnableStateMachine
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<>(stateMachineFactory, stateMachineRuntimePersister);
}

@Bean
public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() {
    StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean();
    factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")});
    return factoryBean;
}


@Configuration
@EnableStateMachineFactory
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 {
        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);
    }
}

Here is my controller:

 @RequestMapping("/state")
public String feedAndGetStates(
        @RequestParam(value = "events", required = false) List<String> events,
        @RequestParam(value = "machine", required = false, defaultValue = MACHINE_ID_1) String machine,
        Model model) throws Exception {


    StateMachine<String, String> stateMachine = getStateMachine(machine);
    if (events != null) {
        for (String event : events) {
            stateMachine
                    .sendEvent(Mono.just(MessageBuilder
                            .withPayload(event).build()))
                    .blockLast();
        }
    }

    StringBuilder contextBuf = new StringBuilder();

    StateMachineContext<String, String> stateMachineContext = stateMachinePersist.read(machine);
    if (stateMachineContext != null) {
        contextBuf.append(stateMachineContext.toString());
    }
    if (ObjectUtils.nullSafeEquals(machine, MACHINE_ID_2)) {
        stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R1);
        if (stateMachineContext != null) {
            contextBuf.append("\n---\n");
            contextBuf.append(stateMachineContext.toString());
        }
        stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R2);
        if (stateMachineContext != null) {
            contextBuf.append("\n---\n");
            contextBuf.append(stateMachineContext.toString());
        }
    }

    model.addAttribute("allMachines", MACHINES);
    model.addAttribute("machine", machine);
    model.addAttribute("currentMachine", currentStateMachine);
    model.addAttribute("allEvents", getEvents());
    model.addAttribute("messages", createMessages(listener.getMessages()));
    model.addAttribute("context", contextBuf.toString());
    return "states";
}

private synchronized StateMachine<String, String> getStateMachine(String machineId) throws Exception {
    listener.resetMessages();
    if (currentStateMachine == null) {
        currentStateMachine = stateMachineService.acquireStateMachine(machineId, false);
        currentStateMachine.addStateListener(listener);
        currentStateMachine.startReactively().block();
    } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
        stateMachineService.releaseStateMachine(currentStateMachine.getId());
        currentStateMachine.stopReactively().block();
        currentStateMachine = stateMachineService.acquireStateMachine(machineId, false);
        currentStateMachine.addStateListener(listener);
        currentStateMachine.startReactively().block();
    }
    return currentStateMachine;
}

I have these transitions in the database, without machineId because I am going to create machine run time and it will be assigned automatically.

Transition Table

Is there any way to define transitions in the database without machineId and to create a state machine with random machineId runtime? If I define transitions in class, I don't have to specify machineID, but when I define them in the DB, it doesn't work without machineId.

Upvotes: 1

Views: 39

Answers (0)

Related Questions