Ref doc updates

This commit is contained in:
Janne Valkealahti
2015-03-28 18:51:19 +00:00
parent d108410005
commit 85cc2df29e
14 changed files with 561 additions and 86 deletions

View File

@@ -181,6 +181,7 @@ configure(rootProject) {
from 'spring-statemachine-samples/src/main/java/'
from 'spring-statemachine-samples/turnstile/src/main/java/'
from 'spring-statemachine-samples/showcase/src/main/java/'
from 'spring-statemachine-samples/cdplayer/src/main/java/'
include '**/*.java'
into 'docs/src/reference/asciidoc/samples'
}

View File

@@ -24,6 +24,17 @@ include::samples/Events.java[tags=snippetA]
== State Machine Concepts
This appendix provides generic information about state machines.
=== Quick Example
Assuming we have states _STATE1_, _STATE2_ and events _EVENT1_,
_EVENT2_, logic of state machine can be defined as shown in below
quick example.
image::images/statechart0.png[]
[source,java,indent=0]
----
include::samples/IntroSample.java[tags=snippetA]
----
[glossary]
=== Glossary
@@ -75,28 +86,77 @@ to FALSE.
A action is a behaviour executed during the triggering of the
transition.
[[crashcourse]]
=== A State Machines Crash Course
This appendix provides generic crash course to a state machine
concepts.
==== States
TBD.
A state is a model which a state machine can be in. It is always
easier to describe state as a real world example rather than trying to
abstract concepts with a generic documentation. For example lets take
a simple example of a keyboard most of use are using every single day.
If you have a full keyboard which has normal keys on a left side and
the numeric keypad on a right side you may have noticed that the
numeric keypad may be in a two different stated depending whether
numlock is activated or not. If it is not active then typing will
result navigation using arrows, etc. If numpad is active then typing
will result numbers to be used. Essentially numpad part of a keyboard
can be in two different states.
To relate state concept to programming it means that instead of using
flags, nested if/else/break clauses or other impractical logic you
simply rely on state, state variables or other interaction with a
state machine.
==== Guard Conditions
TBD.
Guard conditions are expressions which evaluates either to *TRUE* or
*FALSE* based on extended state variables and event parameters. Guards
are used with actions and transitions to dynamically choose if
particular action or transition should be executed. Aspects of guards,
event parameters and extended state variables are simply to make state
machine desing much more simple.
==== Events
TBD.
Event is the most used trigger behaviour to drive a state machine.
There are other ways to trigger behaviour to happen in state machine
like a timer but events are the ones which really allows user to
interact with a state machine. Events are also called as signals to
possibly alter a state machine state.
==== Transitions
TBD.
A transition is a relationship between a source state and a target
state. A switch from a state to another is a _state transition_ caused
by a _trigger_.
==== Actions
TBD.
Actions are the ones which really glues state machine state changes
with a users own code. State machine can execute action on various
changes and steps in a state machine like entering or exiting a state,
or doing a state transition.
==== Hierarchical State Machines
TBD.
Concept of a hierarchical state machine is used to simplify state
design when particular states can only exist together.
==== Regions
TBD.
Regions which are also called as orthogonal regions are usually viewed
as exclusive OR operation applied to a states. Concept of a region in
terms of a state machine is usually a little difficult to understang
but things gets a little simpler with a simple example.
Some of use have a full size keyboard with main keys on a left side and numeric
keys on a right side. You've probably noticed that both sides really
have their own state which you see if you press a numlock key which
only alters behaviour of numbad itself. If you don't have a full size
keyboard you can buy a simple external usb numbad having only numbad
part of a keys. If left and right side can freely exist without the
other they must have a totally different states which means they are
operating on different state machines.
It would be a little inconvenient to handle two different
statemachines as totally separate entities because in a sense they are
still working together in a sense. This is why orthogonal regios can
combine together a multiple simultaneous states withing a single state
in a state machine.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,10 +1,49 @@
[[introduction]]
= Introduction
Spring State Machine(SSM) is a framework for application developers to
use state machine concepts with Spring.
use traditional state machine concepts with Spring applications.
Before you continue it's worth to go through appendices <<glossary>>
and <<crashcourse>> to get a generic idea of what state machines are
mostly because rest of a documentation expects reader to be fairly
familiar with state machine concepts.
== Requirements
Spring Statemachine {revnumber} is built and tested with JDK 7, Spring
Spring Statemachine {revnumber} is built and tested with JDK 7 and Spring
Framework {spring-version}.
== Background
State machines are powerful because behaviour is always supposed to be
consistent and relatively easily debugged due to ways how operational
rules are written in stone when machine is started. Idea is that your
application is and may exist in a finite number of states and then something
happens which takes your application from one state to the next.
It is much easier to design high level logic outside of your
application and then interact witha state machine with a various
different ways.
Traditionally state machines are added to a existing project when
developer realizes that code base is starting to look like a plate
full of spaghetti. Spaghetti code looks like never ending hierarchical
structure of IFs, ELSEs and BREAK clauses and probably compiler should
ask developer to go home when things are starting to look too complex.
== Usage Scenarios
Project is a good candiate to use state machines if:
* Application or part of its structure can be represented as states.
* You want to split complex logic into smaller manageable tasks.
* Application is already suffering concurrency issues with i.e.
something happening asynchronously.
You are already trying to implement a state machine if:
* Use of boolean flags or enums to model situations.
* Having variables which only have meaning for some part of your
application lifecycle.
* Looping throught if/else structure and checking if particular flag or
enum is set and then making further exceptions what to do when certain
combination of your flags and enums exists or doesn't exist together.

