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.
357 lines (340 loc) • 13.1 kB
JavaScript
"use strict";
let constants = require("../common/constants");
let specStrings = require("../common/specStrings");
let _ = require("lodash");
let is = require("../common/is");
let ScopeNode = require("./scopeNode");
let errors = require("../common/errors");
let converters = require("../common/converters");
let Serializer = require("backpack-node").system.Serializer;
let arrayHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
let ser = null;
if (!serializer) {
ser = new Serializer(); // It should get serialized internally.
}
if (_.isArray(propValue)) {
let stuff = [];
for (let pv of propValue) {
if (is.activity(pv)) {
stuff.push(specStrings.hosting.createActivityInstancePart(pv.instanceId));
}
else {
if (!serializer) {
stuff.push(ser.toJSON(pv));
}
else {
stuff.push(pv);
}
}
}
result.name = propName;
result.value = stuff;
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
let ser = null;
if (!serializer) {
ser = new Serializer(); // It should get serialized internally.
}
if (_.isArray(part.value)) {
let scopePartValue = [];
for (let pv of part.value) {
let activityId = specStrings.hosting.getInstanceId(pv);
if (activityId) {
scopePartValue.push(getActivityById(activityId));
}
else {
if (!serializer) {
scopePartValue.push(ser.fromJSON(pv));
}
else {
scopePartValue.push(pv);
}
}
}
result.value = scopePartValue;
return true;
}
return false;
}
};
let activityHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
if (is.activity(propValue)) {
result.name = propName;
result.value = specStrings.hosting.createActivityInstancePart(propValue.instanceId);
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
let activityId = specStrings.hosting.getInstanceId(part.value);
if (activityId) {
result.value = getActivityById(activityId);
return true;
}
return false;
}
};
let parentHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
if (propValue && propValue.__marker === constants.markers.$parent) {
result.name = propName;
result.value = {
$type: constants.markers.$parent,
id: propValue.$activity.instanceId
};
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
return false;
}
};
let activityPropHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
if (_.isFunction(propValue) && !activity.hasOwnProperty(propName) &&
_.isFunction(activity[propName])) {
result.value = specStrings.hosting.createActivityPropertyPart(propName);
return true;
}
else if (_.isObject(propValue) && propValue === activity[propName]) {
result.value = specStrings.hosting.createActivityPropertyPart(propName);
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
let activityProperty = specStrings.hosting.getActivityPropertyName(part);
if (activityProperty) {
if (_.isUndefined(activity[activityProperty])) {
throw new errors.ActivityRuntimeError("Activity has no property '" + part + "'.");
}
result.name = activityProperty;
result.value = activity[activityProperty];
return true;
}
return false;
}
};
let errorInstanceHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
if (propValue instanceof Error) {
result.name = propName;
result.value = {
type: constants.types.error,
name: propValue.name,
stack: propValue.stack
};
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
if (part.value && part.value.type === constants.types.error) {
let errorName = part.value.name;
let ErrorConstructor = global[errorName];
if (_.isFunction(ErrorConstructor)) {
result.value = new ErrorConstructor(part.value.stack);
}
else {
result.value = new Error(`Error: ${errorName} Stack: ${part.value.stack}`);
}
return true;
}
return false;
}
};
let objectHandler = {
serialize: function (serializer, activity, execContext, getActivityById, propName, propValue, result) {
if (serializer) {
return false; // it's handled externally.
}
if (propName === "__schedulingState") {
result.name = propName;
result.value = _.clone(propValue);
result.value.indices = converters.mapToArray(propValue.indices);
result.value.$type = constants.types.schedulingState;
return true;
}
if (_.isDate(propValue)) {
result.name = propName;
result.value = {
time: propValue.getTime(),
$type: constants.types.date
};
return true;
}
if (propValue instanceof Map) {
result.name = propName;
result.value = {
data: converters.mapToArray(propValue),
$type: constants.types.map
};
return true;
}
if (propValue instanceof Set) {
result.name = propName;
result.value = {
data: converters.setToArray(propValue),
$type: constants.types.set
};
return true;
}
if (propValue instanceof RegExp) {
result.name = propName;
result.value = {
pattern: propValue.pattern,
flags: propValue.flags,
$type: constants.types.rex
};
return true;
}
if (_.isPlainObject(propValue)) {
result.name = propName;
result.value = {
data: new Serializer().toJSON(propValue),
$type: constants.types.object
};
return true;
}
return false;
},
deserialize: function (serializer, activity, getActivityById, part, result) {
if (part.value) {
if (part.value.$type === constants.types.schedulingState) {
result.value = _.clone(part.value);
result.value.indices = converters.arrayToMap(part.value.indices);
delete result.value.$type;
return true;
}
if (part.value.$type === constants.types.date) {
result.value = new Date(part.value.time);
return true;
}
if (part.value.$type === constants.types.map) {
result.value = converters.arrayToMap(part.value.data);
return true;
}
if (part.value.$type === constants.types.set) {
result.value = converters.arrayToSet(part.value.data);
return true;
}
if (part.value.$type === constants.types.rex) {
result.value = new RegExp(part.value.pattern, part.value.flags);
return true;
}
if (part.value.$type === constants.types.object) {
result.value = new Serializer().fromJSON(part.value.data);
return true;
}
}
return false;
}
};
let scopeSerializer = {
handlers: [],
installHandler: function (handler) {
this.handlers.push(handler);
},
serialize: function (execContext, getActivityById, enablePromotions, nodes, serializer) {
let state = [];
let promotedProperties = enablePromotions ? new Map() : null;
for (let node of nodes) {
if (node.instanceId === constants.ids.initialScope) {
continue;
}
let item = {
instanceId: node.instanceId,
userId: node.userId,
parentId: node.parent ? node.parent.instanceId : null,
parts: []
};
let activity = getActivityById(node.instanceId);
for (let prop of node.properties()) {
if (!activity.nonSerializedProperties.has(prop.name)) {
let done = false;
for (let handler of this.handlers) {
let result = { name: null, value: null };
if (handler.serialize(serializer, activity, execContext, getActivityById, prop.name, prop.value, result)) {
if (result.name) {
item.parts.push({
name: prop.name,
value: result.value
});
}
else {
item.parts.push(result.value);
}
done = true;
break;
}
}
if (!done) {
item.parts.push({
name: prop.name,
value: prop.value
});
}
}
}
state.push(item);
// Promotions:
if (promotedProperties && activity.promotedProperties) {
for (let promotedPropName of activity.promotedProperties) {
let pv = node.getPropertyValue(promotedPropName, true);
if (!_.isUndefined(pv) && !(is.activity(pv))) {
let promotedEntry = promotedProperties.get(promotedPropName);
// If an Activity Id greater than other, then we can sure that other below or after in the tree.
if (_.isUndefined(promotedEntry) || node.instanceId > promotedEntry.level) {
promotedProperties.set(promotedPropName, { level: node.instanceId, value: pv });
}
}
}
}
}
let actualPromotions = null;
if (promotedProperties) {
actualPromotions = {};
for (let kvp of promotedProperties.entries()) {
actualPromotions[kvp[0]] = kvp[1].value;
}
}
return {
state: state,
promotedProperties: actualPromotions
};
},
deserializeNodes: function* (getActivityById, json, serializer) {
for (let item of json) {
let scopePart = {};
let activity = getActivityById(item.instanceId);
for (let part of item.parts) {
let done = false;
for (let handler of this.handlers) {
let result = { name: null, value: null };
if (handler.deserialize(serializer, activity, getActivityById, part, result)) {
scopePart[result.name || part.name] = result.value;
done = true;
break;
}
}
if (!done) {
scopePart[part.name] = part.value;
}
}
yield new ScopeNode(item.instanceId, scopePart, item.userId, activity);
}
}
};
scopeSerializer.installHandler(arrayHandler);
scopeSerializer.installHandler(activityHandler);
scopeSerializer.installHandler(parentHandler);
scopeSerializer.installHandler(objectHandler);
scopeSerializer.installHandler(activityPropHandler);
scopeSerializer.installHandler(errorInstanceHandler);
module.exports = scopeSerializer;