diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index 03439572..ea0bcc3a 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -626,3 +626,24 @@ What's happening in above chart: * At a same time we request states from all machines and plot it. * Finally we do a simple transition back to `S21` from `S211` to make sure that all state machines are still functioning properly. + +[[devdocs]] +== Developer Documentation +This appendix provides generic information for a developers who may +want to contribute or other people who want to understand how state +machine works or what are its internal concepts. + +[[devdocs-configmodel]] +=== StateMachine Config Model +`StateMachineModel` and other related SPI classes are an abstraction +between various configuration and factory classes. This also allows +easier integation for others to build state machines. + +As shown above a state machine can be instantiated by building a model +using configuration data classes and then asking a factory to build a +state machine. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests6.java[tags=snippetA] +---- diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 2681d80c..ec1db3f6 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -330,6 +330,22 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetYB] More about distributed states, refer to section <>. +`StateMachineModelVerifier` is an interface what is used internally to +do some sanity checks for a state machine structure. Its purpose is to +fail fast early instead of letting common configuration errors into a +state machine itself. On default verifier is automatically enabled and +`DefaultStateMachineModelVerifier` implementation is used. + +With `withVerifier()` user can disable verifier or set a custom one if +needed. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetYC] +---- + +More about config model, refer to section <>. + [[sm-factories]] == State Machine Factories There are use cases when state machine needs to be created dynamically diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ConfigurationData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ConfigurationData.java index 8fc8e967..e1382239 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ConfigurationData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/ConfigurationData.java @@ -15,13 +15,17 @@ */ package org.springframework.statemachine.config.model; +import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; import org.springframework.security.access.AccessDecisionManager; import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder; +import org.springframework.statemachine.config.model.verifier.DefaultStateMachineModelVerifier; import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier; import org.springframework.statemachine.ensemble.StateMachineEnsemble; import org.springframework.statemachine.listener.StateMachineListener; @@ -52,7 +56,15 @@ public class ConfigurationData { private final SecurityRule transitionSecurityRule; /** - * Instantiates a new state machine configuration config. + * Instantiates a new state machine configuration config data. + */ + public ConfigurationData() { + this(null, new SyncTaskExecutor(), new ConcurrentTaskScheduler(), false, null, new ArrayList>(), false, + null, null, null, null, true, new DefaultStateMachineModelVerifier()); + } + + /** + * Instantiates a new state machine configuration config data. * * @param beanFactory the bean factory * @param taskExecutor the task executor diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java index 61b4c05c..e0a9c71a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/StateData.java @@ -45,6 +45,25 @@ public class StateData { private boolean end = false; private PseudoStateKind pseudoStateKind; + /** + * Instantiates a new state data. + * + * @param state the state + */ + public StateData(S state) { + this(state, false); + } + + /** + * Instantiates a new state data. + * + * @param state the state + * @param initial the initial + */ + public StateData(S state, boolean initial) { + this(null, null, state, null, null, null, initial); + } + /** * Instantiates a new state data. * @@ -57,12 +76,29 @@ public class StateData { */ public StateData(Object parent, Object region, S state, Collection deferred, Collection> entryActions, Collection> exitActions) { + this(parent, region, state, deferred, entryActions, exitActions, false); + } + + /** + * Instantiates a new state data. + * + * @param parent the parent + * @param region the region + * @param state the state + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param initial the initial + */ + public StateData(Object parent, Object region, S state, Collection deferred, + Collection> entryActions, Collection> exitActions, boolean initial) { this.state = state; this.deferred = deferred; this.entryActions = entryActions; this.exitActions = exitActions; this.parent = parent; this.region = region; + this.initial = initial; } /** diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionData.java index 2330aef3..d181ac0e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionData.java @@ -40,6 +40,17 @@ public class TransitionData { private final TransitionKind kind; private final SecurityRule securityRule; + /** + * Instantiates a new transition data. + * + * @param source the source + * @param target the target + * @param event the event + */ + public TransitionData(S source, S target, E event) { + this(source, target, null, event, null, null, null, null, TransitionKind.EXTERNAL, null); + } + /** * Instantiates a new transition data. * diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java index 9d9c9fad..36186330 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java @@ -34,17 +34,26 @@ public class TransitionsData { private final Map> forks; private final Map> joins; + /** + * Instantiates a new transitions data. + * + * @param transitionsData the transitions data + */ + public TransitionsData(Collection> transitionsData) { + this(transitionsData, null, null, null); + } + /** * Instantiates a new state machine transitions. * - * @param transitions the transitions + * @param transitionsData the transitions data * @param choices the choices * @param forks the forks * @param joins the joins */ - public TransitionsData(Collection> transitions, + public TransitionsData(Collection> transitionsData, Map>> choices, Map> forks, Map> joins) { - this.transitions = transitions; + this.transitions = transitionsData; this.choices = choices; this.forks = forks; this.joins = joins; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java index 6e9bc1d3..0bc3fdaf 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java @@ -87,4 +87,27 @@ public class StateMachineModelTests { stateMachine.sendEvent("E1"); assertThat(stateMachine.getState().getIds(), contains("S2")); } + + @Test + public void testMinimalConfig() { + ConfigurationData configurationData = new ConfigurationData<>(); + + Collection> stateData = new ArrayList<>(); + stateData.add(new StateData("S1", true)); + stateData.add(new StateData("S2")); + StatesData statesData = new StatesData<>(stateData); + + Collection> transitionData = new ArrayList<>(); + transitionData.add(new TransitionData("S1", "S2", "E1")); + TransitionsData transitionsData = new TransitionsData<>(transitionData); + + StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData); + ObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(stateMachineModel); + + StateMachine stateMachine = factory.getStateMachine(); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index 122c4e99..e11af2c0 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -49,6 +49,8 @@ import org.springframework.statemachine.config.builders.StateMachineConfiguratio import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.config.configurers.StateConfigurer.History; +import org.springframework.statemachine.config.model.StateMachineModel; +import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier; import org.springframework.statemachine.ensemble.StateMachineEnsemble; import org.springframework.statemachine.event.OnExtendedStateChanged; import org.springframework.statemachine.event.OnStateMachineError; @@ -814,7 +816,6 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { .taskScheduler(new ConcurrentTaskScheduler()) .listener(new StateMachineListenerAdapter()); } - } // end::snippetYA[] @@ -838,10 +839,37 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { // naturally not null but should return ensemble instance return null; } - } // end::snippetYB[] +// tag::snippetYC[] + @Configuration + @EnableStateMachine + public class Config19 + extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineConfigurationConfigurer config) + throws Exception { + config + .withVerifier() + .enabled(true) + .verifier(verifier()); + } + + @Bean + public StateMachineModelVerifier verifier() { + return new StateMachineModelVerifier() { + + @Override + public void verify(StateMachineModel model) { + // throw exception indicating malformed model + } + }; + } + } +// end::snippetYC[] + public class AccessorSamples { StateMachine stateMachine = null; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests6.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests6.java new file mode 100644 index 00000000..c126dc91 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests6.java @@ -0,0 +1,68 @@ +/* + * 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 org.springframework.statemachine.docs; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Test; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.ObjectStateMachineFactory; +import org.springframework.statemachine.config.model.ConfigurationData; +import org.springframework.statemachine.config.model.DefaultStateMachineModel; +import org.springframework.statemachine.config.model.StateData; +import org.springframework.statemachine.config.model.StateMachineModel; +import org.springframework.statemachine.config.model.StatesData; +import org.springframework.statemachine.config.model.TransitionData; +import org.springframework.statemachine.config.model.TransitionsData; + +public class DocsConfigurationSampleTests6 { + + @Test + public void testMinimalConfig() { +// tag::snippetA[] + // setup configuration data + ConfigurationData configurationData = new ConfigurationData<>(); + + // setup states data + Collection> stateData = new ArrayList<>(); + stateData.add(new StateData("S1", true)); + stateData.add(new StateData("S2")); + StatesData statesData = new StatesData<>(stateData); + + // setup transitions data + Collection> transitionData = new ArrayList<>(); + transitionData.add(new TransitionData("S1", "S2", "E1")); + TransitionsData transitionsData = new TransitionsData<>(transitionData); + + // setup model + StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, + transitionsData); + + // instantiate machine via factory + ObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(stateMachineModel); + StateMachine stateMachine = factory.getStateMachine(); +// end::snippetA[] + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), contains("S1")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), contains("S2")); + } + +}