View File

@@ -10,12 +10,6 @@ Moore presented another paper which is known as a Moore Machine. If
you're ever read anything about state machines, names Mealy and Moore
should have popped up at some point.
Traditionally state machines are added to a existing project when
developer realizes that code base is starting to look like a plate
full of spaghetti. Spaghetti code looks like never ending hierarchical
structure of IFs, ELSEs and BREAK clauses and probably compiler should
ask developer to go home when things are starting to look too complex.
This reference documentations contains following parts.
<<introduction>> introduction to this reference documentation

View File

@@ -76,11 +76,10 @@ Event PUSH send
----
== Showcase
Showcase is a complex state machine showing all possible transition
topologies up to four levels of state nesting.
image::images/statechart2.png[]
image::images/statechart2.png[width=200]
.States
[source,java,indent=0]
@@ -111,3 +110,122 @@ include::samples/demo/showcase/Application.java[tags=snippetD]
----
include::samples/demo/showcase/Application.java[tags=snippetE]
----
== CD Player
CD Player is a sample which resembles better use case of most of use have
used in a real world. CD Player itself is a really simple entity where
user can open a deck, inser or change a disk, then drive player
functionality by pressing various buttons like _eject_, _play_,
_stop_, _pause_, _rewind_ and _backward_.
How many of use have really given a thought of what it will take to
make a code for a CD Player which interacts with a hardware. Yes,
concept of a player is overly simple but if you look behind a scenes
things actually get a bit convoluted.
You've probably noticed that if your deck is open and you press play,
deck will close and a song will start to play if CD was inserted in
a first place. In a sense when deck is open you first need to close
it and then try to start playing if cd is actually instered. Hopefully
you have now realised that a simple CD Player is not anymore so simple.
Sure you can wrap all this with a simple class with few boolean variables
and probably few nested if/else clauses, that will do the job, but what
about if you need to make all this behaviour much more complex, do you
really want to keep adding more flags and if/else clauses.
image::images/statechart3.png[]
Lets go throught how this sample and its state machine is designed and
how those two interacts with each other.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetA]
----
What we did in above configuration:
* We used EnumStateMachineConfigurerAdapter to configure states and
transitions.
* States _CLOSED_ and _OPEN_ are defined as substates of _IDLE_,
states _PLAYING_ and _PAUSED_ are defined as substates of _BUSY_.
* With state _CLOSED_ we added entry action as bean
_closedEntryAction_.
* With transition we mostly mapped events to expected state
transitions like _EJECT_ closing and opening a deck, _PLAY_, _STOP_
and _PAUSE_ doing their natural transitions. Few words to mention
what we did for other transitions.
** With source state _PLAYING_ we added a timer trigger which is
needed to automatically track elapsed time within a playing track and
to have facility to make a decision when to switch to next track.
** With event _PLAY_ if source state is _IDLE_ and target state is
_BUSY_ we defined action _playAction_ and guard _playGuard_.
** Lastly with event _LOAD_ and state _OPEN_ we defined internal
transition with action _loadAction_ which will insert cd disc into
extended state variables.
This machine only have six states which are introduced as an enum.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetB]
----
Events represent, in a sense in this example, what buttons user would
press and if user loads a cd disc into a deck.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetC]
----
Beans _cdPlayer_ and _library_ are just used with a sample to drive
the application.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetD]
----
We can define extended state variable key as simple enums.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetE]
----
We wanted to make this samply type safe so we're defining our own
annotation _@StatesOnTransition_ which have a mandatory meta
annotation _@OnTransition_.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetF]
----
_ClosedEntryAction_ is a entry action for state _CLOSED_ to simply
send and _PLAY_ event to a statemachine if cd disc is present.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetG]
----
_LoadAction_ is simply updating extended state variable if event
headers contained information about a cd disc to load.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetH]
----
_PlayAction_ is simply resetting player elapsed time which is kept as
an extended state variable.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetI]
----
_PlayGuard_ is used to guard transition from _IDLE_ to _BUSY_ with
event _PLAY_ if extended state variable _CD_ doesn't indicate that cd
disc has been loaded.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetJ]
----
Now lets see how this cd player works and we can go a little deeper in
its state machine logic.

