workflow-4-node
Version:
Workflow 4 Node is a .NET Workflow Foundation like framework for Node.js. The goal is to reach feature equivalence and beyond.
503 lines (442 loc) • 16.9 kB
JavaScript
"use strict";
var ActivityExecutionState = require("./activityExecutionState");
var ResumeBookmarkQueue = require("./resumeBookmarkQueue");
var enums = require("../common/enums");
var errors = require("../common/errors");
var util = require("util");
var EventEmitter = require("events").EventEmitter;
var _ = require("lodash");
var constants = require("../common/constants");
var ScopeTree = require("./scopeTree");
var is = require("../common/is");
var CallContext = require("./callContext");
var assert = require("better-assert");
var Bluebird = require("bluebird");
var converters = require("../common/converters");
function ActivityExecutionContext(engine) {
EventEmitter.call(this);
this._activityStates = new Map();
this._bookmarks = new Map();
this._resumeBMQueue = new ResumeBookmarkQueue();
this.rootActivity = null;
this._knownActivities = new Map();
this._scopeTree = this._createScopeTree();
this.engine = engine; // Could be null in special cases, see workflowRegistry.js
}
util.inherits(ActivityExecutionContext, EventEmitter);
Object.defineProperties(ActivityExecutionContext.prototype, {
scope: {
get: function get() {
return this._scopeTree.currentScope;
}
},
hasScope: {
get: function get() {
return !this._scopeTree.isOnInitial;
}
}
});
ActivityExecutionContext.prototype._createScopeTree = function () {
var self = this;
return new ScopeTree({
resultCollected: function resultCollected(context, reason, result, bookmarkName) {
context.activity.resultCollected.call(context.scope, context, reason, result, bookmarkName);
}
}, function (id) {
return self._getKnownActivity(id);
});
};
ActivityExecutionContext.prototype.initialize = function (rootActivity) {
if (this.rootActivity) {
throw new Error("Context is already initialized.");
}
if (!is.activity(rootActivity)) {
throw new TypeError("Argument 'rootActivity' value is not an activity.");
}
this.rootActivity = rootActivity;
this._initialize(null, rootActivity, { instanceId: 0 });
};
ActivityExecutionContext.prototype._checkInit = function () {
if (!this.rootActivity) {
throw new Error("Context is not initialized.");
}
};
ActivityExecutionContext.prototype._initialize = function (parent, activity, idCounter) {
var activityId = activity._instanceId;
var nextId = (idCounter.instanceId++).toString();
if (!activityId) {
activityId = nextId;
activity.instanceId = activityId;
} else if (activityId !== nextId) {
throw new errors.ActivityRuntimeError("Activity " + activity + " has been assigned to an other position.");
}
var state = this.getExecutionState(activityId);
state.parentInstanceId = parent ? parent.instanceId : null;
this._knownActivities.set(activityId, activity);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = activity.immediateChildren(this)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var child = _step.value;
this._initialize(activity, child, idCounter);
state.childInstanceIds.add(child.instanceId);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
};
ActivityExecutionContext.prototype.getExecutionState = function (idOrActivity) {
var self = this;
var id = undefined;
if (_.isString(idOrActivity)) {
id = idOrActivity;
} else if (is.activity(idOrActivity)) {
id = idOrActivity.instanceId;
} else {
throw new TypeError("Cannot get state of " + idOrActivity);
}
var state = self._activityStates.get(id);
if (_.isUndefined(state)) {
state = new ActivityExecutionState(id);
state.on(enums.activityStates.run, function (args) {
self.emit(enums.activityStates.run, args);
});
state.on(enums.activityStates.end, function (args) {
self.emit(enums.activityStates.end, args);
});
self._activityStates.set(id, state);
}
return state;
};
ActivityExecutionContext.prototype._getKnownActivity = function (activityId) {
var activity = this._knownActivities.get(activityId);
if (!activity) {
throw new errors.ActivityRuntimeError("Activity by id '" + activityId + "' not found.");
}
return activity;
};
ActivityExecutionContext.prototype.createBookmark = function (activityId, name, endCallback) {
this.registerBookmark({
name: name,
instanceId: activityId,
timestamp: new Date().getTime(),
endCallback: endCallback
});
return name;
};
ActivityExecutionContext.prototype.registerBookmark = function (bookmark) {
var bm = this._bookmarks.get(bookmark.name);
if (bm) {
throw new errors.ActivityRuntimeError("Bookmark '" + bookmark.name + "' already exists.");
}
this._bookmarks.set(bookmark.name, bookmark);
};
ActivityExecutionContext.prototype.isBookmarkExists = function (name) {
return this._bookmarks.has(name);
};
ActivityExecutionContext.prototype.getBookmarkTimestamp = function (name, throwIfNotFound) {
var bm = this._bookmarks.get(name);
if (_.isUndefined(bm) && throwIfNotFound) {
throw new Error("Bookmark '" + name + "' not found.");
}
return bm ? bm.timestamp : null;
};
ActivityExecutionContext.prototype.deleteBookmark = function (name) {
this._bookmarks.delete(name);
};
ActivityExecutionContext.prototype.noopCallbacks = function (bookmarkNames) {
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = bookmarkNames[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var name = _step2.value;
var bm = this._bookmarks.get(name);
if (bm) {
bm.endCallback = _.noop;
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
};
ActivityExecutionContext.prototype.resumeBookmarkInScope = function (callContext, name, reason, result) {
var bm = this._bookmarks.get(name);
if (_.isUndefined(bm)) {
throw new Error("Bookmark '" + name + "' doesn't exists. Cannot continue with reason: " + reason + ".");
}
var self = this;
return new Bluebird(function (resolve, reject) {
setImmediate(function () {
try {
bm = self._bookmarks.get(name);
if (bm) {
// If bm is still exists.
self._doResumeBookmark(callContext, bm, reason, result, reason === enums.activityStates.idle);
resolve(true);
}
resolve(false);
} catch (e) {
reject(e);
}
});
});
};
ActivityExecutionContext.prototype.resumeBookmarkInternal = function (callContext, name, reason, result) {
var bm = this._bookmarks.get(name);
this._resumeBMQueue.enqueue(name, reason, result);
};
ActivityExecutionContext.prototype.resumeBookmarkExternal = function (name, reason, result) {
var self = this;
var bm = self._bookmarks.get(name);
if (!bm) {
throw new errors.BookmarkNotFoundError("Internal resume bookmark request cannot be processed because bookmark '" + name + "' doesn't exists.");
}
self._doResumeBookmark(new CallContext(this, bm.instanceId), bm, reason, result);
};
ActivityExecutionContext.prototype.processResumeBookmarkQueue = function () {
var self = this;
var command = self._resumeBMQueue.dequeue();
if (command) {
var bm = self._bookmarks.get(command.name);
if (!bm) {
throw new errors.BookmarkNotFoundError("Internal resume bookmark request cannot be processed because bookmark '" + command.name + "' doesn't exists.");
}
self._doResumeBookmark(new CallContext(this, bm.instanceId), bm, command.reason, command.result);
return true;
}
return false;
};
ActivityExecutionContext.prototype._doResumeBookmark = function (callContext, bookmark, reason, result, noRemove) {
var scope = callContext.scope;
if (!noRemove) {
this._bookmarks.delete(bookmark.name);
}
var cb = bookmark.endCallback;
if (_.isString(cb)) {
cb = scope[bookmark.endCallback];
if (!_.isFunction(cb)) {
cb = null;
}
}
if (!cb) {
throw new errors.ActivityRuntimeError("Bookmark's '" + bookmark.name + "' callback '" + bookmark.endCallback + "' is not defined on the current scope.");
}
// TODO: if it fails, resume on default callback with the error!
cb.call(scope, callContext, reason, result, bookmark);
};
ActivityExecutionContext.prototype.cancelExecution = function (scope, activityIds) {
var self = this;
var allIds = new Set();
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = activityIds[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var id = _step3.value;
self._cancelSubtree(scope, allIds, id);
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = self._bookmarks.values()[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
var bm = _step4.value;
if (allIds.has(bm.instanceId)) {
self._bookmarks.delete(bm.name);
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
};
ActivityExecutionContext.prototype._cancelSubtree = function (scope, allIds, activityId) {
var self = this;
allIds.add(activityId);
var state = self.getExecutionState(activityId);
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = state.childInstanceIds.values()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var id = _step5.value;
self._cancelSubtree(scope, allIds, id);
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
state.reportState(enums.activityStates.cancel, null, scope);
};
ActivityExecutionContext.prototype.deleteScopeOfActivity = function (callContext, activityId) {
this._scopeTree.deleteScopePart(callContext.instanceId, activityId);
};
ActivityExecutionContext.prototype.emitWorkflowEvent = function (args) {
this.emit(enums.events.workflowEvent, args);
};
/* SERIALIZATION */
ActivityExecutionContext.prototype.getStateAndPromotions = function (serializer, enablePromotions) {
if (serializer && !_.isFunction(serializer.toJSON)) {
throw new TypeError("Argument 'serializer' is not a serializer.");
}
var activityStates = new Map();
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = this._activityStates.values()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
var s = _step6.value;
activityStates.set(s.instanceId, s.asJSON());
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
var scopeStateAndPromotions = this._scopeTree.getExecutionState(this, enablePromotions, serializer);
var serialized = undefined;
if (serializer) {
serialized = serializer.toJSON({
activityStates: activityStates,
bookmarks: this._bookmarks,
scope: scopeStateAndPromotions.state
});
} else {
serialized = {
activityStates: converters.mapToArray(activityStates),
bookmarks: converters.mapToArray(this._bookmarks),
scope: scopeStateAndPromotions.state
};
}
return {
state: serialized,
promotedProperties: scopeStateAndPromotions.promotedProperties
};
};
ActivityExecutionContext.prototype.setState = function (serializer, json) {
if (serializer && !_.isFunction(serializer.fromJSON)) {
throw new TypeError("Argument 'serializer' is not a serializer.");
}
if (!_.isObject(json)) {
throw new TypeError("Argument 'json' is not an object.");
}
if (serializer) {
json = serializer.fromJSON(json);
if (!(json.activityStates instanceof Map)) {
throw new TypeError("activityStates property value of argument 'json' is not an Map instance.");
}
if (!(json.bookmarks instanceof Map)) {
throw new TypeError("Bookmarks property value of argument 'json' is not an Map instance.");
}
} else {
if (!json.activityStates) {
throw new TypeError("activityStates property value of argument 'json' is not an object.");
}
if (!json.bookmarks) {
throw new TypeError("Bookmarks property value of argument 'json' is not an object.");
}
json = {
activityStates: converters.arrayToMap(json.activityStates),
bookmarks: converters.arrayToMap(json.bookmarks),
scope: json.scope
};
}
var _iteratorNormalCompletion7 = true;
var _didIteratorError7 = false;
var _iteratorError7 = undefined;
try {
for (var _iterator7 = this._activityStates.values()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
var s = _step7.value;
var stored = json.activityStates.get(s.instanceId);
if (_.isUndefined(stored)) {
throw new Error("Activity's of '" + s.instanceId + "' state not found.");
}
s.fromJSON(stored);
}
} catch (err) {
_didIteratorError7 = true;
_iteratorError7 = err;
} finally {
try {
if (!_iteratorNormalCompletion7 && _iterator7.return) {
_iterator7.return();
}
} finally {
if (_didIteratorError7) {
throw _iteratorError7;
}
}
}
this._bookmarks = json.bookmarks;
this._scopeTree.setState(json.scope, serializer);
};
/* SERIALIZATION */
module.exports = ActivityExecutionContext;
//# sourceMappingURL=activityExecutionContext.js.map