Polish model usage

- Make model data classes easier to use for
  simple use cases.
- Start adding dev docs.
- Generic docs changes.
- Relates to #172
- Relates to #158
- Relates to #185
This commit is contained in:
Janne Valkealahti
2016-03-19 14:22:00 +00:00
parent d3eb24ed1c
commit 32c335c5e3
9 changed files with 230 additions and 6 deletions

View File

@@ -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]
----

View File

@@ -330,6 +330,22 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetYB]
More about distributed states, refer to section <<sm-distributed>>.
`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 <<devdocs-configmodel>>.
[[sm-factories]]
== State Machine Factories
There are use cases when state machine needs to be created dynamically

View File

@@ -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<S, E> {
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<StateMachineListener<S, E>>(), false,
null, null, null, null, true, new DefaultStateMachineModelVerifier<S, E>());
}
/**
* Instantiates a new state machine configuration config data.
*
* @param beanFactory the bean factory
* @param taskExecutor the task executor

View File

@@ -45,6 +45,25 @@ public class StateData<S, E> {
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<S, E> {
*/
public StateData(Object parent, Object region, S state, Collection<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> 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<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions, boolean initial) {
this.state = state;
this.deferred = deferred;
this.entryActions = entryActions;
this.exitActions = exitActions;
this.parent = parent;
this.region = region;
this.initial = initial;
}
/**

View File

@@ -40,6 +40,17 @@ public class TransitionData<S, E> {
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.
*

View File

@@ -34,17 +34,26 @@ public class TransitionsData<S, E> {
private final Map<S, List<S>> forks;
private final Map<S, List<S>> joins;
/**
* Instantiates a new transitions data.
*
* @param transitionsData the transitions data
*/
public TransitionsData(Collection<TransitionData<S, E>> 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<TransitionData<S, E>> transitions,
public TransitionsData(Collection<TransitionData<S, E>> transitionsData,
Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks, Map<S, List<S>> joins) {
this.transitions = transitions;
this.transitions = transitionsData;
this.choices = choices;
this.forks = forks;
this.joins = joins;

View File

@@ -87,4 +87,27 @@ public class StateMachineModelTests {
stateMachine.sendEvent("E1");
assertThat(stateMachine.getState().getIds(), contains("S2"));
}
@Test
public void testMinimalConfig() {
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
Collection<StateData<String, String>> stateData = new ArrayList<>();
stateData.add(new StateData<String, String>("S1", true));
stateData.add(new StateData<String, String>("S2"));
StatesData<String, String> statesData = new StatesData<>(stateData);
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);
StateMachine<String,String> stateMachine = factory.getStateMachine();
stateMachine.start();
assertThat(stateMachine.getState().getIds(), contains("S1"));
stateMachine.sendEvent("E1");
assertThat(stateMachine.getState().getIds(), contains("S2"));
}
}

View File

@@ -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<States, Events>());
}
}
// 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<States, Events> {
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withVerifier()
.enabled(true)
.verifier(verifier());
}
@Bean
public StateMachineModelVerifier<States, Events> verifier() {
return new StateMachineModelVerifier<States, Events>() {
@Override
public void verify(StateMachineModel<States, Events> model) {
// throw exception indicating malformed model
}
};
}
}
// end::snippetYC[]
public class AccessorSamples {
StateMachine<String, String> stateMachine = null;

View File

@@ -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<String, String> configurationData = new ConfigurationData<>();
// setup states data
Collection<StateData<String, String>> stateData = new ArrayList<>();
stateData.add(new StateData<String, String>("S1", true));
stateData.add(new StateData<String, String>("S2"));
StatesData<String, String> statesData = new StatesData<>(stateData);
// setup transitions data
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
// setup model
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData,
transitionsData);
// instantiate machine via factory
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);
StateMachine<String, String> stateMachine = factory.getStateMachine();
// end::snippetA[]
stateMachine.start();
assertThat(stateMachine.getState().getIds(), contains("S1"));
stateMachine.sendEvent("E1");
assertThat(stateMachine.getState().getIds(), contains("S2"));
}
}