devebot
Version:
Nodejs Microservice Framework
437 lines (435 loc) • 16.4 kB
JavaScript
"use strict";
const util = require("util");
const lodash = require("lodash");
const Promise = require("bluebird");
const Injektor = require("injektor");
const RunhookManager = require("./runhook-manager");
const chores = require("../utils/chores");
const constx = require("../utils/constx");
const errors = require("../utils/errors");
const nodash = require("../utils/nodash");
const blockRef = chores.getBlockRef(__filename);
const _isUpgradeSupported = chores.isUpgradeSupported.bind(chores);
const FRAMEWORK_PACKAGE_NAME = constx.FRAMEWORK.PACKAGE_NAME;
const DEFAULT_SERVICES = ["jobqueue-binder"];
function SandboxManager(params = {}) {
const issueInspector = params.issueInspector;
const loggingFactory = params.loggingFactory.branch(blockRef);
const L = loggingFactory.getLogger();
const T = loggingFactory.getTracer();
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "constructor-begin"],
text: " + constructor start ..."
}));
const managerMap = {};
chores.loadServiceByNames(managerMap, __dirname, DEFAULT_SERVICES);
const managerNames = lodash.keys(managerMap);
const utilityMap = {};
const utilityNames = [];
if (_isUpgradeSupported("sandbox-mapping-loader")) {
utilityNames.push("mapping-loader");
}
chores.loadServiceByNames(utilityMap, __dirname, utilityNames);
const serviceMap = {};
params.bundleLoader.loadServices(serviceMap);
chores.kickOutOf(serviceMap, managerNames);
const triggerMap = {};
params.bundleLoader.loadTriggers(triggerMap);
chores.kickOutOf(triggerMap, managerNames);
// split the triggerMap to incomingStarterMap and outgoingStarterMap
const incomingStarterMap = {};
const internalStarterMap = {};
const outgoingStarterMap = {};
lodash.forOwn(triggerMap, function (triggerDescriptor, triggerName) {
switch (triggerDescriptor.facingType) {
case "incoming":
case "receiver":
lodash.set(incomingStarterMap, triggerName, triggerDescriptor);
break;
case "outgoing":
case "producer":
lodash.set(outgoingStarterMap, triggerName, triggerDescriptor);
break;
default:
lodash.set(internalStarterMap, triggerName, triggerDescriptor);
}
});
const sandboxNames = params.sandboxNames;
const sandboxConfig = params.sandboxConfig;
L && L.has("dunce") && L.log("dunce", T && T.add({
sandboxNames: sandboxNames,
sandboxConfig: util.inspect(sandboxConfig)
}).toMessage({
text: " - load the sandbox${sandboxNames} with configuration: ${sandboxConfig}"
}));
L && L.has("silly") && L.log("silly", T && T.add({
sandboxNames,
sandboxConfig
}).toMessage({
tags: [blockRef, "sandbox-info"],
text: " - create sandbox${sandboxNames}.injektor object"
}));
const COPIED_DEPENDENCIES = ["appName", "appInfo", "sandboxNames", "sandboxOrigin", "sandboxConfig", "profileNames", "profileConfig", "contextManager", "schemaValidator", "loggingFactory", "processManager"];
if (_isUpgradeSupported("builtin-mapping-loader")) {
COPIED_DEPENDENCIES.push("mappingLoader");
}
const refNameMappings = lodash.assign(lodash.keyBy(COPIED_DEPENDENCIES), {
"sandboxBaseConfig": "sandboxOrigin"
});
function initInjektor(params, refNameMappings, myInjektor) {
myInjektor = myInjektor || new Injektor(chores.injektorOptions);
for (const refName in refNameMappings) {
const refAlias = lodash.get(refNameMappings, refName);
myInjektor.registerObject(refName, lodash.get(params, refAlias), chores.injektorContext);
}
return myInjektor;
}
const sandboxInjektor = initInjektor(params, refNameMappings);
lodash.forOwn(utilityMap, function (utilityConstruktor, utilityName) {
sandboxInjektor.defineService(utilityName, utilityConstruktor, chores.injektorContext);
});
lodash.forOwn(serviceMap, function (serviceRecord, serviceName) {
sandboxInjektor.defineService(serviceRecord.name, serviceRecord.construktor, {
scope: serviceRecord.crateScope
});
});
lodash.forOwn(triggerMap, function (triggerRecord, triggerName) {
sandboxInjektor.defineService(triggerRecord.name, triggerRecord.construktor, {
scope: triggerRecord.crateScope
});
});
const dialectMap = params.bridgeLoader.loadDialects({}, lodash.get(sandboxConfig, ["bridges"], {}));
lodash.forOwn(dialectMap, function (dialectRecord, dialectName) {
sandboxInjektor.defineService(dialectRecord.name, dialectRecord.construktor, {
scope: dialectRecord.crateScope
});
});
const REGISTRY_EXCLUDED_SERVICES = [getComponentLabel("sandboxRegistry")];
L && L.has("silly") && L.log("silly", T && T.add({
excludedServices: REGISTRY_EXCLUDED_SERVICES
}).toMessage({
tags: [blockRef, "excluded-internal-services"],
text: " - REGISTRY_EXCLUDED_SERVICES: ${excludedServices}"
}));
sandboxInjektor.registerObject("sandboxRegistry", new SandboxRegistry({
injektor: sandboxInjektor,
excludedServices: REGISTRY_EXCLUDED_SERVICES
}), chores.injektorContext);
function getCrateName(handlerRecord) {
return [handlerRecord.crateScope, handlerRecord.name].join(sandboxInjektor.separator);
}
const injectedHandlers = {};
const injectedServices = {};
const sandboxName = params["sandboxNames"].join(",");
const profileName = params["profileNames"].join(",");
const miscObjects = {
bridgeDialectNames: lodash.map(lodash.values(dialectMap), getCrateName),
pluginServiceNames: lodash.map(lodash.values(serviceMap), getCrateName),
pluginTriggerNames: lodash.map(lodash.values(triggerMap), getCrateName),
sandboxName: sandboxName,
profileName: profileName
};
lodash.forOwn(miscObjects, function (obj, name) {
sandboxInjektor.registerObject(name, obj, chores.injektorContext);
});
const instantiateObject = function (_injektor, handlerRecord, handlerType, injectedHandlers, injectedServices) {
const exceptions = [];
const handlerName = [handlerRecord.crateScope, handlerRecord.name].join(_injektor.separator);
L && L.has("silly") && L.log("silly", T && T.add({
handlerName,
handlerType
}).toMessage({
tags: [blockRef, "instantiateObject"],
text: " - instantiate object: ${handlerName}"
}));
const handler = _injektor.lookup(handlerName, exceptions);
if (handler && injectedHandlers) {
lodash.set(injectedHandlers, [handlerName], handler);
}
if (handler && injectedServices && handlerRecord.crateScope) {
lodash.set(injectedServices, [handlerRecord.crateScope, handlerName], handler);
}
if (handler && handlerType === "TRIGGER") {
const methods = {
start: handler.start || handler.open,
stop: handler.stop || handler.close
};
const requiredMethods = lodash.filter(lodash.keys(methods), function (name) {
return !lodash.isFunction(methods[name]);
});
if (!lodash.isEmpty(requiredMethods)) {
issueInspector.collect({
stage: "check-methods",
type: handlerType,
name: handlerName,
hasError: true,
methods: requiredMethods
});
}
}
lodash.forEach(exceptions, function (exception) {
const opStatus = {
stage: "instantiating",
type: handlerType,
name: handlerName,
hasError: true,
stack: exception.stack
};
issueInspector.collect(opStatus);
});
};
lodash.forOwn(dialectMap, function (dialectRecord, dialectName) {
instantiateObject(sandboxInjektor, dialectRecord, "DIALECT", injectedHandlers, injectedServices);
});
lodash.forOwn(serviceMap, function (serviceRecord, serviceName) {
instantiateObject(sandboxInjektor, serviceRecord, "SERVICE", injectedHandlers, injectedServices);
});
lodash.forOwn(triggerMap, function (triggerRecord, triggerName) {
instantiateObject(sandboxInjektor, triggerRecord, "TRIGGER", injectedServices);
});
const runhookInjektor = initInjektor(params, lodash.assign({
"bundleLoader": "bundleLoader"
}, refNameMappings));
runhookInjektor.registerObject("sandboxName", sandboxName, chores.injektorContext);
runhookInjektor.registerObject("profileName", profileName, chores.injektorContext);
runhookInjektor.registerObject("injectedHandlers", injectedHandlers, chores.injektorContext);
runhookInjektor.registerObject("injectedServices", injectedServices, chores.injektorContext);
lodash.forOwn(managerMap, function (managerConstructor, managerName) {
runhookInjektor.defineService(managerName, managerConstructor, chores.injektorContext);
});
runhookInjektor.defineService("runhookManager", RunhookManager, chores.injektorContext);
const runhookManager = runhookInjektor.lookup("runhookManager", chores.injektorContext);
sandboxInjektor.registerObject("runhookManager", runhookManager, chores.injektorContext);
this.getRunhookManager = function () {
return runhookManager;
};
this.getSandboxService = function (serviceName, context) {
return sandboxInjektor.lookup(serviceName, context);
};
this.getBridgeDialectNames = function () {
return sandboxInjektor.lookup("bridgeDialectNames", chores.injektorContext);
};
this.getPluginServiceNames = function () {
return sandboxInjektor.lookup("pluginServiceNames", chores.injektorContext);
};
this.getPluginTriggerNames = function () {
return sandboxInjektor.lookup("pluginTriggerNames", chores.injektorContext);
};
this.startTriggers = function (triggerNames) {
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "trigger", "start"],
text: " - Start triggers"
}));
return this.eachTriggers(function (trigger) {
return trigger.start();
}, triggerNames, {
actionName: "start"
});
};
this.stopTriggers = function (triggerNames) {
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "trigger", "stop"],
text: " - Stop triggers"
}));
return this.eachTriggers(function (trigger) {
return trigger.stop();
}, triggerNames, {
actionName: "stop"
});
};
this.eachTriggers = function (iteratee, triggerNames, options) {
if (!lodash.isFunction(iteratee)) return;
if (lodash.isString(triggerNames)) triggerNames = [triggerNames];
if (triggerNames && !lodash.isArray(triggerNames)) return;
L && L.has("silly") && L.log("silly", T && T.add({
triggerNames: triggerNames || "all"
}).toMessage({
tags: [blockRef, "trigger", "loop"],
text: " - Loop triggers: ${triggerNames}"
}));
const actionName = options && options.actionName;
const triggers = [];
function initTrigger(triggerRecord, triggerId) {
const triggerName = getCrateName(triggerRecord);
if (!triggerNames || triggerNames.indexOf(triggerName) >= 0) {
L && L.has("silly") && L.log("silly", T && T.add({
actionName,
triggerName
}).toMessage({
tags: [blockRef, "trigger", "action"],
text: " - ${actionName} trigger[${triggerName}]"
}));
const trigger = sandboxInjektor.lookup(triggerName);
if (trigger) {
triggers.push(trigger);
}
}
}
lodash.forOwn(outgoingStarterMap, initTrigger);
lodash.forOwn(internalStarterMap, initTrigger);
lodash.forOwn(incomingStarterMap, initTrigger);
return Promise.mapSeries(triggers, iteratee);
};
this.getServiceInfo = function () {
return {};
};
this.getServiceHelp = function () {
const self = this;
const blocks = [];
blocks.push({
type: "record",
title: "Sandbox overview",
label: {
sandbox_names: "List of sandboxes",
bridge_dialect_names: "Bridge dialects",
plugin_service_names: "Plugin services",
plugin_trigger_names: "Plugin triggers"
},
data: {
sandbox_names: JSON.stringify(self.getSandboxNames(), null, 2),
bridge_dialect_names: JSON.stringify(self.getBridgeDialectNames(), null, 2),
plugin_service_names: JSON.stringify(self.getPluginServiceNames(), null, 2),
plugin_trigger_names: JSON.stringify(self.getPluginTriggerNames(), null, 2)
}
});
mergeSandboxServiceHelps.call(self, self.getBridgeDialectNames(), blocks);
mergeSandboxServiceHelps.call(self, self.getPluginServiceNames(), blocks);
mergeSandboxServiceHelps.call(self, self.getPluginTriggerNames(), blocks);
return blocks;
};
L && L.has("silly") && L.log("silly", T && T.toMessage({
tags: [blockRef, "constructor-end"],
text: " - constructor has finished"
}));
}
SandboxManager.argumentSchema = {
"$id": "sandboxManager",
"type": "object",
"properties": {
"appName": {
"type": "string"
},
"appInfo": {
"type": "object"
},
"bridgeLoader": {
"type": "object"
},
"bundleLoader": {
"type": "object"
},
"sandboxNames": {
"type": "array",
"items": {
"type": "string"
}
},
"sandboxOrigin": {
"type": "object"
},
"sandboxConfig": {
"type": "object"
},
"profileNames": {
"type": "array",
"items": {
"type": "string"
}
},
"profileConfig": {
"type": "object"
},
"contextManager": {
"type": "object"
},
"issueInspector": {
"type": "object"
},
"loggingFactory": {
"type": "object"
},
"processManager": {
"type": "object"
},
"schemaValidator": {
"type": "object"
}
}
};
if (_isUpgradeSupported("builtin-mapping-loader")) {
lodash.assign(SandboxManager.argumentSchema.properties, {
"mappingLoader": {
"type": "object"
}
});
}
module.exports = SandboxManager;
function getComponentLabel(compName) {
return FRAMEWORK_PACKAGE_NAME + chores.getSeparator() + compName;
}
function mergeSandboxServiceHelps(serviceNames, blocks) {
const self = this;
serviceNames.forEach(function (serviceName) {
pickSandboxServiceHelp.call(self, serviceName, blocks);
});
}
function pickSandboxServiceHelp(serviceName, blocks) {
const self = this;
const serviceObject = self.getSandboxService(serviceName);
if (lodash.isObject(serviceObject) && lodash.isFunction(serviceObject.getServiceHelp)) {
const serviceHelp = nodash.arrayify(serviceObject.getServiceHelp());
if (lodash.isArray(serviceHelp)) {
lodash.forEach(serviceHelp, function (serviceInfo) {
if (lodash.isString(serviceInfo.title)) {
serviceInfo.title = serviceName + " - " + serviceInfo.title;
}
blocks.push(serviceInfo);
});
}
}
}
function SandboxRegistry(params = {}) {
const {
injektor,
isExcluded,
excludedServices
} = params;
function validateBeanName(beanName, context = {}) {
const info = injektor.parseName(beanName, context);
if (info.scope === FRAMEWORK_PACKAGE_NAME) {
const RestrictedError = errors.assertConstructor("RestrictedBeanError");
throw new RestrictedError(util.format("dependency scope [%s] is restricted", FRAMEWORK_PACKAGE_NAME));
}
const exceptions = [];
const fullname = injektor.resolveName(beanName, {
scope: context.scope,
exceptions: exceptions
});
if (fullname != null) {
const DuplicatedError = errors.assertConstructor("DuplicatedBeanError");
throw new DuplicatedError("dependency item is duplicated");
}
}
this.declareObject = function (beanName, beanObject, context) {
validateBeanName(beanName, context);
injektor.registerObject(beanName, beanObject, context);
};
this.defineService = function (beanName, construktor, context) {
validateBeanName(beanName, context);
injektor.defineService(beanName, construktor, context);
};
this.lookup = function (serviceName, context) {
context = context || {};
const exceptions = [];
const fullname = injektor.resolveName(serviceName, {
scope: context.scope,
exceptions: exceptions
});
if (fullname == null) return null;
if (lodash.isFunction(isExcluded) && isExcluded(fullname)) return null;
if (lodash.isArray(excludedServices) && excludedServices.indexOf(fullname) >= 0) return null;
return injektor.lookup(serviceName, context);
};
// @deprecated
this.lookupService = this.lookup;
}