Refactor region handling and persistence

- Make kryo in AbstractKryoStateMachineSerialisationService aware of same classloader
  most likely use in an app. This takes away some of those weird kryo
  errors you see with a web apps.
- Add context references concept to StateMachineContext which can be used
  to store reference id and then individual running machines with regions
  can independently store their states. Whole machine state can then get
  restored more accurately.
- Add new `region(String id)` to StateConfigurer which can be used to set region id.
  This is equivalent as setting region id with json based machine structure where
  you need to define region id's with orthogonal regions are in use.
- Add new datajpamultipersist sample showing running regions and how those are
  persisted to a database.
- Fixes #617
- Fixes #605
- Fixes #615
This commit is contained in:
Janne Valkealahti
2019-01-12 08:36:51 +00:00
parent c700301767
commit 84ca0aec3e
34 changed files with 1297 additions and 51 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ public class JpaRepositoryStateMachinePersist<S, E> extends RepositoryStateMachi
protected JpaRepositoryStateMachine build(StateMachineContext<S, E> context, Object contextObj, byte[] serialisedContext) {
JpaRepositoryStateMachine jpaRepositoryStateMachine = new JpaRepositoryStateMachine();
jpaRepositoryStateMachine.setMachineId(context.getId());
jpaRepositoryStateMachine.setState(context.getState().toString());
jpaRepositoryStateMachine.setState(context.getState() != null ? context.getState().toString() : null);
jpaRepositoryStateMachine.setStateMachineContext(serialisedContext);
return jpaRepositoryStateMachine;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 the original author or authors.
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,15 @@
package org.springframework.statemachine.data.jpa;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -314,6 +317,30 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
}
@Test
@SuppressWarnings("unchecked")
public void testStateMachinePersistWithRootRegions() {
context.register(TestConfig.class, ConfigWithRootRegions.class);
context.refresh();
JpaStateMachineRepository stateMachineRepository = context.getBean(JpaStateMachineRepository.class);
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
stateMachine.start();
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S10", "S20"));
stateMachine.sendEvent("E1");
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S11", "S21"));
assertThat(stateMachineRepository.count(), is(3l));
List<String> ids = StreamSupport.stream(stateMachineRepository.findAll().spliterator(), false)
.map(jrsm -> jrsm.getMachineId()).collect(Collectors.toList());
assertThat(ids.size(), is(3));
// [null#238e8cc0-a932-4583-b696-2c057e5ebefe, null#486e20be-853e-4e4d-9a68-c62c061469ef, testid]
assertThat(ids, containsInAnyOrder("testid", "testid#R1", "testid#R2"));
}
@EnableAutoConfiguration
static class TestConfig {
}
@@ -442,4 +469,57 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
public enum PersistTestEvents {
E1, E2;
}
@Configuration
@EnableStateMachine
static class ConfigWithRootRegions extends StateMachineConfigurerAdapter<String, String> {
@Autowired
private JpaStateMachineRepository jpaStateMachineRepository;
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.machineId("testid")
.and()
.withPersistence()
.runtimePersister(stateMachineRuntimePersister());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.region("R1")
.initial("S10")
.state("S10")
.state("S11")
.and()
.withStates()
.region("R2")
.initial("S20")
.state("S20")
.state("S21");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S10")
.target("S11")
.event("E1")
.and()
.withExternal()
.source("S20")
.target("S21")
.event("E1");
}
@Bean
public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister() {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2018 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,16 @@
*/
package org.springframework.statemachine.data;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.kryo.KryoStateMachineSerialisationService;
import org.springframework.statemachine.service.StateMachineSerialisationService;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.util.Assert;
/**
@@ -66,8 +70,27 @@ public abstract class RepositoryStateMachinePersist<M extends RepositoryStateMac
@Override
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
M repositoryStateMachine = getRepository().findById(contextObj.toString()).orElse(null);
// use child contexts if we have those, otherwise fall back to child context refs.
if (repositoryStateMachine != null) {
return serialisationService.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext());
StateMachineContext<S, E> context = serialisationService
.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext());;
if (context != null && context.getChilds() != null && context.getChilds().isEmpty()
&& context.getChildReferences() != null) {
List<StateMachineContext<S, E>> contexts = new ArrayList<>();
for (String childRef : context.getChildReferences()) {
repositoryStateMachine = getRepository().findById(childRef).orElse(null);
if (repositoryStateMachine != null) {
contexts.add(serialisationService
.deserialiseStateMachineContext(repositoryStateMachine.getStateMachineContext()));
}
}
return new DefaultStateMachineContext<S, E>(contexts, context.getState(), context.getEvent(),
context.getEventHeaders(), context.getExtendedState(), context.getHistoryStates(),
context.getId());
} else {
return context;
}
}
return null;
}