Pax State Machine

State Machine

This is a requirement and design document for a state machine based on an implementation we did for a customer. This is an early draft. The source code exists in ops4j SVN https://scm.ops4j.org/repos/ops4j/laboratory/andreas/org.ops4j.statemachine/ - far from ready !

Requirements

Must have

  • Java code should be used to setup states. No configuration (e.g XML) should be necessarily to create states and transitions.
  • User can implement states in one or more bundles, and have transitions between those.

(ie. One group of states can be implemented in one bundle and a different group of state can be implemented by a different bundle)

  • Support compound states - states in states.
  • Support for many state machines running at the same time.
  • Type safe events for conditions in transitions between states.

Nice to have

  • Generate UML state diagram from runtime by traversal of all states.
  • Generate states and transitions from an UML state diagram, support at least part of the UML state pattern set ?
  • Import e.g. Unimod (http://unimod.sourceforge.net/) configuration
  • Graphical tools for design like Unimod (http://unimod.sourceforge.net/eclipse-plugin.html)
  • Possible to use the state machine without a runtime OSGi ?

    Design and API

org.ops4j.statemachine.IStateMachine

Waits for events to be fired to performs transitions between IState objects. A state machine will be created,started and registered when a org.ops4j.statemachine.IStartState OSGi service is registered. The state machine will be registered with a unique service property nameand the service interface name org.ops4j.statemachine.IStateMachine.

Methods:

  • void fire(Object event) when this method is called:
    • The event will be put into a queue
    • State machine thread wakes up (wait - notifyAll) and removes first event from the queue
    • Check if any transition of the current state will be triggered by that event.
      • If not, check the next event in the queue and remove that event from the queue
      • If it is, perform a transition to the next state, which will be the new current state of the state machine.
    • When a transition occurs to a new state the queue will be emptied. It will not be emptied if the transition goes to the same state.
    •  
  • void addStateTransitionListener(IStateTransitionListener listener) makes it possible to get notified when a transition to a new state occurs.
  •  
  • void removeStateTransitionListener(IStateTransitionListener listener) removes the listener.
  •  
  • IState getCurrentState() returns the current state the statemachine is in.
    org.ops4j.statemachine.IState

TODO. I think most of the methods described in the State class should exist in this interface.

org.ops4j.statemachine.State

Contains state methods, enter and exit. Can contain transitions to other states. Can be registered as an OSGi service with the service interface org.ops4j.statemachine.IState. This is needed if another state in a different bundle should be able to add transition from/to that state.

Methods:

  • State(String name) constructor, for a state that does not belong to a compound state. Name should be unique (see getName method).
  •  
  • State(String name, IState compoundState) constructor, for a state that does belong to a compound state.
  •  
  • void enter() is called when the state machine changes its current state to this state. (It is not called if a transition occurs from to the same state.)
  • void exit() called when the state machine leaves this state to a different state.
  • String getName() is used for generating UML state diagram in runtime and for tracing purpose (the name of the state as it should be displayed in the UML diagram). Should be unique. Can also be used to find and register (as OSGi services) states, see the register method.
  •  
  • IState getParent() returns the parent state if this state belongs to a compound state. If it does not it should return a org.ops4j.statemachine.NullState.
  •  
  • void addTransition( Transition iTransition ) appends the transition to the end of a list.
  • List<Transition> getTransitions()returns an ordered collection of transitions. The state machine will check if an event can trigger an transition by traversing this list.The first one that will trigger a transition will be executed. (It does not check if two or more transition can occur for the same event).
  • void register(BundleContext ctx) registers the state as an OSGi service with the service name org.ops4j.statemachine.IState and service property name set to getName()
  •  
  • static IState find(String name, BundleContext ctx) finds a registered state. Returns on object of type NullState if not found.
  •  
  • Set<IState> getConnectedStates()returns a set of all states that are connected by a transition to this state, including itself. Will also check the parent compound state transitions, if the state is part of a compound state.
  •  
  • IStateMachinegetStateMachine() returns the statemachine running this state, or null if this state is not a current state of a state machine.
  •  
    org.ops4j.statemachine.IStartState

This is a marker interface that extends the IState interface. There are two reason why this interface exists:

  • Can be used as an OSGi service interface name. The state machine bundle listen for IStartState services - when a new one is registered a state machine

will be created, started and registered. (See the convenience method in the StartState.register)

  • Can be used to declare which one of the children states in a compound state should be the start state when an compound state is entered.
    org.ops4j.statemachine.StartState

An implementation of IStartState.

Methods:

  • void start(BundleContext context, StringstateMachineName) registers the state under the service interface name org.ops4j.statemachine.IStartState and service property name=stateMachineNamein order to get a state machine to start on this state. The state machine that is created will be registered with the service property name=stateMachineName.
    org.ops4j.statemachine.EndState

When the state machine enter this state the state machine will be stopped.

org.ops4j.statemachine.Condition

Same as a guard in UML (maybe we should rename it to guard instead) Checks if the given event will satisfy the condition. TODO

org.ops4j.statemachine.Action

When a transition occur an action can be performed. TODO

==org.ops4j.statemachine.Transition Contains an condition, action and a state to go to, when the condition is satisfied. TODO

Tutorial

Transitions

Here is an example of how to model a light bulb having two state, on and off. An transition to the other state will occur then an event of type ToggleEvent is fired.

IState stateOn = new State("on");
IState stateOff= new State("off");

class ToggleEvent
{
}

Condition condition = new EventCondition(ToggleEvent.class);

Transition transitionToOn = new Transition(condition, stateOn);
stateOff.addTransition(transitionToOn);

Transition transitionToOff = new Transition(condition, stateOff);
stateOn.addTransition(transitionToOff);The code above can be simplified by using some convenience methods in the State class. IState stateOn = new State("on");
IState stateOff= new State("off");

class ToggleEvent
{
}

stateOn.addTransition(ToggleEvent.class, stateOff);
stateOff.addTransition(ToggleEvent.class, stateOn);It is also possible to have condition that checks the instance of the event as well as the type of the event.
Actions and State methods

We can add action both then the transition occurs in an Action object or when a new state is entered. Example of action in the state enter method.

IState stateOn = new State("on")
{
@Override
public void enter()
{ 				System.out.println("Light On"); 			}
};Remember that the enter method will be called only if a new state is entered. If a transition goes back to itself the enter method will not be called. You can also perform the action in an action object that will be performed when the transition occurs. Example: Condition condition = new EventCondition(ToggleEvent.class);
IAction action = new Action()
{
public void perform(Object event)
{ 				System.out.println("Some one switched on the light"); 			}
};
Transition transitionToOn = new Transition(condition, action, stateOn);
Compound States

Let say we want to expand the example above of the light bulb with the working and not working states. One way to do it is by using compound states. It can only be in on or off mode if the light bulb is working. If it is broken it is considered as neither on or off, in this example. // compound states: working and broken, should start in working state

IState working = new StartState("working");
IState broken = new State("broken");

class BrokenEvent
{
}

// if it is working it can always be broken
working.addTransition(BrokenEvent.class, broken);

// we have to tell which of the child states is the start state in a compound state
IState stateOn = new StartState("on", working);
IState stateOff= new State("off", working);

class ToggleEvent
{
}

stateOn.addTransition(ToggleEvent.class, stateOff);
stateOff.addTransition(ToggleEvent.class, stateOn);

Notice that the light bulb can be broken both when it is on and off. When an event is fired the state machine will first check transitions for the child state of the compound state. If no transition occurs it will then check if the parent compound state will trigger an transition for this event.

When the working compound state is entered from the example above, the state machine will traverse all its child states and enter the child state which is of type StartState. That means that the enter method of the compound working and the child StartState on will be entered.

A connected graph of state can never have two or more states of type StartState. (An assert will throw a RuntimeException if this is not true when a transition is added between two states).

Register start state

In order to let a state machine run we must register a start state. Let say we want the working state to be the start state. A start state should implement and register the interface org.osp4j.statemachine.IStartStateThis can be done by using the register method.

Example:

BundleContext context = ...;

// the working state object is from the example above
// start a state machine named lightbulb on the state working
working.start(context, "lightbulb");

// the working state has now been register with the OSGi service
// interface name org.osp4j.statemachine.IStartState which will cause
// the creation and registration of a new state machine with the name lightbulb.
Fire events

In order to fire events to the light bulb example above we must find the state machine for the light bulb.

// no error handling here \!
ServiceReference ref\[\] = context.getServiceReferences(IStateMachine.class.getName(), "(name=lightbulb)");
IStateMachine machine = (IStateMachine) context.getService(ref[0]);

class BrokenEvent
{
}

machine.fire(new BrokenEvent());

Maybe the code above should be simplified with some convenience method and the API.

Start and stop of bundles containing states

It is possible that groups of state will disappear/appear when bundles are stopped/started.

A bundle can contain states that have transitions from or to other external states in other bundles. Standard OSGi lookup and bind are used to access those other states. When transitions are added to states outside a bundle it is important that all transitions in those external states are removed when the bundle is stopped, so that the state machine does not enter states that belongs to a bundle that has been stopped.

We should also check if the state machine is currently running on one of its states. If it is the state machine should enter a different state that belongs to a different bundle or the state machine should be stopped.