devebot
Version:
Nodejs Microservice Framework
371 lines (367 loc) • 12.1 kB
JavaScript
"use strict";
const Promise = require("bluebird");
const Injektor = require("injektor");
const lodash = require("lodash");
const chores = require("../utils/chores");
const constx = require("../utils/constx");
const blockRef = chores.getBlockRef(__filename);
/**
* The constructor for RunhookManager class.
*
* @constructor
* @param {Object} params - The parameters of the constructor.
* @param {Object} params.runhook - The parameters that sent to Runhooks
*/
function RunhookManager(params = {}) {
const loggingFactory = params.loggingFactory.branch(blockRef);
const L = loggingFactory.getLogger();
const T = loggingFactory.getTracer();
L && L.has("silly") && L.log("silly", T && T.add({
sandboxName: params.sandboxName
}).toMessage({
tags: [blockRef, "constructor-begin"],
text: " + constructor start in sandbox <{sandboxName}>"
}));
const runhookInstance = {
appName: params.appName,
appInfo: params.appInfo,
sandboxName: params.sandboxName,
sandboxConfig: params.sandboxConfig,
loggingFactory: params.loggingFactory,
service: params.injectedHandlers,
//@Deprecated, injectedServices
injectedServices: params.injectedHandlers
};
/**
* @param {Object} command
* @param {string} command.name - The name of the command/routine.
* @param {string} command.package - The package on which this command/routine belongs to.
* @param {string} command.requestId - The requestId.
*/
function buildRunhookInstance(command, runhookId) {
runhookId = runhookId || command.requestId;
const customized = {
loggingFactory: params.loggingFactory.branch(command.name, runhookId)
};
if (command.package && !chores.isSpecialBundle(command.package)) {
if (params.injectedServices && params.injectedServices[command.package]) {
customized.injectedServices = params.injectedServices[command.package];
}
}
return lodash.defaults(customized, runhookInstance);
}
// default: undefined ~ false
const predefinedContext = lodash.get(params, ["profileConfig", constx.ROUTINE.ROOT_KEY, "predefinedContext"]) === true;
const routineStore = new Injektor(chores.injektorOptions);
const routineMap = {};
function getRunhooks() {
return routineMap;
}
function getRunhook(command) {
if (!command || !command.name) {
return {
code: -1,
message: "command.name is undefined"
};
}
const fn = routineStore.suggestName(command.name);
if (fn == null || fn.length === 0) {
return {
code: -2,
message: "command.name not found"
};
}
if (fn.length >= 2) {
try {
return routineStore.lookup(command.name, {
scope: command.package
});
} catch (err) {
if (err.name === "DuplicatedRelativeNameError") {
return {
code: -3,
message: "command.name is duplicated"
};
} else {
return {
code: -9,
message: "unknown error"
};
}
}
}
return routineStore.lookup(fn[0]);
}
this.getDefinitions = function (defs) {
defs = defs || [];
lodash.forOwn(getRunhooks(), function (value, key) {
defs.push(lodash.assign({
package: value.crateScope,
name: value.name
}, value.object && value.object.info));
});
return defs;
};
this.getRunhook = function (command) {
return getRunhook(command);
};
this.isAvailable = function (command) {
return lodash.isFunction(getRunhook(command).handler);
};
this.execute = function (command, context) {
const self = this;
context = context || {};
command = command || {};
command.requestId = command.requestId || T.getLogID();
const reqTr = T.branch({
key: "requestId",
value: command.requestId
});
L && L.has("trace") && L.log("trace", reqTr.add({
commandName: command.name,
command
}).toMessage({
tags: [blockRef, "execute", "begin"],
text: "${commandName}#${requestId} - validate: {command}"
}));
const routine = getRunhook(command);
let validationError = null;
if (lodash.isEmpty(routine) || routine.code === -1) {
validationError = {
message: routine.message || "command.name is undefined"
};
}
if (routine.code === -2) {
validationError = {
name: command.name,
message: routine.message || "command.name not found"
};
}
if (routine.code === -3) {
validationError = {
name: command.name,
message: routine.message || "command.name is duplicated"
};
}
if (validationError) {
context.outlet && context.outlet.render("invalid", validationError);
return Promise.reject(validationError);
}
const payload = command.payload || command.data;
const schema = routine && routine.info && routine.info.schema;
if (schema && lodash.isObject(schema)) {
L && L.has("silly") && L.log("silly", reqTr.add({
commandName: command.name,
payload,
schema
}).toMessage({
tags: [blockRef, "execute", "validate-by-schema"],
text: "${commandName}#${requestId} - validate payload: {payload} by schema: {schema}"
}));
const result = params.schemaValidator.validate(payload, schema);
if (result.valid === false) {
validationError = {
message: "failed validation using schema",
schema: schema
};
}
}
const validate = routine && routine.info && routine.info.validate;
if (validate && lodash.isFunction(validate)) {
L && L.has("silly") && L.log("silly", reqTr.add({
commandName: command.name,
payload
}).toMessage({
tags: [blockRef, "execute", "validate-by-method"],
text: "${commandName}#${requestId} - validate payload: {payload} using validate()"
}));
if (!validate(payload)) {
validationError = {
message: "failed validation using validate() function"
};
}
}
if (validationError) {
L && L.has("error") && L.log("error", reqTr.add({
commandName: command.name,
validationError
}).toMessage({
tags: [blockRef, "execute", "validation-error"],
text: "${commandName}#${requestId} - validation error: {validationError}"
}));
context.outlet && context.outlet.render("failed", validationError);
return Promise.reject(validationError);
}
L && L.has("trace") && L.log("trace", reqTr.add({
commandName: command.name
}).toMessage({
tags: [blockRef, "execute", "enqueue"],
text: "${commandName}#${requestId} - processing"
}));
let promize = null;
const mode = routine.mode || command.mode;
if (mode !== "remote" || params.jobqueueBinder.enabled === false) {
const progressMeter = self.createProgressMeter({
progress: function (completed, total, data) {
const ok = lodash.isNumber(total) && total > 0 && lodash.isNumber(completed) && completed >= 0 && completed <= total;
const percent = ok ? total === 100 ? completed : lodash.round(completed * 100 / total) : -1;
context.outlet && context.outlet.render("progress", {
progress: percent,
data: data
});
}
});
promize = self.process(command, {
progressMeter: progressMeter
}).then(function (result) {
context.outlet && context.outlet.render("completed", result);
return Promise.resolve(result);
}).catch(function (errorMessage) {
context.outlet && context.outlet.render("failed", errorMessage);
return Promise.reject(errorMessage);
});
} else {
promize = params.jobqueueBinder.instance.enqueueJob(command).then(function (task) {
return new Promise(function (resolve, reject) {
task.on("started", function (info) {
context.outlet && context.outlet.render("started", info);
}).on("progress", function (info) {
context.outlet && context.outlet.render("progress", info);
}).on("timeout", function (info) {
context.outlet && context.outlet.render("timeout", info);
reject(_newError("JobHasTimeout", null, {
payload: {
state: "timeout",
data: info
}
}));
}).on("cancelled", function (info) {
context.outlet && context.outlet.render("cancelled", info);
reject(_newError("JobHasCancelled", null, {
payload: {
state: "cancelled",
data: info
}
}));
}).on("failed", function (error) {
context.outlet && context.outlet.render("failed", error);
reject(_newError("JobHasFailed", null, {
payload: {
state: "failed",
data: error
}
}));
}).on("completed", function (result) {
context.outlet && context.outlet.render("completed", result);
resolve(result);
});
});
});
}
return promize;
};
this.process = function (command, context) {
context = context || {};
command = command || {};
command.requestId = command.requestId || T.getLogID();
const reqTr = T.branch({
key: "requestId",
value: command.requestId
});
L && L.has("trace") && L.log("trace", reqTr.add({
commandName: command.name,
command
}).toMessage({
tags: [blockRef, "process", "begin"],
text: "${commandName}#${requestId} - process: {command}"
}));
const routine = getRunhook(command);
const handler = routine && routine.handler;
const options = command.options;
const payload = command.payload || command.data;
if (lodash.isFunction(handler)) {
L && L.has("trace") && L.log("trace", reqTr.add({
commandName: command.name,
command: command,
predefinedContext: predefinedContext
}).toMessage({
tags: [blockRef, "process", "handler-invoked"],
text: "${commandName}#${requestId} - handler is invoked"
}));
if (predefinedContext) {
return Promise.resolve().then(handler.bind(null, options, payload, context));
} else {
return Promise.resolve().then(handler.bind(buildRunhookInstance(command), options, payload, context));
}
} else {
return Promise.reject(lodash.assign({
reason: "invalid_command_handler"
}, command));
}
};
this.createProgressMeter = function (args) {
if (args && lodash.isFunction(args.progress)) {
return {
update: function (completed, total, extra) {
args.progress(completed, total, extra);
}
};
}
return {
update: function () {}
};
};
params.bundleLoader.loadRoutines(routineMap, predefinedContext ? runhookInstance : {});
lodash.forOwn(getRunhooks(), function (value, key) {
routineStore.registerObject(value.name, value.object, {
scope: value.crateScope
});
});
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "constructor-end"],
text: " - constructor has finished"
}));
}
RunhookManager.argumentSchema = {
"$id": "runhookManager",
"type": "object",
"properties": {
"appName": {
"type": "string"
},
"appInfo": {
"type": "object"
},
"sandboxName": {
"type": "string"
},
"sandboxConfig": {
"type": "object"
},
"profileName": {
"type": "string"
},
"profileConfig": {
"type": "object"
},
"loggingFactory": {
"type": "object"
},
"injectedHandlers": {
"type": "object"
},
"jobqueueBinder": {
"type": "object"
},
"bundleLoader": {
"type": "object"
},
"schemaValidator": {
"type": "object"
}
}
};
function _newError(name, message, payload) {
return payload;
}
module.exports = RunhookManager;