Preliminary support for Spring Data persist

- Add new repository model for storing StateMachineContext
  via a new StateMachineRepository.
- New StateMachineRuntimePersister interface to abstract needed
  functionality to do a runtime machine persistence.
- As runtime persistence, as of now, is done via interceptors, define
  JpaRepositoryStateMachinePersist and JpaPersistingStateMachineInterceptor
  to define StateMachineRuntimePersister logic.
- Add new datajpapersist sample demonstrating new concepts.
- Keep tests related to jpa as there's not redis/mongo integration
  implemented in this first iteration.
- As this is going to be WIP until features around this issues
  are completed, docs, etc are not yet added. Also, interfaces and impls
  are subject to change during a process.
- Relates to #423
- Relates to #426
- Relates to #427
This commit is contained in:
Janne Valkealahti
2017-11-18 16:38:49 +00:00
parent d82bca1280
commit 33a5370d01
29 changed files with 1442 additions and 13 deletions

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data.jpa;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.persist.AbstractPersistingStateMachineInterceptor;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
import org.springframework.statemachine.support.StateMachineInterceptor;
import org.springframework.util.Assert;
/**
* {@code JPA} implementation of a {@link AbstractPersistingStateMachineInterceptor}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class JpaPersistingStateMachineInterceptor<S, E> extends AbstractPersistingStateMachineInterceptor<S, E>
implements StateMachinePersist<S, E, Object>, StateMachineRuntimePersister<S, E> {
private final JpaRepositoryStateMachinePersist<S, E> persist;
/**
* Instantiates a new jpa persisting state machine interceptor.
*
* @param jpaStateMachineRepository the jpa state machine repository
*/
public JpaPersistingStateMachineInterceptor(JpaStateMachineRepository jpaStateMachineRepository) {
Assert.notNull(jpaStateMachineRepository, "'jpaStateMachineRepository' must be set");
this.persist = new JpaRepositoryStateMachinePersist<S, E>(jpaStateMachineRepository);
}
/**
* Instantiates a new jpa persisting state machine interceptor.
*
* @param persist the persist
*/
public JpaPersistingStateMachineInterceptor(JpaRepositoryStateMachinePersist<S, E> persist) {
Assert.notNull(persist, "'persist' must be set");
this.persist = persist;
}
@Override
public StateMachineInterceptor<S, E> getInterceptor() {
return this;
}
@Override
public void write(StateMachineContext<S, E> context, Object contextObj) throws Exception {
persist.write(context, contextObj);
}
@Override
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
return persist.read(contextObj);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data.jpa;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import org.springframework.statemachine.data.RepositoryStateMachine;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
/**
* A {@link RepositoryStateMachine} interface for JPA used for states machines.
*
* @author Janne Valkealahti
*
*/
@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
public class JpaRepositoryStateMachine extends RepositoryStateMachine {
@Id
private String machineId;
private String state;
@Lob
private byte[] stateMachineContext;
@Override
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
@Override
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
@Override
public byte[] getStateMachineContext() {
return stateMachineContext;
}
public void setStateMachineContext(byte[] stateMachineContext) {
this.stateMachineContext = stateMachineContext;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data.jpa;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.data.RepositoryStateMachinePersist;
import org.springframework.statemachine.data.StateMachineRepository;
/**
* {@code JPA} based implementation of a {@link RepositoryStateMachinePersist}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class JpaRepositoryStateMachinePersist<S, E> extends RepositoryStateMachinePersist<JpaRepositoryStateMachine, S, E> {
private final JpaStateMachineRepository jpaStateMachineRepository;
/**
* Instantiates a new jpa repository state machine persist.
*
* @param jpaStateMachineRepository the jpa state machine repository
*/
public JpaRepositoryStateMachinePersist(JpaStateMachineRepository jpaStateMachineRepository) {
super();
this.jpaStateMachineRepository = jpaStateMachineRepository;
}
@Override
protected StateMachineRepository<JpaRepositoryStateMachine> getRepository() {
return jpaStateMachineRepository;
}
@Override
protected JpaRepositoryStateMachine build(StateMachineContext<S, E> context, byte[] serialisedContext) {
JpaRepositoryStateMachine jpaRepositoryStateMachine = new JpaRepositoryStateMachine();
jpaRepositoryStateMachine.setMachineId(context.getId());
jpaRepositoryStateMachine.setState(context.getState().toString());
jpaRepositoryStateMachine.setStateMachineContext(serialisedContext);
return jpaRepositoryStateMachine;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data.jpa;
import org.springframework.statemachine.data.StateMachineRepository;
/**
* JPA entity for state machine.
* A {@link StateMachineRepository} interface for JPA used for states machines.
*
* @author Janne Valkealahti
*
*/
public interface JpaStateMachineRepository extends StateMachineRepository<JpaRepositoryStateMachine>{
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -27,12 +27,21 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.data.AbstractRepositoryTests;
import org.springframework.statemachine.data.RepositoryState;
import org.springframework.statemachine.data.RepositoryTransition;
import org.springframework.statemachine.data.StateMachineRepository;
import org.springframework.statemachine.data.StateRepository;
import org.springframework.statemachine.data.TransitionRepository;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
import org.springframework.statemachine.transition.TransitionKind;
/**
@@ -253,6 +262,36 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
context.refresh();
}
@Test
@SuppressWarnings("unchecked")
public void testStateMachinePersistWithStrings() {
context.register(TestConfig.class, ConfigWithStrings.class);
context.refresh();
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
stateMachine.start();
assertThat(stateMachine.getState().getId(), is("S1"));
stateMachine.sendEvent("E1");
assertThat(stateMachine.getState().getId(), is("S2"));
stateMachine.sendEvent("E2");
assertThat(stateMachine.getState().getId(), is("S1"));
}
@Test
@SuppressWarnings("unchecked")
public void testStateMachinePersistWithEnums() {
context.register(TestConfig.class, ConfigWithEnums.class);
context.refresh();
StateMachine<PersistTestStates, PersistTestEvents> stateMachine = context.getBean(StateMachine.class);
stateMachine.start();
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
stateMachine.sendEvent(PersistTestEvents.E1);
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S2));
stateMachine.sendEvent(PersistTestEvents.E2);
assertThat(stateMachine.getState().getId(), is(PersistTestStates.S1));
}
@EnableAutoConfiguration
static class TestConfig {
}
@@ -275,6 +314,110 @@ public class JpaRepositoryTests extends AbstractRepositoryTests {
@Autowired
StateRepository<? extends RepositoryState> statesRepository4;
@Autowired
JpaStateMachineRepository jpaStateMachineRepository1;
@Autowired
StateMachineRepository<JpaRepositoryStateMachine> jpaStateMachineRepository2;
}
@Configuration
@EnableStateMachine
public static class ConfigWithStrings extends StateMachineConfigurerAdapter<String, String> {
@Autowired
private JpaStateMachineRepository jpaStateMachineRepository;
@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.machineId("xxx1")
.and()
.withPersistence()
.runtimePersister(stateMachineRuntimePersister());
}
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S2");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1")
.target("S2")
.event("E1")
.and()
.withExternal()
.source("S2")
.target("S1")
.event("E2");
}
@Bean
public StateMachineRuntimePersister<String, String> stateMachineRuntimePersister() {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
@Configuration
@EnableStateMachine
public static class ConfigWithEnums extends StateMachineConfigurerAdapter<PersistTestStates, PersistTestEvents> {
@Autowired
private JpaStateMachineRepository jpaStateMachineRepository;
@Override
public void configure(StateMachineConfigurationConfigurer<PersistTestStates, PersistTestEvents> config) throws Exception {
config
.withConfiguration()
.machineId("xxx2")
.and()
.withPersistence()
.runtimePersister(stateMachineRuntimePersister());
}
@Override
public void configure(StateMachineStateConfigurer<PersistTestStates, PersistTestEvents> states) throws Exception {
states
.withStates()
.initial(PersistTestStates.S1)
.state(PersistTestStates.S2);
}
@Override
public void configure(StateMachineTransitionConfigurer<PersistTestStates, PersistTestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(PersistTestStates.S1)
.target(PersistTestStates.S2)
.event(PersistTestEvents.E1)
.and()
.withExternal()
.source(PersistTestStates.S2)
.target(PersistTestStates.S1)
.event(PersistTestEvents.E2);
}
@Bean
public StateMachineRuntimePersister<PersistTestStates, PersistTestEvents> stateMachineRuntimePersister() {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}
public enum PersistTestStates {
S1, S2;
}
public enum PersistTestEvents {
E1, E2;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data;
/**
* Generic base class representing state machine entity.
*
* @author Janne Valkealahti
*
*/
public abstract class RepositoryStateMachine extends BaseRepositoryEntity {
/**
* Gets the machine id.
*
* @return the machine id
*/
public abstract String getMachineId();
/**
* Gets the state.
*
* @return the state
*/
public abstract String getState();
/**
* Gets the state machine context.
*
* @return the state machine context
*/
public abstract byte[] getStateMachineContext();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -30,7 +30,6 @@ import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.SpelExpressionAction;
import org.springframework.statemachine.config.model.AbstractStateMachineModelFactory;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
@@ -80,8 +79,6 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode
@Override
public StateMachineModel<String, String> build(String machineId) {
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
Collection<StateData<String, String>> stateDatas = new ArrayList<>();
for (RepositoryState s : stateRepository.findByMachineId(machineId == null ? "" : machineId)) {
@@ -284,8 +281,7 @@ public class RepositoryStateMachineModelFactory extends AbstractStateMachineMode
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData, choicesCopy, junctionsCopy, forks, joins,
entrys, exits, historys);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
return stateMachineModel;
return new DefaultStateMachineModel<>(null, statesData, transitionsData);
}
private Guard<String, String> resolveGuard(RepositoryTransition t) {

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data;
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.KryoSerialisationService;
/**
* Base implementation of a {@link StateMachinePersist} using Spring Data Repositories.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
* @param <M> the type of entity
*/
public abstract class RepositoryStateMachinePersist<M extends RepositoryStateMachine, S, E> implements StateMachinePersist<S, E, Object> {
private final Log log = LogFactory.getLog(RepositoryStateMachinePersist.class);
private final KryoSerialisationService serialisationService = new KryoSerialisationService();
@Override
public void write(StateMachineContext<S, E> context, Object contextObj) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Persisting context " + context + " using contextObj " + contextObj);
}
M build = build(context, serialisationService.serializeStateMachineContext(context));
getRepository().save(build);
}
@Override
public StateMachineContext<S, E> read(Object contextObj) throws Exception {
M repositoryStateMachine = getRepository().findOne(contextObj.toString());
if (repositoryStateMachine != null) {
return serialisationService.deserializeStateMachineContext(repositoryStateMachine.getStateMachineContext());
}
return null;
}
/**
* Gets the repository.
*
* @return the repository
*/
protected abstract StateMachineRepository<M> getRepository();
/**
* Builds the generic {@link RepositoryStateMachine} entity.
*
* @param context the context
* @param serialisedContext the serialised context
* @return the repository state machine entity
*/
protected abstract M build(StateMachineContext<S, E> context, byte[] serialisedContext);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.data;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
/**
* Generic {@link Repository} interface for states machines.
*
* @author Janne Valkealahti
*
*/
@NoRepositoryBean
public interface StateMachineRepository<M extends RepositoryStateMachine> extends CrudRepository<M, String> {
}