diff --git a/build.gradle b/build.gradle index b19cb8c0..ab82d93f 100644 --- a/build.gradle +++ b/build.gradle @@ -226,7 +226,8 @@ configure(sampleProjects()) { configurations.archives.artifacts.removeAll { it.archiveTask.is jar } tasks.findByPath("artifactoryPublish")?.enabled = false dependencies { - compile project(":spring-statemachine-samples-common") +// compile project(":spring-statemachine-samples-common") + compile project(":spring-statemachine-core") compile "org.springframework:spring-context-support:$springVersion" testCompile "org.springframework:spring-test:$springVersion" testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" diff --git a/settings.gradle b/settings.gradle index a426ead2..255c0750 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,6 +19,7 @@ include 'spring-statemachine-samples:persist' include 'spring-statemachine-samples:web' include 'spring-statemachine-samples:scope' include 'spring-statemachine-samples:security' +include 'spring-statemachine-samples:eventservice' rootProject.children.find { if (it.name == 'spring-statemachine-recipes') { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ObservableMap.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ObservableMap.java index 442a0035..1ae1c7e0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ObservableMap.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ObservableMap.java @@ -132,6 +132,11 @@ public class ObservableMap implements Map { return delegate.entrySet(); } + @Override + public String toString() { + return delegate.toString(); + } + /** * Gets the delegating map instance. * diff --git a/spring-statemachine-samples/build.gradle b/spring-statemachine-samples/build.gradle index 78bfc158..193bd28a 100644 --- a/spring-statemachine-samples/build.gradle +++ b/spring-statemachine-samples/build.gradle @@ -2,27 +2,43 @@ description = 'Spring State Machine Samples Common' project('spring-statemachine-samples-turnstile') { description = 'Spring State Machine Turnstile Sample' + dependencies { + compile project(":spring-statemachine-samples-common") + } } project('spring-statemachine-samples-showcase') { description = 'Spring State Machine Showcase Sample' + dependencies { + compile project(":spring-statemachine-samples-common") + } } project('spring-statemachine-samples-cdplayer') { description = 'Spring State Machine CD Player Sample' + dependencies { + compile project(":spring-statemachine-samples-common") + } } project('spring-statemachine-samples-tasks') { description = 'Spring State Machine Parallel Regions Sample' + dependencies { + compile project(":spring-statemachine-samples-common") + } } project('spring-statemachine-samples-washer') { description = 'Spring State Machine History State Sample' + dependencies { + compile project(":spring-statemachine-samples-common") + } } project('spring-statemachine-samples-zookeeper') { description = 'Spring State Machine Distributed Sample' dependencies { + compile project(":spring-statemachine-samples-common") compile project(":spring-statemachine-zookeeper") } } @@ -30,6 +46,7 @@ project('spring-statemachine-samples-zookeeper') { project('spring-statemachine-samples-persist') { description = 'Spring State Machine Persist Sample' dependencies { + compile project(":spring-statemachine-samples-common") compile project(":spring-statemachine-recipes-common") compile ("org.hsqldb:hsqldb:2.3.1") compile ("org.springframework:spring-jdbc:$springVersion") @@ -66,3 +83,13 @@ project('spring-statemachine-samples-security') { compile("org.springframework.security:spring-security-web:$springSecurityVersion") } } + +project('spring-statemachine-samples-eventservice') { + description = 'Spring State Machine Event Service Sample' + dependencies { + compile project(":spring-statemachine-redis") + compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion") +// compile("org.springframework.boot:spring-boot-starter-redis:$springBootVersion") + compile("org.apache.commons:commons-pool2:2.4.2") + } +} diff --git a/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Application.java b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Application.java new file mode 100644 index 00000000..e68e4396 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Application.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 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 demo.eventservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Pageview.java b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Pageview.java new file mode 100644 index 00000000..68d4b0f7 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/Pageview.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 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 demo.eventservice; + +import demo.eventservice.StateMachineConfig.Events; + +public class Pageview { + + private String user; + private Events id; + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public Events getId() { + return id; + } + + public void setId(Events id) { + this.id = id; + } + + @Override + public String toString() { + return "Pageview [user=" + user + ", id=" + id + "]"; + } + +} diff --git a/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineConfig.java b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineConfig.java new file mode 100644 index 00000000..608602d8 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineConfig.java @@ -0,0 +1,235 @@ +/* + * Copyright 2016 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 demo.eventservice; + +import java.io.IOException; +import java.io.InputStream; +import java.util.EnumSet; +import java.util.Scanner; + +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.aop.target.CommonsPool2TargetSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachinePersist; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.StateMachineBuilder; +import org.springframework.statemachine.config.StateMachineBuilder.Builder; +import org.springframework.statemachine.redis.RedisStateMachineContextRepository; +import org.springframework.statemachine.support.RepositoryStateMachinePersist; + +@Configuration +public class StateMachineConfig { + + @Bean + @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) + public ProxyFactoryBean stateMachine() { + ProxyFactoryBean pfb = new ProxyFactoryBean(); + pfb.setTargetSource(poolTargetSource()); + return pfb; + } + + @Bean + public CommonsPool2TargetSource poolTargetSource() { + CommonsPool2TargetSource pool = new CommonsPool2TargetSource(); + pool.setMaxSize(3); + pool.setTargetBeanName("stateMachineTarget"); + return pool; + } + + @Bean(name = "stateMachineTarget") + @Scope(scopeName="prototype") + public StateMachine stateMachineTarget() throws Exception { + Builder builder = StateMachineBuilder.builder(); + + builder.configureConfiguration() + .withConfiguration() + .autoStartup(true); + + builder.configureStates() + .withStates() + .initial(States.HOME) + .states(EnumSet.allOf(States.class)); + + builder.configureTransitions() + .withInternal() + .source(States.ITEMS).event(Events.ADD) + .action(addAction()) + .and() + .withInternal() + .source(States.CART).event(Events.DEL) + .action(delAction()) + .and() + .withInternal() + .source(States.PAYMENT).event(Events.PAY) + .action(payAction()) + .and() + .withExternal() + .source(States.HOME).target(States.ITEMS) + .action(pageviewAction()) + .event(Events.VIEW_I) + .and() + .withExternal() + .source(States.CART).target(States.ITEMS) + .action(pageviewAction()) + .event(Events.VIEW_I) + .and() + .withExternal() + .source(States.ITEMS).target(States.CART) + .action(pageviewAction()) + .event(Events.VIEW_C) + .and() + .withExternal() + .source(States.PAYMENT).target(States.CART) + .action(pageviewAction()) + .event(Events.VIEW_C) + .and() + .withExternal() + .source(States.CART).target(States.PAYMENT) + .action(pageviewAction()) + .event(Events.VIEW_P) + .and() + .withExternal() + .source(States.ITEMS).target(States.HOME) + .action(resetAction()) + .event(Events.RESET) + .and() + .withExternal() + .source(States.CART).target(States.HOME) + .action(resetAction()) + .event(Events.RESET) + .and() + .withExternal() + .source(States.PAYMENT).target(States.HOME) + .action(resetAction()) + .event(Events.RESET); + + return builder.build(); + } + + @Bean + public Action pageviewAction() { + return new Action() { + + @Override + public void execute(StateContext context) { + String variable = context.getTarget().getId().toString(); + Integer count = context.getExtendedState().get(variable, Integer.class); + if (count == null) { + context.getExtendedState().getVariables().put(variable, 1); + } else { + context.getExtendedState().getVariables().put(variable, (count + 1)); + } + } + }; + } + + @Bean + public Action addAction() { + return new Action() { + + @Override + public void execute(StateContext context) { + Integer count = context.getExtendedState().get("COUNT", Integer.class); + if (count == null) { + context.getExtendedState().getVariables().put("COUNT", 1); + } else { + context.getExtendedState().getVariables().put("COUNT", (count + 1)); + } + } + }; + } + + @Bean + public Action delAction() { + return new Action() { + + @Override + public void execute(StateContext context) { + Integer count = context.getExtendedState().get("COUNT", Integer.class); + if (count != null && count > 0) { + context.getExtendedState().getVariables().put("COUNT", (count - 1)); + } + } + }; + } + + @Bean + public Action payAction() { + return new Action() { + + @Override + public void execute(StateContext context) { + context.getExtendedState().getVariables().put("PAYED", true); + } + }; + } + + @Bean + public Action resetAction() { + return new Action() { + + @Override + public void execute(StateContext context) { + context.getExtendedState().getVariables().clear(); + } + }; + } + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new JedisConnectionFactory(); + } + + @Bean + public StateMachinePersist stateMachinePersist(RedisConnectionFactory connectionFactory) { + return new RepositoryStateMachinePersist(new RedisStateMachineContextRepository(connectionFactory)); + } + + @Bean + public String stateChartModel() throws IOException { + ClassPathResource model = new ClassPathResource("statechartmodel.txt"); + InputStream inputStream = model.getInputStream(); + Scanner scanner = new Scanner(inputStream); + String content = scanner.useDelimiter("\\Z").next(); + scanner.close(); + return content; + } + + public enum States { + HOME, + ITEMS, + CART, + PAYMENT + } + + public enum Events { + VIEW_I, + VIEW_C, + VIEW_P, + RESET, + ADD, + DEL, + PAY + } +} diff --git a/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineController.java b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineController.java new file mode 100644 index 00000000..50a32782 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/java/demo/eventservice/StateMachineController.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 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 demo.eventservice; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachineContext; +import org.springframework.statemachine.StateMachinePersist; +import org.springframework.statemachine.access.StateMachineAccess; +import org.springframework.statemachine.access.StateMachineFunction; +import org.springframework.statemachine.support.DefaultStateMachineContext; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; + +import demo.eventservice.StateMachineConfig.Events; +import demo.eventservice.StateMachineConfig.States; + +@Controller +public class StateMachineController { + + @Autowired + private StateMachine stateMachine; + + @Autowired + private StateMachinePersist stateMachinePersist; + + @Autowired + private String stateChartModel; + + @RequestMapping("/") + public String home() { + return "redirect:/state"; + } + + @RequestMapping("/state") + public String feedAndGetState(@RequestParam(value = "user", required = false) String user, + @RequestParam(value = "id", required = false) Events id, Model model) throws Exception { + model.addAttribute("user", user); + model.addAttribute("allTypes", Events.values()); + model.addAttribute("stateChartModel", stateChartModel); + // we may get into this page without a user so + // do nothing with a state machine + if (StringUtils.hasText(user)) { + resetStateMachineFromStore(user); + if (id != null) { + feedMachine(user, id); + } + model.addAttribute("states", stateMachine.getState().getIds()); + model.addAttribute("extendedState", stateMachine.getExtendedState().getVariables()); + } + return "states"; + } + + @RequestMapping(value = "/feed",method= RequestMethod.POST) + @ResponseStatus(HttpStatus.OK) + public void feedPageview(@RequestBody(required = true) Pageview event) throws Exception { + Assert.notNull(event.getUser(), "User must be set"); + Assert.notNull(event.getId(), "Id must be set"); + resetStateMachineFromStore(event.getUser()); + feedMachine(event.getUser(), event.getId()); + } + + private void feedMachine(String user, Events id) throws Exception { + stateMachine.sendEvent(id); + stateMachinePersist.write(new DefaultStateMachineContext(stateMachine.getState().getId(), null, null, + stateMachine.getExtendedState()), "testprefix:" + user); + } + + private StateMachine resetStateMachineFromStore(String user) throws Exception { + final StateMachineContext context = stateMachinePersist.read("testprefix:" + user); + stateMachine.stop(); + stateMachine.getStateMachineAccessor() + .doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.resetStateMachine(context); + } + }); + stateMachine.start(); + return stateMachine; + } +} diff --git a/spring-statemachine-samples/eventservice/src/main/resources/application.yml b/spring-statemachine-samples/eventservice/src/main/resources/application.yml new file mode 100644 index 00000000..5aa882d0 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/resources/application.yml @@ -0,0 +1,3 @@ +security: + basic: + enabled: false diff --git a/spring-statemachine-samples/eventservice/src/main/resources/logback.xml b/spring-statemachine-samples/eventservice/src/main/resources/logback.xml new file mode 100644 index 00000000..39d50aa5 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/resources/logback.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-statemachine-samples/eventservice/src/main/resources/statechartmodel.txt b/spring-statemachine-samples/eventservice/src/main/resources/statechartmodel.txt new file mode 100644 index 00000000..bb5759cc --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/resources/statechartmodel.txt @@ -0,0 +1,20 @@ ++---------------------------------------------------------------------------------------------------------------------+ +| SM | ++---------------------------------------------------------------------------------------------------------------------+ +| | +| +------------------+ VIEW_I +------------------+ VIEW_C +------------------+ VIEW_P +------------------+ | +| *-->| HOME |--------->| ITEMS |--------->| CART |--------->| PAYMENT | | +| | | | | | | | | | +| | | | ADD | | DEL | | PAY | | +| | | | +------------+ | | +------------+ | | +------------+ | | +| | | RESET | | | | VIEW_I | | | | VIEW_C | | | | | +| | |<---------| | v |<---------| | v |<---------| | v | | +| +------------------+ +------------------+ +------------------+ +------------------+ | +| ^ ^ ^ | | | | +| | | RESET | | | | | +| | +----------------------------------------------------+ | | | +| | | VIEW_I | | | +| | RESET +---------------------------------------------------+ | | +| +--------------------------------------------------------------------------------------------------+ | +| | ++---------------------------------------------------------------------------------------------------------------------+ diff --git a/spring-statemachine-samples/eventservice/src/main/resources/templates/states.html b/spring-statemachine-samples/eventservice/src/main/resources/templates/states.html new file mode 100644 index 00000000..f61e4e91 --- /dev/null +++ b/spring-statemachine-samples/eventservice/src/main/resources/templates/states.html @@ -0,0 +1,28 @@ + + + + Spring Statemachine Event Service Demo + + + +
+

+

+

+

+ +
+
    +
  • + + +
  • +
+ + + +
+ +
+
+
diff --git a/spring-statemachine-samples/eventservice/src/test/java/demo/eventservice/EventServiceTests.java b/spring-statemachine-samples/eventservice/src/test/java/demo/eventservice/EventServiceTests.java
new file mode 100644
index 00000000..2cf3da58
--- /dev/null
+++ b/spring-statemachine-samples/eventservice/src/test/java/demo/eventservice/EventServiceTests.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 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 demo.eventservice;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.statemachine.StateMachineContext;
+import org.springframework.statemachine.StateMachinePersist;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import demo.eventservice.EventServiceTests.Config;
+import demo.eventservice.StateMachineConfig.Events;
+import demo.eventservice.StateMachineConfig.States;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = { Application.class, Config.class })
+@WebAppConfiguration
+@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+public class EventServiceTests {
+
+	private MockMvc mvc;
+
+	@Autowired
+	private WebApplicationContext context;
+
+	@Test
+	public void testHome() throws Exception {
+		mvc.
+			perform(get("/state")).
+			andExpect(status().isOk());
+	}
+
+	@Test
+	public void testSendEvent() throws Exception {
+		mvc.
+			perform(get("/state").param("user", "joe").param("id", "VIEW_I")).
+			andExpect(status().isOk()).
+			andExpect(content().string(containsString("States: [ITEMS]")));
+	}
+
+	@Test
+	public void testAdd() throws Exception {
+		mvc.
+			perform(get("/state").param("user", "joe").param("id", "VIEW_I")).
+			andExpect(status().isOk()).
+			andExpect(content().string(containsString("States: [ITEMS]")));
+		mvc.
+			perform(get("/state").param("user", "joe").param("id", "ADD")).
+			andExpect(status().isOk()).
+			andExpect(content().string(containsString("States: [ITEMS]"))).
+			andExpect(content().string(containsString("Extended State: {COUNT=1, ITEMS=1}")));
+	}
+
+	@Test
+	public void testFeed() throws Exception {
+		mvc.
+			perform(post("/feed").content("{\"user\":\"joe\",\"id\":\"VIEW_I\"}").contentType(MediaType.APPLICATION_JSON)).
+			andExpect(status().isOk());
+		mvc.
+			perform(post("/feed").content("{\"user\":\"joe\",\"id\":\"ADD\"}").contentType(MediaType.APPLICATION_JSON)).
+			andExpect(status().isOk());
+		mvc.
+			perform(get("/state").param("user", "joe")).
+			andExpect(status().isOk()).
+			andExpect(content().string(containsString("States: [ITEMS]"))).
+			andExpect(content().string(containsString("Extended State: {COUNT=1, ITEMS=1}")));
+	}
+
+	@Before
+	public void setup() throws Exception {
+		mvc = MockMvcBuilders.webAppContextSetup(context).build();
+	}
+
+	@Configuration
+	static class Config {
+
+		@Bean
+		public StateMachinePersist stateMachinePersist() {
+			// use stateMachinePersist without redis to ease testing
+			return new InMemoryStateMachinePersist();
+		}
+	}
+
+	static class InMemoryStateMachinePersist implements StateMachinePersist {
+
+		private final HashMap> contexts = new HashMap<>();
+
+		@Override
+		public void write(StateMachineContext context, String contextOjb) throws Exception {
+			contexts.put(contextOjb, context);
+		}
+
+		@Override
+		public StateMachineContext read(String contextOjb) throws Exception {
+			return contexts.get(contextOjb);
+		}
+	}
+}