From 3d538f7600a37012d2eb3bade446d6992a90d163 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 21 Nov 2017 10:46:49 +0000 Subject: [PATCH] Add base DefaultStateMachineService implementation - First take on new interface StateMachineService with its default impl. - Modify datajpapersist sample to use it. - Relates to #432 --- .../service/DefaultStateMachineService.java | 120 ++++++++++++++++++ .../service/StateMachineService.java | 45 +++++++ .../datajpapersist/StateMachineConfig.java | 16 ++- .../StateMachineController.java | 55 +++----- 4 files changed, 195 insertions(+), 41 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/service/StateMachineService.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java new file mode 100644 index 00000000..e2196f30 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java @@ -0,0 +1,120 @@ +/* + * 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.service; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.StateMachineContext; +import org.springframework.statemachine.StateMachineException; +import org.springframework.statemachine.StateMachinePersist; +import org.springframework.statemachine.access.StateMachineAccess; +import org.springframework.statemachine.access.StateMachineFunction; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.util.Assert; + +/** + * Default implementation of a {@link StateMachineService}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultStateMachineService implements StateMachineService { + + private final StateMachineFactory stateMachineFactory; + private final Map> machines = new HashMap>(); + private StateMachinePersist stateMachinePersist; + + /** + * Instantiates a new default state machine service. + * + * @param stateMachineFactory the state machine factory + */ + public DefaultStateMachineService(StateMachineFactory stateMachineFactory) { + this(stateMachineFactory, null); + } + + /** + * Instantiates a new default state machine service. + * + * @param stateMachineFactory the state machine factory + * @param stateMachinePersist the state machine persist + */ + public DefaultStateMachineService(StateMachineFactory stateMachineFactory, + StateMachinePersist stateMachinePersist) { + Assert.notNull(stateMachineFactory, "'stateMachineFactory' must be set"); + this.stateMachineFactory = stateMachineFactory; + this.stateMachinePersist = stateMachinePersist; + } + + @Override + public StateMachine acquireStateMachine(String machineId) { + synchronized (machines) { + StateMachine stateMachine = machines.get(machineId); + if (stateMachine == null) { + stateMachine = stateMachineFactory.getStateMachine(machineId); + machines.put(machineId, stateMachine); + } + if (stateMachinePersist != null) { + try { + StateMachineContext stateMachineContext = stateMachinePersist.read(machineId); + return restoreStateMachine(stateMachine, stateMachineContext); + } catch (Exception e) { + throw new StateMachineException("Unable to read context from store", e); + } + } else { + return stateMachine; + } + } + } + + @Override + public void releaseStateMachine(String machineId) { + synchronized (machines) { + StateMachine stateMachine = machines.remove(machineId); + if (stateMachine != null) { + stateMachine.stop(); + } + } + } + + /** + * Sets the state machine persist. + * + * @param stateMachinePersist the state machine persist + */ + public void setStateMachinePersist(StateMachinePersist stateMachinePersist) { + this.stateMachinePersist = stateMachinePersist; + } + + protected StateMachine restoreStateMachine(StateMachine stateMachine, final StateMachineContext stateMachineContext) { + if (stateMachineContext == null) { + return stateMachine; + } + stateMachine.stop(); + stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() { + + @Override + public void apply(StateMachineAccess function) { + function.resetStateMachine(stateMachineContext); + } + }); + return stateMachine; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/StateMachineService.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/StateMachineService.java new file mode 100644 index 00000000..d5c7bf8b --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/StateMachineService.java @@ -0,0 +1,45 @@ +/* + * 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.service; + +import org.springframework.statemachine.StateMachine; + +/** + * Service class helping to persist and restore {@link StateMachine}s + * in a runtime environment. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface StateMachineService { + + /** + * Acquires the state machine. + * + * @param machineId the machine id + * @return the state machine + */ + StateMachine acquireStateMachine(String machineId); + + /** + * Release the state machine. + * + * @param machineId the machine id + */ + void releaseStateMachine(String machineId); +} diff --git a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java index 490408a5..b17d61e6 100644 --- a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java +++ b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineConfig.java @@ -20,14 +20,18 @@ import java.util.EnumSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachinePersist; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.StateMachineFactory; 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.jpa.JpaPersistingStateMachineInterceptor; import org.springframework.statemachine.data.jpa.JpaStateMachineRepository; import org.springframework.statemachine.persist.StateMachineRuntimePersister; +import org.springframework.statemachine.service.DefaultStateMachineService; +import org.springframework.statemachine.service.StateMachineService; @Configuration public class StateMachineConfig { @@ -35,7 +39,7 @@ public class StateMachineConfig { //tag::snippetA[] @Configuration @EnableStateMachineFactory - public static class Config extends StateMachineConfigurerAdapter { + public static class MachineConfig extends StateMachineConfigurerAdapter { @Autowired private JpaStateMachineRepository jpaStateMachineRepository; @@ -93,6 +97,16 @@ public class StateMachineConfig { } //end::snippetA[] + @Configuration + public static class ServiceConfig { + + @Bean + public StateMachineService stateMachineService(StateMachineFactory stateMachineFactory, + StateMachinePersist stateMachinePersist) { + return new DefaultStateMachineService<>(stateMachineFactory, stateMachinePersist); + } + } + //tag::snippetB[] public enum States { S1, S2, S3, S4, S5, S6; diff --git a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java index 9ed291c1..655c57c7 100644 --- a/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java +++ b/spring-statemachine-samples/datajpapersist/src/main/java/demo/datajpapersist/StateMachineController.java @@ -22,9 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; 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.config.StateMachineFactory; +import org.springframework.statemachine.service.StateMachineService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.ObjectUtils; @@ -41,15 +39,15 @@ public class StateMachineController { public final static String MACHINE_ID_2 = "datajpapersist2"; private final static String[] MACHINES = new String[] { MACHINE_ID_1, MACHINE_ID_2 }; + private final StateMachineLogListener listener = new StateMachineLogListener(); + private StateMachine currentStateMachine; + @Autowired - private StateMachineFactory stateMachineFactory; + private StateMachineService stateMachineService; @Autowired private StateMachinePersist stateMachinePersist; - private StateMachine cachedStateMachine; - private final StateMachineLogListener listener = new StateMachineLogListener(); - @RequestMapping("/") public String home() { return "redirect:/state"; @@ -60,7 +58,6 @@ public class StateMachineController { @RequestParam(value = "events", required = false) List events, @RequestParam(value = "machine", required = false, defaultValue = MACHINE_ID_1) String machine, Model model) throws Exception { - StateMachine stateMachine = getStateMachine(machine); if (events != null) { for (Events event : events) { @@ -77,40 +74,18 @@ public class StateMachineController { } private synchronized StateMachine getStateMachine(String machineId) throws Exception { - if (cachedStateMachine == null) { - cachedStateMachine = buildStateMachine(machineId); - cachedStateMachine.start(); - } else { - if (!ObjectUtils.nullSafeEquals(cachedStateMachine.getId(), machineId)) { - cachedStateMachine.stop(); - cachedStateMachine = buildStateMachine(machineId); - cachedStateMachine.start(); - } - } - return cachedStateMachine; - } - - private StateMachine buildStateMachine(String machineId) throws Exception { - StateMachine stateMachine = stateMachineFactory.getStateMachine(machineId); - stateMachine.addStateListener(listener); listener.resetMessages(); - return restoreStateMachine(stateMachine, stateMachinePersist.read(machineId)); - } - - private StateMachine restoreStateMachine(StateMachine stateMachine, - StateMachineContext stateMachineContext) { - if (stateMachineContext == null) { - return stateMachine; + if (currentStateMachine == null) { + currentStateMachine = stateMachineService.acquireStateMachine(machineId); + currentStateMachine.addStateListener(listener); + currentStateMachine.start(); + } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) { + currentStateMachine.stop(); + currentStateMachine = stateMachineService.acquireStateMachine(machineId); + currentStateMachine.addStateListener(listener); + currentStateMachine.start(); } - stateMachine.stop(); - stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction>() { - - @Override - public void apply(StateMachineAccess function) { - function.resetStateMachine(stateMachineContext); - } - }); - return stateMachine; + return currentStateMachine; } private Events[] getEvents() {