diff --git a/build.gradle b/build.gradle index 3f70d3b20..2f6406ea0 100644 --- a/build.gradle +++ b/build.gradle @@ -313,6 +313,7 @@ project('spring-statemachine-build-tests') { dependencies { testCompile project(":spring-statemachine-uml") testCompile project(":spring-statemachine-test") + testCompile project(":spring-statemachine-redis") testCompile project(":spring-statemachine-data-common:spring-statemachine-data-jpa") testCompile project(":spring-statemachine-data-common:spring-statemachine-data-redis") testCompile project(":spring-statemachine-data-common:spring-statemachine-data-mongodb") diff --git a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/AbstractBuildTests.java b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/AbstractBuildTests.java index 60d388f6e..f50047637 100644 --- a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/AbstractBuildTests.java +++ b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/AbstractBuildTests.java @@ -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. @@ -36,6 +36,18 @@ public void clean() { context = null; } + protected enum TestStates { + SI,S1,S2,S3,S4,SF,SH, + S10,S11,S101,S111,S112,S12,S121,S122,S13, + S20,S21,S201,S211,S212, + S1011,S1012,S2011,S2012, + S30,S31,S32,S33 + } + + protected enum TestEvents { + E1,E2,E3,E4,EF,EH + } + /** * Builds the context. * diff --git a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java new file mode 100644 index 000000000..0e7f5f1cc --- /dev/null +++ b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/RedisPersistTests.java @@ -0,0 +1,192 @@ +/* + * 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.buildtests; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachinePersist; +import org.springframework.statemachine.buildtests.tck.redis.RedisRule; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.persist.RepositoryStateMachinePersist; +import org.springframework.statemachine.persist.StateMachinePersister; +import org.springframework.statemachine.redis.RedisStateMachineContextRepository; +import org.springframework.statemachine.redis.RedisStateMachinePersister; + +public class RedisPersistTests extends AbstractBuildTests { + + @Rule + public RedisRule redisAvailableRule = new RedisRule(); + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Test + @SuppressWarnings("unchecked") + public void testPersistRegions() throws Exception { + context.register(RedisConfig.class, Config1.class); + context.refresh(); + + StateMachineFactory stateMachineFactory = context.getBean(StateMachineFactory.class); + StateMachinePersister persister = context.getBean(StateMachinePersister.class); + + StateMachine stateMachine = stateMachineFactory.getStateMachine("testid"); + stateMachine.start(); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getId(), is("testid")); + + stateMachine.sendEvent(TestEvents.E1); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S20, TestStates.S30)); + persister.persist(stateMachine, "xxx1"); + + stateMachine.sendEvent(TestEvents.E2); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S30)); + persister.persist(stateMachine, "xxx2"); + + stateMachine.sendEvent(TestEvents.E3); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S4)); + persister.persist(stateMachine, "xxx3"); + + stateMachine = stateMachineFactory.getStateMachine(); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getId(), nullValue()); + stateMachine = persister.restore(stateMachine, "xxx1"); + assertThat(stateMachine.getId(), is("testid")); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S20, TestStates.S30)); + stateMachine.sendEvent(TestEvents.E2); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S30)); + + stateMachine = stateMachineFactory.getStateMachine(); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getId(), nullValue()); + stateMachine = persister.restore(stateMachine, "xxx2"); + assertThat(stateMachine.getId(), is("testid")); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S30)); + stateMachine.sendEvent(TestEvents.E3); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S4)); + + stateMachine = stateMachineFactory.getStateMachine(); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.getId(), nullValue()); + stateMachine = persister.restore(stateMachine, "xxx3"); + assertThat(stateMachine.getId(), is("testid")); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(TestStates.S4)); + } + + @Configuration + static class RedisConfig { + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new JedisConnectionFactory(); + } + + @Bean + public StateMachinePersist stateMachinePersist(RedisConnectionFactory connectionFactory) { + RedisStateMachineContextRepository repository = + new RedisStateMachineContextRepository(connectionFactory); + return new RepositoryStateMachinePersist(repository); + } + + @Bean + public StateMachinePersister stateMachinePersister( + StateMachinePersist stateMachinePersist) { + return new RedisStateMachinePersister(stateMachinePersist); + } + } + + @Configuration + @EnableStateMachineFactory + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .state(TestStates.SI) + .fork(TestStates.S1) + .state(TestStates.S2) + .end(TestStates.SF) + .join(TestStates.S3) + .state(TestStates.S4) + .and() + .withStates() + .parent(TestStates.S2) + .initial(TestStates.S20) + .state(TestStates.S20) + .state(TestStates.S21) + .and() + .withStates() + .parent(TestStates.S2) + .initial(TestStates.S30) + .state(TestStates.S30) + .state(TestStates.S31); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S20) + .target(TestStates.S21) + .event(TestEvents.E2) + .and() + .withExternal() + .source(TestStates.S30) + .target(TestStates.S31) + .event(TestEvents.E3) + .and() + .withFork() + .source(TestStates.S1) + .target(TestStates.S20) + .target(TestStates.S30) + .and() + .withJoin() + .source(TestStates.S21) + .source(TestStates.S31) + .target(TestStates.S3) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4); + } + + } + +} diff --git a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java index 305cb1f00..5c7a7d8fa 100644 --- a/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java +++ b/spring-statemachine-kryo/src/main/java/org/springframework/statemachine/kryo/StateMachineContextSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -42,8 +42,10 @@ public void write(Kryo kryo, Output output, StateMachineContext context) { kryo.writeClassAndObject(output, context.getEvent()); kryo.writeClassAndObject(output, context.getState()); kryo.writeClassAndObject(output, context.getEventHeaders()); - kryo.writeClassAndObject(output, context.getExtendedState().getVariables()); + kryo.writeClassAndObject(output, context.getExtendedState() != null ? context.getExtendedState().getVariables() : null); kryo.writeClassAndObject(output, context.getChilds()); + kryo.writeClassAndObject(output, context.getHistoryStates()); + kryo.writeClassAndObject(output, context.getId()); } @SuppressWarnings("unchecked") @@ -54,7 +56,9 @@ public StateMachineContext read(Kryo kryo, Input input, Class eventHeaders = (Map) kryo.readClassAndObject(input); Map variables = (Map) kryo.readClassAndObject(input); List> childs = (List>) kryo.readClassAndObject(input); - return new DefaultStateMachineContext(childs, state, event, eventHeaders, new DefaultExtendedState(variables)); + Map historyStates = (Map) kryo.readClassAndObject(input); + String id = (String) kryo.readClassAndObject(input); + return new DefaultStateMachineContext(childs, state, event, eventHeaders, new DefaultExtendedState(variables), historyStates, id); } }