View File

@@ -73,36 +73,115 @@ Configuration for state machine factory is exactly same as you've seen
in various examples in this document where state machine configuration
is hard coded.
Actually creating a state machine using _@EnableStateMachine_ will
work via factory so _@EnableStateMachineFactory_ is merely exposing
that factory via its interface.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetF]
----
=== Factory Limitations
Now that you've used _@EnableStateMachineFactory_ to create a factory
instead of a state machine bean, it can be injected and used as is to
request new state machines.
xxx
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetL]
----
=== Factory Limitations
Current limitation of factory is that all actions and guard it is
associating with created state machine will share a same instances.
This means that from your actions and guard you will need to
specifially handle a case that same bean will be called by a different
state machines. This limitation is something which will be resolved in
future releases.
[[sm-listeners]]
== State Machine Listeners
== Listening State Machine Events
There are use cases where you just want to know what is happening with
a state machine, react to something or simply get logging for
debugging purposes. SSM provides interfaces for adding listeners which
then gives an option to get callback when various state changes,
actions, etc are happening.
[[sm-context]]
== Context Integration
You basically have two options, either to listen Spring application
context events or directly attach listener to a state machine. Both of
these basically will provide same information where one is producing
events as event classes and other producing callbacks via a listener
interface. Both of these have pros and cons which will be discussed later.
TBD
=== Annotation Support
TBD
=== Context Events
=== Application Context Events
Application context events classes are _OnTransitionStartEvent_,
_OnTransitionEvent_, _OnTransitionEndEvent_, _OnStateExitEvent_,
_OnStateEntryEvent_ and _OnStateChangedEvent_. There can be used as is
as spring typed _ApplicationListener_ class but all also share a
common class _StateMachineEvent_ which can be used to get events for
all.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetG]
----
=== State Machine Listener
For using _StateMachineListener_ you can either extend it and
implement all callback methods or use _StateMachineListenerAdapter_
class which contains stub method implementations and choose which ones
to override.
=== Limitations and Problems
TBD ctx events may create too much traffic, etc.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetH]
----
[[sm-context]]
== Context Integration
It is a little limited to do interaction with a state machine by
either listening its events or using actions with states and
transitions. Time to time this approach would be too limited and
verbose to create interaction with the application a state machine is
working with. For this specific use case we have made a spring style
context intergration which easily attach state machine functionality
into your beans.
=== Annotation Support
_@WithStateMachine_ annotation can be used to associate a state
machine with a existing bean. Withing this annotation a propertys
_source_ and _target_ can be used to qualify a transition
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetI]
----
Default _@OnTransition_ annotation can't be used with a state and
event enums user have created due to java language limitations, thus
string representation have to be used.
However if you want to have a type safe annotation it is possible to
create a new annotation and use _@OnTransition_ as meta annotations.
This user level annotation can make a reference to actual states and
events enums and framework will try to match these in a same way.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetJ]
----
Above we created a _@StatesOnTransition_ annotation which defines
`source` and `target` as a type safe manner.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetK]
----
In your own bean you can then use this _@StatesOnTransition_ as is and
use type safe `source` and `target`.

