
eu.hgross.blaubot.core.statemachine.ConnectionStateMachine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of blaubot Show documentation
Show all versions of blaubot Show documentation
An easy to use publish/subscribe middleware to create and communicate through dynamically created adhoc networks.
package eu.hgross.blaubot.core.statemachine;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
import eu.hgross.blaubot.admin.AbstractAdminMessage;
import eu.hgross.blaubot.admin.RelayAdminMessage;
import eu.hgross.blaubot.core.Blaubot;
import eu.hgross.blaubot.core.BlaubotConnectionManager;
import eu.hgross.blaubot.core.IBlaubotAdapter;
import eu.hgross.blaubot.core.IBlaubotConnection;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.core.ServerConnectionManager;
import eu.hgross.blaubot.core.State;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionManagerListener;
import eu.hgross.blaubot.core.acceptor.discovery.BlaubotBeaconService;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeacon;
import eu.hgross.blaubot.core.statemachine.events.AbstractBlaubotDeviceDiscoveryEvent;
import eu.hgross.blaubot.core.statemachine.events.AbstractBlaubotStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.AbstractTimeoutStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.AdminMessageStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.ConnectionClosedStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.ConnectionEstablishedStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.StartStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.events.StopStateMachineEvent;
import eu.hgross.blaubot.core.statemachine.states.FreeState;
import eu.hgross.blaubot.core.statemachine.states.IBlaubotState;
import eu.hgross.blaubot.core.statemachine.states.StoppedState;
import eu.hgross.blaubot.messaging.IBlaubotAdminMessageListener;
import eu.hgross.blaubot.util.Log;
/**
* Statemachine for the network creation. Simply delegates the events from the {@link BlaubotConnectionManager} and the
* {@link AbstractBlaubotDeviceDiscoveryEvent}s from the beacons to the current state. The state will handle the event
* based state changes by themselves (calling nextState).
*
* @author Henning Gross
*
*/
public class ConnectionStateMachine {
private static final String LOG_TAG = "ConnectionStateMachine";
protected final Blaubot blaubot;
private final List connectionStateMachineListeners;
private final List beacons;
private final List acceptors;
private final List adapters;
private final BlaubotBeaconService beaconService;
private final BlockingQueue stateMachineEventQueue;
private final StateMachineSession stateMachineSession;
private StateMachineEventDispatcher stateMachineEventDispatcher;
protected IBlaubotState currentState;
/**
* Create the connection state machine for a blaubot instance.
*
* @param ownDevice
* @param connectionManager the connection manager managing the blaubot acceptors
* @param adapters the adapters
* @param blaubot the blaubot instance
*/
public ConnectionStateMachine(IBlaubotDevice ownDevice, final BlaubotConnectionManager connectionManager, final List adapters, List beacons, final Blaubot blaubot, ServerConnectionManager serverConnectionManager) {
this.blaubot = blaubot;
this.adapters = adapters;
this.stateMachineEventQueue = new LinkedBlockingQueue();
this.beacons = beacons;
this.acceptors = BlaubotAdapterHelper.getConnectionAcceptors(adapters);
this.beaconService = new BlaubotBeaconService(ownDevice, beacons, acceptors, this);
this.connectionStateMachineListeners = new CopyOnWriteArrayList<>();
// connect to admin messages
this.blaubot.getChannelManager().addAdminMessageListener(adminMessageChannelListener);
connectionManager.addConnectionListener(connectionListener);
this.stateMachineSession = new StateMachineSession(this, ownDevice, serverConnectionManager);
this.currentState = new StoppedState();
}
private final Object eventDispatcherLock = new Object();
/**
* Starts the CSM's event dispatcher. Mandatory for the CSM to do anything.
*/
public void startEventDispatcher() {
synchronized (eventDispatcherLock) {
if(isEventDispatcherRunning()) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "EventDispatcher already running - ignoring startEventDispatcher()");
}
return;
}
stateMachineEventDispatcher = new StateMachineEventDispatcher();
stateMachineEventDispatcher.start();
}
}
public void stopEventDispatcher() {
synchronized (eventDispatcherLock) {
if(stateMachineEventDispatcher != null) {
stateMachineEventDispatcher.interrupt();
stateMachineEventDispatcher = null;
}
}
}
/**
* Starts the state machine by pushing a start event to the CSM's event queue.
*/
public void startStateMachine() {
pushStateMachineEvent(new StartStateMachineEvent(currentState));
}
/**
* Actually handles the start of the CSM
*/
private void _startStateMachine() {
if (!(currentState instanceof StoppedState)) {
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "ConnectionStateMachine already started. Ignoring _startStateMachine()");
}
return; // already started
}
StoppedState stoppedState = (StoppedState) this.currentState;
stoppedState.handleState(this.stateMachineSession);
changeState(new FreeState());
}
/**
* Requests a stop of the CSM by pushing an event to the CSM's event queue.
*/
public void stopStateMachine() {
pushStateMachineEvent(new StopStateMachineEvent(currentState));
}
/**
* Actually handles the stop of the state machine
*/
private void _stopStateMachine() {
this.changeState(new StoppedState());
}
/**
* changes state
*
* @param newState
*/
private void changeState(IBlaubotState newState) {
if(newState == currentState)
return; // do nothing if same state
IBlaubotState oldState = currentState;
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "[Current state: " + currentState + "] Changing to state " + newState);
}
assertStateChange(currentState, newState);
boolean sendStarted = false;
boolean sendStopped = false;
if (currentState instanceof StoppedState && !(newState instanceof StoppedState)) {
sendStarted = true;
} else if (newState instanceof StoppedState && !(currentState instanceof StoppedState)) {
sendStopped = true;
}
currentState = newState;
// let the beacons signal the new state, if not a StoppedState, which would make no sense at all
// inform the beacon service
beaconService.onStateChanged(currentState);
if (!(newState instanceof StoppedState)) {
for (IBlaubotBeacon beacon : beacons) {
beacon.onConnectionStateMachineStateChanged(newState);
}
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Handling state " + currentState.getClass().getSimpleName() + " ...");
}
currentState.handleState(stateMachineSession);
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "State " + currentState.getClass().getSimpleName() + " handled.");
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Notifying ConnectionStateMachineListeners onStateChanged() ...");
}
// inform the listeners
for (IBlaubotConnectionStateMachineListener connectionStateMachineListener : this.connectionStateMachineListeners) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Notifying listener " + connectionStateMachineListener);
}
if (sendStarted)
connectionStateMachineListener.onStateMachineStarted();
if (sendStopped)
connectionStateMachineListener.onStateMachineStopped();
connectionStateMachineListener.onStateChanged(oldState, newState);
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Done notifying listener " + connectionStateMachineListener);
}
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Done notifying onStateChanged() ...");
}
}
/**
* @param currentState
* @param nextState
*/
private static void assertStateChange(IBlaubotState currentState, IBlaubotState nextState) {
State cur = State.getStateByStatemachineClass(currentState.getClass());
State to = State.getStateByStatemachineClass(nextState.getClass());
if(!cur.isStateChangeAllowed(to)) {
throw new IllegalStateException("A state change from " + cur + " to " + to + " is not allowed.");
}
}
/**
* Registers a listener to get informed about state changes of the state machine
* @param connectionStateMachineListener the listener to add
*/
public void addConnectionStateMachineListener(IBlaubotConnectionStateMachineListener connectionStateMachineListener) {
this.connectionStateMachineListeners.add(connectionStateMachineListener);
}
/**
* Removes a previously added listener
* @param connectionStateMachineListener the listener to remove
*/
public void removeConnectionStateMachineListener(IBlaubotConnectionStateMachineListener connectionStateMachineListener) {
this.connectionStateMachineListeners.remove(connectionStateMachineListener);
}
public List getConnectionAcceptors() {
return acceptors;
}
public BlaubotBeaconService getBeaconService() {
return beaconService;
}
private IBlaubotConnectionManagerListener connectionListener = new IBlaubotConnectionManagerListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "[Current state: " + currentState + "] Connection down: " + connection + ". Pushing as CSM event to queue.");
}
ConnectionClosedStateMachineEvent event = new ConnectionClosedStateMachineEvent(connection);
event.setConnectionStateMachineState(currentState);
pushStateMachineEvent(event);
}
@Override
public void onConnectionEstablished(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "[Current state: " + currentState + "] Got new connection: " + connection + ". Pushing as CSM event to queue.");
}
ConnectionEstablishedStateMachineEvent event = new ConnectionEstablishedStateMachineEvent(connection);
event.setConnectionStateMachineState(currentState);
pushStateMachineEvent(event);
}
};
private IBlaubotAdminMessageListener adminMessageChannelListener = new IBlaubotAdminMessageListener() {
@Override
public void onAdminMessage(AbstractAdminMessage adminMessage) {
if (adminMessage instanceof RelayAdminMessage) {
// don't process relay messages
return;
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "[Current state: " + currentState + "] Got admin message: " + adminMessage + ". Pushing as CSM event to queue.");
}
pushStateMachineEvent(new AdminMessageStateMachineEvent(currentState, adminMessage));
}
};
public IBlaubotState getCurrentState() {
return this.currentState;
}
public boolean isStateMachineStarted() {
return !(currentState instanceof StoppedState);
}
private boolean isEventDispatcherRunning() {
synchronized (eventDispatcherLock) {
return this.stateMachineEventDispatcher != null && this.stateMachineEventDispatcher.isAlive();
}
}
/**
* Dispatches events from the eventQueue to the current state. (UI-Thread alike)
*
* @author Henning Gross
*
*/
class StateMachineEventDispatcher extends Thread {
private static final String LOG_TAG = "StateMachineEventDispatcher";
/**
* Max time the processing of an event may take. If it takes longer,
* an exception will be thrown.
*/
private static final int MAX_EVENT_PROCESSING_TIME = 60000; // ms
private Timer processingTimeoutTimer;
public StateMachineEventDispatcher() {
setName("csm-event-dispatcher");
}
private void handleState(IBlaubotState state) {
changeState(state);
}
/**
* Starts a timer that will log warnings, if the ConnectionStateMachine takes too long
* to process an event.
* @param event the event that took too long to be processed
*/
private void startTimer(final AbstractBlaubotStateMachineEvent event) {
processingTimeoutTimer = new Timer();
final TimerTask timerTask = new TimerTask() {
@Override
public void run() {
final String message = " [curState: " + currentState + "] The processing of " + event + " took longer than " + MAX_EVENT_PROCESSING_TIME + " ms";
if (Log.logWarningMessages()) {
Log.e(LOG_TAG, message);
}
// throw new RuntimeException(new TimeoutException(message));
}
};
processingTimeoutTimer.schedule(timerTask, MAX_EVENT_PROCESSING_TIME);
}
/**
* cancels the timer
*/
private void cancelTimer() {
if (processingTimeoutTimer != null) {
processingTimeoutTimer.cancel();
processingTimeoutTimer = null;
}
}
@Override
public void run() {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "StateMachineEventDispatcher started.");
}
while(!isInterrupted() && Thread.currentThread() == stateMachineEventDispatcher) {
try {
AbstractBlaubotStateMachineEvent event = stateMachineEventQueue.take();
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "[curState: "+ currentState +"] CSM EventQueue (size=" + stateMachineEventQueue.size() + ") took: " + event);
}
// start the timeout timer
startTimer(event);
// we measure the processing time
long startTime = System.currentTimeMillis();
if(event instanceof AdminMessageStateMachineEvent) {
AbstractAdminMessage aam = ((AdminMessageStateMachineEvent)event).getAdminMessage();
IBlaubotState state = currentState.onAdminMessage(aam);
handleState(state);
} else if(event instanceof ConnectionClosedStateMachineEvent) {
IBlaubotState state = currentState.onConnectionClosed(((ConnectionClosedStateMachineEvent) event).getConnection());
handleState(state);
} else if(event instanceof ConnectionEstablishedStateMachineEvent) {
IBlaubotState state = currentState.onConnectionEstablished(((ConnectionEstablishedStateMachineEvent) event).getConnection());
handleState(state);
} else if(event instanceof AbstractBlaubotDeviceDiscoveryEvent) {
// filter discovery events that discover ourselves
if(!stateMachineSession.getOwnDevice().getUniqueDeviceID().equals(((AbstractBlaubotDeviceDiscoveryEvent) event).getRemoteDevice().getUniqueDeviceID())) {
IBlaubotState state = currentState.onDeviceDiscoveryEvent((AbstractBlaubotDeviceDiscoveryEvent) event);
handleState(state);
}
} else if (event instanceof AbstractTimeoutStateMachineEvent) {
IBlaubotState state = currentState.onTimeoutEvent((AbstractTimeoutStateMachineEvent) event);
handleState(state);
} else if(event instanceof StopStateMachineEvent) {
_stopStateMachine();
} else if(event instanceof StartStateMachineEvent) {
_startStateMachine();
} else {
throw new RuntimeException("Unknown event in event queue!");
}
// stop timeout timer
cancelTimer();
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Event processing took " + (System.currentTimeMillis() - startTime) + " ms");
}
} catch (InterruptedException e) {
break;
}
}
cancelTimer();
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "StateMachineEventDispatcher stopped.");
}
}
}
/**
* Pushes a AbstractBlaubotStateMachineEvent to the StateMachine's event queue.
* @param stateMachineEvent the event
*/
public void pushStateMachineEvent(AbstractBlaubotStateMachineEvent stateMachineEvent) {
try {
stateMachineEventQueue.put(stateMachineEvent);
} catch (InterruptedException e) {
// ignore
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy