@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
205 lines (171 loc) • 5.58 kB
JavaScript
"use strict";
/* jshint ignore:start */
var EventEmitter = require("events").EventEmitter;
var utils = require("util");
module.exports = StateMaschine;
/**
* Contructor. Create the StateMaschine object
*
*@param {object} context The state maschine context with states
*@param {object} externalContext The external (i.e. Xsodata context)
*/
function StateMaschine(context, externalContext) {
EventEmitter.call(this);
this._context = context;
this._currentState = null;
this._isFinal = false;
this._statesHistory = [];
this._externalContext = externalContext;
}
utils.inherits(StateMaschine, EventEmitter);
/**
* Handles any error. This method also emits the error event via eventemitter
*
@event error {error} The error occured
*/
function handleError(error) {
this.emit("error", error);
}
/**
* Sets the current state of the state maschine
*
*@param {string} stateName Name of state to set
*/
function setCurrentState(stateName) {
var state = this._context.states[stateName];
if (!state) {
throw new Error("State '" + stateName + "' does not exist");
}
this._currentState = {
name: stateName,
context: state
};
}
/**
* Builds the internal state source. This is the first parameter of each state function
* (i.e. action()) called. The state source is the previous state of the current state.
*
*@return {object} The current state source
*/
function buildCurrentStateSource() {
if (this._statesHistory.length === 0) {
return null;
}
return {
name: this._statesHistory[this._statesHistory.length - 1].name
};
}
/**
* Returns the external context. This is the object which was provided as the second parameter
* to the contructor function. With this method the external context can be accessed via api
* function though this.getExternalContext() inside any of state function like action()
*
*@return {any} The external context
*/
StateMaschine.prototype.getExternalContext = function getExternalContext() {
return this._externalContext;
};
/**
* Returns the current state of the state maschine
*
*@return {object} The current state
*/
StateMaschine.prototype.getCurrentState = function getCurrentState() {
return this._currentState;
};
/**
* Returns a list of previous states which had been passed before the current state.
*
*@return {array} List of previous states
*/
StateMaschine.prototype.getHistory = function getHistory() {
return this._statesHistory;
};
/**
* Resets the state maschine to state it hed before initialize() method was called.
*/
StateMaschine.prototype.reset = function reset() {
this._statesHistory = [];
this._currentState = null;
this._isFinal = false;
};
/**
* Starts the state maschine to run and process it's states.
*/
StateMaschine.prototype.initialize = function initialize() {
this.next(this._context.initializeWith);
};
/**
* Returns true if the state maschine is in final state. State maschine get's into final state
* if either this.setFinal(true) is called inside a states function or a state has a property
* isFinal == true.
*/
StateMaschine.prototype.isFinal = function isFinal() {
return this._isFinal;
};
/**
* Sets the state maschine to final state. This emits also a 'final' event. When state maschine is
* in final state all calls regarding to state changing will be irgnored.
*
*@event final
*/
StateMaschine.prototype.setFinal = function setFinal() {
this.emit("final");
this._isFinal = true;
};
/**
* Triggers the state maschine to do a transistion from one state to another state provided by
* parameter stateName. If second agrument is provided all states functions of the next state will
* recieve this argument as a second parameter.
*
*@param {string} stateName The name of the next state to transit into
*@param {any} arg1 Any argument which the next state function should recieve
*/
StateMaschine.prototype.next = function next(stateName, arg1) {
var resetCurrentState = false;
var currentStateTemp = this._currentState;
this._arg1 = arg1;
if (currentStateTemp && currentStateTemp.context.isFinal === true) {
this.setFinal(true);
}
if (this.isFinal() === true) {
return;
}
try {
if (!stateName) {
throw new Error("You can not call .next() without any state target");
}
buildCurrentStateSource.call(this);
if (!this._currentState) {
// We are in initial state
// Be carefull with refactoring because of recusion
setCurrentState.call(this, stateName);
} else {
setCurrentState.call(this, stateName);
this.getHistory().push(currentStateTemp);
resetCurrentState = true;
}
} catch (error) {
if (resetCurrentState === true) {
var lastState = this.getHistory().pop();
setCurrentState.call(this, lastState.name);
}
return handleError.call(this, error);
}
this.process();
};
/**
* Starts to process a transition to the next state. This is done by calling the action() method
* of the current states context.
*/
StateMaschine.prototype.process = function process() {
try {
if (this._currentState.context.action) {
this._currentState.context.action.call(this, buildCurrentStateSource.call(this),
this._arg1);
}
} catch (error) {
return handleError.call(this, error);
}
};
/* jshint ignore:end */