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).