UNPKG

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.

863 lines (745 loc) 31.2 kB
/*jshint -W054 */ "use strict"; var constants = require("../common/constants"); var errors = require("../common/errors"); var enums = require("../common/enums"); var _ = require("lodash"); var specStrings = require("../common/specStrings"); var util = require("util"); var is = require("../common/is"); var CallContext = require("./callContext"); var uuid = require('node-uuid'); var async = require("../common/asyncHelpers").async; var assert = require("better-assert"); var debug = require("debug")("wf4node:Activity"); var common = require("../common"); var SimpleProxy = common.SimpleProxy; function Activity() { this.args = null; this.displayName = null; this.id = uuid.v4(); this._instanceId = null; this._structureInitialized = false; this._scopeKeys = null; this._createScopePartImpl = null; this["@require"] = null; // Properties not serialized: this.nonSerializedProperties = new Set(); // Properties are not going to copied in the scope: this.nonScopedProperties = new Set(); this.nonScopedProperties.add("nonScopedProperties"); this.nonScopedProperties.add("nonSerializedProperties"); this.nonScopedProperties.add("arrayProperties"); this.nonScopedProperties.add("activity"); this.nonScopedProperties.add("id"); this.nonScopedProperties.add("_instanceId"); this.nonScopedProperties.add("args"); this.nonScopedProperties.add("displayName"); this.nonScopedProperties.add("complete"); this.nonScopedProperties.add("cancel"); this.nonScopedProperties.add("idle"); this.nonScopedProperties.add("fail"); this.nonScopedProperties.add("end"); this.nonScopedProperties.add("schedule"); this.nonScopedProperties.add("createBookmark"); this.nonScopedProperties.add("resumeBookmark"); this.nonScopedProperties.add("resultCollected"); this.nonScopedProperties.add("codeProperties"); this.nonScopedProperties.add("initializeStructure"); this.nonScopedProperties.add("_initializeStructure"); this.nonScopedProperties.add("_structureInitialized"); this.nonScopedProperties.add("clone"); this.nonScopedProperties.add("_scopeKeys"); this.nonScopedProperties.add("_createScopePartImpl"); this.nonScopedProperties.add("@require"); this.nonScopedProperties.add("initializeExec"); this.nonScopedProperties.add("unInitializeExec"); this.codeProperties = new Set(); this.arrayProperties = new Set(["args"]); } Object.defineProperties(Activity.prototype, { collectAll: { value: true, writable: false, enumerable: false }, instanceId: { enumerable: false, get: function get() { if (this._instanceId) { return this._instanceId; } throw new errors.ActivityRuntimeError("Activity is not initialized in a context."); }, set: function set(value) { this._instanceId = value; } } }); Activity.prototype.toString = function () { return (this.displayName ? this.displayName + " " : "") + "(" + this.constructor.name + ":" + this.id + ")"; }; /* forEach */ Activity.prototype.all = regeneratorRuntime.mark(function _callee(execContext) { return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: return _context.delegateYield(this._children(true, null, execContext, null), "t0", 1); case 1: case "end": return _context.stop(); } } }, _callee, this); }); Activity.prototype.children = regeneratorRuntime.mark(function _callee2(execContext) { return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: return _context2.delegateYield(this._children(true, this, execContext, null), "t0", 1); case 1: case "end": return _context2.stop(); } } }, _callee2, this); }); Activity.prototype.immediateChildren = regeneratorRuntime.mark(function _callee3(execContext) { return regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: return _context3.delegateYield(this._children(false, this, execContext), "t0", 1); case 1: case "end": return _context3.stop(); } } }, _callee3, this); }); Activity.prototype._children = regeneratorRuntime.mark(function _callee4(deep, except, execContext, visited) { var self, fieldName, fieldValue, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, obj; return regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: assert(execContext instanceof require("./activityExecutionContext"), "Cannot enumerate activities without an execution context."); visited = visited || new Set(); self = this; if (visited.has(self)) { _context4.next = 58; break; } visited.add(self); // Ensure it's structure created: this._initializeStructure(execContext); if (!(self !== except)) { _context4.next = 9; break; } _context4.next = 9; return self; case 9: _context4.t0 = regeneratorRuntime.keys(self); case 10: if ((_context4.t1 = _context4.t0()).done) { _context4.next = 58; break; } fieldName = _context4.t1.value; if (!self.hasOwnProperty(fieldName)) { _context4.next = 56; break; } fieldValue = self[fieldName]; if (!fieldValue) { _context4.next = 56; break; } if (!_.isArray(fieldValue)) { _context4.next = 49; break; } _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; _context4.prev = 19; _iterator = fieldValue[Symbol.iterator](); case 21: if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { _context4.next = 33; break; } obj = _step.value; if (!(obj instanceof Activity)) { _context4.next = 30; break; } if (!deep) { _context4.next = 28; break; } return _context4.delegateYield(obj._children(deep, except, execContext, visited), "t2", 26); case 26: _context4.next = 30; break; case 28: _context4.next = 30; return obj; case 30: _iteratorNormalCompletion = true; _context4.next = 21; break; case 33: _context4.next = 39; break; case 35: _context4.prev = 35; _context4.t3 = _context4["catch"](19); _didIteratorError = true; _iteratorError = _context4.t3; case 39: _context4.prev = 39; _context4.prev = 40; if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } case 42: _context4.prev = 42; if (!_didIteratorError) { _context4.next = 45; break; } throw _iteratorError; case 45: return _context4.finish(42); case 46: return _context4.finish(39); case 47: _context4.next = 56; break; case 49: if (!(fieldValue instanceof Activity)) { _context4.next = 56; break; } if (!deep) { _context4.next = 54; break; } return _context4.delegateYield(fieldValue._children(deep, except, execContext, visited), "t4", 52); case 52: _context4.next = 56; break; case 54: _context4.next = 56; return fieldValue; case 56: _context4.next = 10; break; case 58: case "end": return _context4.stop(); } } }, _callee4, this, [[19, 35, 39, 47], [40,, 42, 46]]); }); /* forEach */ /* Structure */ Activity.prototype.isArrayProperty = function (propName) { return this.arrayProperties.has(propName); }; Activity.prototype._initializeStructure = function (execContext) { if (!this._structureInitialized) { this.initializeStructure(execContext); this._structureInitialized = true; } }; Activity.prototype.initializeStructure = _.noop; Activity.prototype.clone = function () { function makeClone(value, canCloneArrays) { if (value instanceof Activity) { return value.clone(); } else if (value instanceof Set) { var newSet = new Set(); var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = value.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var item = _step2.value; newSet.add(item); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return newSet; } else if (_.isArray(value)) { if (canCloneArrays) { var newArray = []; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = value[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var item = _step3.value; newArray.push(makeClone(item, false)); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } return newArray; } else { return value; } } else { return value; } } var Constructor = this.constructor; var newInst = new Constructor(); for (var key in this) { if (this.hasOwnProperty(key)) { var value = this[key]; if (newInst[key] !== value) { newInst[key] = makeClone(value, true); } } } return newInst; }; /* RUN */ Activity.prototype.start = function (callContext) { if (!(callContext instanceof CallContext)) { throw new Error("Argument 'context' is not an instance of ActivityExecutionContext."); } var args = undefined; if (arguments.length > 1) { args = []; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); } } this._start(callContext, null, args); }; Activity.prototype._start = function (callContext, variables, args) { var self = this; if (_.isUndefined(args)) { args = this.args || []; } if (!_.isArray(args)) { args = [args]; } var myCallContext = callContext.next(self, variables); var state = myCallContext.executionState; if (state.isRunning) { throw new Error("Activity is already running."); } // We should allow IO operations to execute: setImmediate(function () { state.reportState(Activity.states.run, null, myCallContext.scope); try { self.initializeExec.call(myCallContext.scope); self.run.call(myCallContext.scope, myCallContext, args); } catch (e) { self.fail(myCallContext, e); } }); }; Activity.prototype.initializeExec = _.noop; Activity.prototype.unInitializeExec = _.noop; Activity.prototype.run = function (callContext, args) { callContext.activity.complete(callContext, args); }; Activity.prototype.complete = function (callContext, result) { this.end(callContext, Activity.states.complete, result); }; Activity.prototype.cancel = function (callContext) { this.end(callContext, Activity.states.cancel); }; Activity.prototype.idle = function (callContext) { this.end(callContext, Activity.states.idle); }; Activity.prototype.fail = function (callContext, e) { this.end(callContext, Activity.states.fail, e); }; Activity.prototype.end = function (callContext, reason, result) { try { this.unInitializeExec.call(callContext.scope, reason, result); } catch (e) { var message = "unInitializeExec failed. Reason of ending was '" + reason + "' and the result is '" + result + "."; reason = Activity.states.fail; result = e; } var state = callContext.executionState; if (state.execState === Activity.states.cancel || state.execState === Activity.states.fail) { // It was cancelled or failed: return; } state.execState = reason; var inIdle = reason === Activity.states.idle; var execContext = callContext.executionContext; var savedScope = callContext.scope; savedScope.update(SimpleProxy.updateMode.oneWay); callContext = callContext.back(inIdle); if (callContext) { try { var bmName = specStrings.activities.createValueCollectedBMName(this.instanceId); if (execContext.isBookmarkExists(bmName)) { execContext.resumeBookmarkInScope(callContext, bmName, reason, result).then(function () { state.emitState(result, savedScope); }, function (e) { state.emitState(result, savedScope); callContext.fail(e); }); return; } } catch (e) { callContext.fail(e); } } else { // We're on root, done. // If wf in idle, but there are internal bookmark resume request, // then instead of emitting done, we have to continue them. if (inIdle && execContext.processResumeBookmarkQueue()) { // We should not emmit idle event, because there was internal bookmark continutations, so we're done. return; } } state.emitState(result, savedScope); }; Activity.prototype._defaultEndCallback = function (callContext, reason, result) { callContext.end(reason, result); }; Activity.prototype.schedule = function (callContext, obj, endCallback) { var self = this; var scope = callContext.scope; var execContext = callContext.executionContext; var selfId = callContext.instanceId; if (!endCallback) { endCallback = "_defaultEndCallback"; } var invokeEndCallback = function invokeEndCallback(_reason, _result) { setImmediate(function () { scope[endCallback].call(scope, callContext, _reason, _result); }); }; if (!_.isString(endCallback)) { callContext.fail(new TypeError("Provided argument 'endCallback' value is not a string.")); return; } var cb = scope[endCallback]; if (!_.isFunction(cb)) { callContext.fail(new TypeError("'" + endCallback + "' is not a function.")); return; } if (scope.__schedulingState) { debug("%s: Error, already existsing state: %j", selfId, scope.__schedulingState); callContext.fail(new errors.ActivityStateExceptionError("There are already scheduled items exists.")); return; } debug("%s: Scheduling object(s) by using end callback '%s': %j", selfId, endCallback, obj); var state = { many: _.isArray(obj), indices: new Map(), results: [], total: 0, idleCount: 0, cancelCount: 0, completedCount: 0, endBookmarkName: null, endCallbackName: endCallback }; var bookmarkNames = []; try { (function () { var startedAny = false; var index = 0; var processValue = function processValue(value) { debug("%s: Checking value: %j", selfId, value); var activity = undefined, variables = null; if (value instanceof Activity) { activity = value; } else if (_.isObject(value) && value.activity instanceof Activity) { activity = value.activity; variables = _.isObject(value.variables) ? value.variables : null; } if (activity) { var instanceId = activity.instanceId; debug("%s: Value is an activity with instance id: %s", selfId, instanceId); if (state.indices.has(instanceId)) { throw new errors.ActivityStateExceptionError("Activity instance '" + instanceId + " has been scheduled already."); } debug("%s: Creating end bookmark, and starting it.", selfId); bookmarkNames.push(execContext.createBookmark(selfId, specStrings.activities.createValueCollectedBMName(instanceId), "resultCollected")); activity._start(callContext, variables); startedAny = true; state.indices.set(instanceId, index); state.results.push(null); state.total++; } else { debug("%s: Value is not an activity.", selfId); state.results.push(value); } }; if (state.many) { debug("%s: There are many values, iterating.", selfId); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = obj[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var value = _step4.value; processValue(value); index++; } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } else { processValue(obj); } if (!startedAny) { debug("%s: No activity has been started, calling end callback with original object.", selfId); var result = state.many ? state.results : state.results[0]; invokeEndCallback(Activity.states.complete, result); } else { debug("%s: %d activities has been started. Registering end bookmark.", selfId, state.indices.size); var endBM = specStrings.activities.createCollectingCompletedBMName(selfId); bookmarkNames.push(execContext.createBookmark(selfId, endBM, endCallback)); state.endBookmarkName = endBM; scope.__schedulingState = state; } scope.update(SimpleProxy.updateMode.oneWay); })(); } catch (e) { debug("%s: Runtime error happened: %s", selfId, e.stack); if (bookmarkNames.length) { debug("%s: Set bookmarks to noop: $j", selfId, bookmarkNames); execContext.noopCallbacks(bookmarkNames); } scope.delete("__schedulingState"); debug("%s: Invoking end callback with the error.", selfId); invokeEndCallback(Activity.states.fail, e); } finally { debug("%s: Final state indices count: %d, total: %d", selfId, state.indices.size, state.total); } }; Activity.prototype.resultCollected = function (callContext, reason, result, bookmark) { var selfId = callContext.instanceId; var execContext = callContext.executionContext; var childId = specStrings.getString(bookmark.name); debug("%s: Scheduling result item collected, childId: %s, reason: %s, result: %j, bookmark: %j", selfId, childId, reason, result, bookmark); var finished = null; var state = this.__schedulingState; var fail = false; try { if (!_.isObject(state)) { throw new errors.ActivityStateExceptionError("Value of __schedulingState is '" + state + "'."); } var index = state.indices.get(childId); if (_.isUndefined(index)) { throw new errors.ActivityStateExceptionError("Child activity of '" + childId + "' scheduling state index out of range."); } debug("%s: Finished child activity id is: %s", selfId, childId); switch (reason) { case Activity.states.complete: debug("%s: Setting %d. value to result: %j", selfId, index, result); state.results[index] = result; debug("%s: Removing id from state.", selfId); state.indices.delete(childId); state.completedCount++; break; case Activity.states.fail: debug("%s: Failed with: %s", selfId, result.stack); fail = true; state.indices.delete(childId); break; case Activity.states.cancel: debug("%s: Incrementing cancel counter.", selfId); state.cancelCount++; debug("%s: Removing id from state.", selfId); state.indices.delete(childId); break; case Activity.states.idle: debug("%s: Incrementing idle counter.", selfId); state.idleCount++; break; default: throw new errors.ActivityStateExceptionError("Result collected with unknown reason '" + reason + "'."); } debug("%s: State so far = total: %s, indices count: %d, completed count: %d, cancel count: %d, error count: %d, idle count: %d", selfId, state.total, state.indices.size, state.completedCount, state.cancelCount, state.idleCount); var endWithNoCollectAll = !callContext.activity.collectAll && reason !== Activity.states.idle; if (endWithNoCollectAll || fail) { if (!fail) { debug("%s: ---- Collecting of values ended, because we're not collecting all values (eg.: Pick).", selfId); } else { debug("%s: ---- Collecting of values ended, because of an error.", selfId); } debug("%s: Shutting down %d other, running acitvities.", selfId, state.indices.size); var ids = []; var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = state.indices.keys()[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var id = _step5.value; ids.push(id); debug("%s: Deleting scope of activity: %s", selfId, id); execContext.deleteScopeOfActivity(callContext, id); var ibmName = specStrings.activities.createValueCollectedBMName(id); debug("%s: Deleting value collected bookmark: %s", selfId, ibmName); execContext.deleteBookmark(ibmName); } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } execContext.cancelExecution(this, ids); debug("%s: Activities cancelled: %j", selfId, ids); debug("%s: Reporting the actual reason: %s and result: %j", selfId, reason, result); finished = function () { execContext.resumeBookmarkInScope(callContext, state.endBookmarkName, reason, result); }; } else { assert(!fail); var onEnd = state.indices.size - state.idleCount === 0; if (onEnd) { debug("%s: ---- Collecting of values ended (ended because of collect all is off: %s).", selfId, endWithNoCollectAll); if (state.cancelCount) { debug("%s: Collecting has been cancelled, resuming end bookmarks.", selfId); finished = function () { execContext.resumeBookmarkInScope(callContext, state.endBookmarkName, Activity.states.cancel); }; } else if (state.idleCount) { debug("%s: This entry has been gone to idle, propagating counter.", selfId); state.idleCount--; // Because the next call will wake up a thread. execContext.resumeBookmarkInScope(callContext, state.endBookmarkName, Activity.states.idle); } else { result = state.many ? state.results : state.results[0]; debug("%s: This entry has been completed, resuming collect bookmark with the result(s): %j", selfId, result); finished = function () { execContext.resumeBookmarkInScope(callContext, state.endBookmarkName, Activity.states.complete, result); }; } } } } catch (e) { callContext.fail(e); this.delete("__schedulingState"); } finally { if (finished) { debug("%s: Schduling finished, removing state.", selfId); this.delete("__schedulingState"); finished(); } } }; /* RUN */ /* SCOPE */ Activity.prototype._getScopeKeys = function () { var self = this; if (!self._scopeKeys || !self._structureInitialized) { self._scopeKeys = []; for (var key in self) { if (!self.nonScopedProperties.has(key) && (_.isUndefined(Activity.prototype[key]) || key === "_defaultEndCallback" || key === "_subActivitiesGot")) { self._scopeKeys.push(key); } } } return self._scopeKeys; }; Activity.prototype.createScopePart = function () { if (!this._structureInitialized) { throw new errors.ActivityRuntimeError("Cannot create activity scope for uninitialized activities."); } if (this._createScopePartImpl === null) { var first = true; var src = "return {"; var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = this._getScopeKeys()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var _fieldName = _step6.value; if (first) { first = false; } else { src += ",\n"; } src += _fieldName + ":a." + _fieldName; } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } src += "}"; try { this._createScopePartImpl = new Function("a,_", src); } catch (e) { debug("Invalid scope part function:%s", src); throw e; } } return this._createScopePartImpl(this, _); }; /* SCOPE */ Activity.states = enums.activityStates; module.exports = Activity; //# sourceMappingURL=activity.js.map