View File

@@ -0,0 +1,16 @@
+----------------------------------------+
| SM |
+----------------------------------------+
| |
| +----------+ +----------+ |
| *-->| STATE1 | | STATE2 | |
| +----------+ +----------+ |
| | entry/ | | entry/ | |
| | exit/ | | exit/ | |
| | |--EVENT1->| | |
| | | | | |
| | |<-EVENT2--| | |
| | | | | |
| +----------+ +----------+ |
| |
+----------------------------------------+

View File

@@ -1,19 +0,0 @@
+----------------------------------------------------------------+
| SM |
+----------------------------------------------------------------+
| |
| +----------------+ +----------------+ |
| *-->| LOCKED | | UNLOCKED | |
| +----------------+ +----------------+ |
| +---| entry/ | | entry/ |---+ |
| | | exit/ | | exit/ | | |
| | | | | | | |
| PUSH| | |---COIN-->| | |COIN |
| | | | | | | |
| | | | | | | |
| | | |<--PUSH---| | | |
| +-->| | | |<--+ |
| | | | | |
| +----------------+ +----------------+ |
| |
+----------------------------------------------------------------+

View File

@@ -15,21 +15,34 @@
*/
package org.springframework.statemachine.docs;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.EnumSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.annotation.AnnoStates;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.config.EnableStateMachine;
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.event.StateMachineEvent;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
/**
* Tests for state machine configuration.
@@ -185,7 +198,7 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
// tag::snippetG[]
static class StateMachineEventListener implements ApplicationListener<StateMachineEvent> {
static class StateMachineApplicationEventListener implements ApplicationListener<StateMachineEvent> {
@Override
public void onApplicationEvent(StateMachineEvent event) {
@@ -193,4 +206,78 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
}
// end::snippetG[]
// tag::snippetH[]
static class StateMachineEventListener extends StateMachineListenerAdapter<States, Events> {
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
}
@Override
public void stateEntered(State<States, Events> state) {
}
@Override
public void stateExited(State<States, Events> state) {
}
@Override
public void transition(Transition<States, Events> transition) {
}
@Override
public void transitionStarted(Transition<States, Events> transition) {
}
@Override
public void transitionEnded(Transition<States, Events> transition) {
}
}
// end::snippetH[]
// tag::snippetI[]
@WithStateMachine
static class Bean1 {
@OnTransition(source = "S1", target = "S2")
public void fromS1ToS2() {
}
}
// end::snippetI[]
// tag::snippetJ[]
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@OnTransition
static @interface StatesOnTransition {
States[] source() default {};
States[] target() default {};
}
// end::snippetJ[]
// tag::snippetK[]
@WithStateMachine
static class Bean2 {
@StatesOnTransition(source = States.S1, target = States.S2)
public void fromS1ToS2() {
}
}
// end::snippetK[]
// tag::snippetL[]
static class Bean3 {
@Autowired
StateMachineFactory<States, Events> factory;
void method() {
StateMachine<States,Events> stateMachine = factory.getStateMachine();
stateMachine.start();
}
}
// end::snippetL[]
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2015 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 java.util.EnumSet;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class IntroSample {
// tag::snippetA[]
static enum States {
STATE1, STATE2
}
static enum Events {
EVENT1, EVENT2
}
@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.STATE1)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.STATE1).target(States.STATE2)
.event(Events.EVENT1)
.and()
.withExternal()
.source(States.STATE2).target(States.STATE1)
.event(Events.EVENT2);
}
}
@WithStateMachine
static class MyBean {
@OnTransition(target = "STATE1")
void toState1() {
}
@OnTransition(target = "STATE2")
void toState2() {
}
}
// end::snippetA[]
}

View File

@@ -88,54 +88,29 @@ public class Application {
}
@Bean
public Action<States, Events> closedEntryAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
if (context.getTransition() != null && context.getTransition().getSource().getId() == States.CLOSED
&& context.getMessageHeader(Variables.CD) != null) {
context.getStateMachine().sendEvent(Events.PLAY);
}
}
};
public ClosedEntryAction closedEntryAction() {
return new ClosedEntryAction();
}
@Bean
public Action<States, Events> loadAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
Object cd = context.getMessageHeader(Variables.CD);
context.getExtendedState().getVariables().put(Variables.CD, cd);
}
};
public LoadAction loadAction() {
return new LoadAction();
}
@Bean
public Action<States, Events> playAction() {
return new Action<States, Events>() {
@Override
public void execute(StateContext<States, Events> context) {
context.getExtendedState().getVariables().put(Variables.ELAPSEDTIME, 0l);
}
};
public PlayAction playAction() {
return new PlayAction();
}
@Bean
public Guard<States, Events> playGuard() {
return new Guard<States, Events>() {
@Override
public boolean evaluate(StateContext<States, Events> context) {
ExtendedState extendedState = context.getExtendedState();
return extendedState.getVariables().get(Variables.CD) != null;
}
};
public PlayGuard playGuard() {
return new PlayGuard();
}
}
//end::snippetA[]
//tag::snippetB[]
public static enum States {
// super state of PLAYING and PAUSED
@@ -186,6 +161,52 @@ public class Application {
}
//end::snippetF[]
//tag::snippetG[]
public static class ClosedEntryAction implements Action<States, Events> {
@Override
public void execute(StateContext<States, Events> context) {
if (context.getTransition() != null
&& context.getTransition().getSource().getId() == States.CLOSED
&& context.getMessageHeader(Variables.CD) != null) {
context.getStateMachine().sendEvent(Events.PLAY);
}
}
}
//end::snippetG[]
//tag::snippetH[]
public static class LoadAction implements Action<States, Events> {
@Override
public void execute(StateContext<States, Events> context) {
Object cd = context.getMessageHeader(Variables.CD);
context.getExtendedState().getVariables().put(Variables.CD, cd);
}
}
//end::snippetH[]
//tag::snippetI[]
public static class PlayAction implements Action<States, Events> {
@Override
public void execute(StateContext<States, Events> context) {
context.getExtendedState().getVariables().put(Variables.ELAPSEDTIME, 0l);
}
}
//end::snippetI[]
//tag::snippetJ[]
public static class PlayGuard implements Guard<States, Events> {
@Override
public boolean evaluate(StateContext<States, Events> context) {
ExtendedState extendedState = context.getExtendedState();
return extendedState.getVariables().get(Variables.CD) != null;
}
}
//end::snippetJ[]
public static void main(String[] args) throws Exception {
Bootstrap.main(args);
}