UNPKG

devebot

Version:

Nodejs Microservice Framework

581 lines (578 loc) 21 kB
"use strict"; const lodash = require("lodash"); const path = require("path"); const util = require("util"); const chores = require("../utils/chores"); const constx = require("../utils/constx"); const loader = require("../utils/loader"); const blockRef = chores.getBlockRef(__filename); const _isUpgradeSupported = chores.isUpgradeSupported.bind(chores); const FILE_JS_FILTER_PATTERN = constx.FILE.JS_FILTER_PATTERN; function BundleLoader(params = {}) { const loggingFactory = params.loggingFactory.branch(blockRef); const L = loggingFactory.getLogger(); const T = loggingFactory.getTracer(); const CTX = lodash.assign({ L, T }, lodash.pick(params, ["issueInspector", "nameResolver", "schemaValidator", "objectDecorator"])); L && L.has("silly") && L.log("silly", T && T.toMessage({ tags: [blockRef, "constructor-begin"], text: " + constructor start ..." })); L && L.has("dunce") && L.log("dunce", T && T.add(params).toMessage({ text: " - bundleLoader start with bundleList: ${bundleList}" })); this.loadMetadata = function (metadataMap) { return loadAllMetainfs(CTX, metadataMap, params.bundleList); }; this.loadRoutines = function (routineMap, routineContext) { return loadAllScripts(CTX, routineMap, "ROUTINE", routineContext, params.bundleList); }; this.loadServices = function (serviceMap) { return loadAllGadgets(CTX, serviceMap, "SERVICE", params.bundleList); }; this.loadTriggers = function (triggerMap) { return loadAllGadgets(CTX, triggerMap, "TRIGGER", params.bundleList); }; L && L.has("silly") && L.log("silly", T && T.toMessage({ tags: [blockRef, "constructor-end"], text: " - constructor has finished" })); } BundleLoader.argumentSchema = { "$id": "bundleLoader", "type": "object", "properties": { "bundleList": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "path": { "type": "string" } }, "required": ["name", "path"] } }, "issueInspector": { "type": "object" }, "nameResolver": { "type": "object" }, "loggingFactory": { "type": "object" }, "objectDecorator": { "type": "object" }, "schemaValidator": { "type": "object" } } }; module.exports = BundleLoader; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private members function hasSeparatedDir(scriptType) { return lodash.filter(constx, function (obj, key) { // return ['METAINF', 'ROUTINE', 'SERVICE', 'TRIGGER'].indexOf(key) >= 0; return obj.ROOT_KEY && obj.SCRIPT_DIR; }).filter(function (info) { return info.ROOT_KEY !== constx[scriptType].ROOT_KEY && info.SCRIPT_DIR === constx[scriptType].SCRIPT_DIR; }).length === 0; } function getFilterPattern(scriptType) { return hasSeparatedDir(scriptType) ? FILE_JS_FILTER_PATTERN : constx[scriptType].ROOT_KEY + "_" + FILE_JS_FILTER_PATTERN; } function loadAllScripts(ctx, scriptMap, scriptType, scriptContext, pluginRootDirs) { scriptMap = scriptMap || {}; if (scriptType !== "ROUTINE") return scriptMap; pluginRootDirs.forEach(function (pluginRootDir) { loadScriptEntries(ctx, scriptMap, scriptType, scriptContext, pluginRootDir); }); return scriptMap; } function loadScriptEntries(ctx, scriptMap, scriptType, scriptContext, pluginRootDir) { const { L, T } = ctx || this || {}; const scriptSubDir = chores.getComponentDir(pluginRootDir, scriptType); const scriptFolder = path.join(pluginRootDir.path, scriptSubDir); L && L.has("dunce") && L.log("dunce", T && T.add({ scriptKey: constx[scriptType].ROOT_KEY, scriptFolder: scriptFolder }).toMessage({ text: " - load ${scriptKey}s from folder: ${scriptFolder}" })); const scriptFiles = chores.filterFiles(scriptFolder, getFilterPattern(scriptType)); scriptFiles.forEach(function (scriptFile) { loadScriptEntry(ctx, scriptMap, scriptType, scriptSubDir, scriptFile, scriptContext, pluginRootDir); }); } function loadScriptEntry(ctx, scriptMap, scriptType, scriptSubDir, scriptFile, scriptContext, pluginRootDir) { const { L, T, issueInspector, nameResolver } = ctx || this || {}; const opStatus = lodash.assign({ type: scriptType, file: scriptFile, subDir: scriptSubDir }, pluginRootDir); const scriptPath = path.join(pluginRootDir.path, scriptSubDir, scriptFile); try { const scriptInit = loader(scriptPath, { stopWhenError: true }); if (lodash.isFunction(scriptInit)) { L && L.has("dunce") && L.log("dunce", T && T.add({ scriptPath }).toMessage({ text: " - script file ${scriptPath} is ok" })); const scriptObject = scriptInit(scriptContext); const output = validateScript(ctx, scriptObject, scriptType); if (!output.valid) { L && L.has("dunce") && L.log("dunce", T && T.add({ validationResult: output }).toMessage({ text: " - validating script fail: ${validationResult}" })); opStatus.hasError = true; } else if (scriptObject.enabled === false) { L && L.has("dunce") && L.log("dunce", T && T.toMessage({ text: " - script is disabled" })); opStatus.isSkipped = true; } else { L && L.has("dunce") && L.log("dunce", T && T.toMessage({ text: " - script validation pass" })); const scriptName = scriptFile.replace(".js", "").toLowerCase(); let pluginName = nameResolver.getOriginalNameOf(pluginRootDir.name, pluginRootDir.type); if (!_isUpgradeSupported("refining-name-resolver")) { pluginName = nameResolver.getOriginalName(pluginRootDir); } const uniqueName = [pluginName, scriptName].join(chores.getSeparator()); const entry = {}; entry[uniqueName] = { crateScope: pluginName, name: scriptName, object: scriptObject }; lodash.defaultsDeep(scriptMap, entry); } } else { L && L.has("dunce") && L.log("dunce", T && T.add({ scriptPath }).toMessage({ text: " - script file ${scriptPath} doesnot contain a function." })); throw new Error(`The module file ${scriptPath} does not contain a function`); } } catch (err) { L && L.has("dunce") && L.log("dunce", T && T.add({ scriptPath }).toMessage({ text: " - script file ${scriptPath} loading has failed." })); opStatus.hasError = true; opStatus.stack = err.stack; } issueInspector.collect(opStatus); } function validateScript(ctx, scriptObject, scriptType) { const { schemaValidator } = ctx || this || {}; const results = []; scriptObject = scriptObject || {}; results.push(schemaValidator.validate(scriptObject, constx[scriptType].SCHEMA_OBJECT)); if (!lodash.isFunction(scriptObject.handler)) { results.push({ valid: false, errors: [{ message: "handler has wrong type: " + typeof scriptObject.handler }] }); } return results.reduce(function (output, result) { output.valid = output.valid && result.valid !== false; output.errors = output.errors.concat(result.errors); return output; }, { valid: true, errors: [] }); } function loadAllMetainfs(ctx, metainfMap, pluginRootDirs) { ctx = ctx || this || {}; metainfMap = metainfMap || {}; pluginRootDirs.forEach(function (pluginRootDir) { loadMetainfEntries(ctx, metainfMap, pluginRootDir); }); return metainfMap; } function loadMetainfEntries(ctx, metainfMap, pluginRootDir) { const { L, T } = ctx = ctx || this || {}; const metainfType = "METAINF"; const metainfSubDir = chores.getComponentDir(pluginRootDir, metainfType); const metainfFolder = path.join(pluginRootDir.path, metainfSubDir); L && L.has("dunce") && L.log("dunce", T && T.add({ metainfKey: constx[metainfType].ROOT_KEY, metainfFolder }).toMessage({ text: " - load ${metainfKey}s from folder: ${metainfFolder}" })); const schemaFiles = chores.filterFiles(metainfFolder, getFilterPattern(metainfType)); schemaFiles.forEach(function (schemaFile) { loadMetainfEntry(ctx, metainfMap, metainfSubDir, schemaFile, pluginRootDir); }); } function loadMetainfEntry(ctx, metainfMap, metainfSubDir, schemaFile, pluginRootDir) { const { L, T, issueInspector, nameResolver } = ctx || this || {}; const metainfType = "METAINF"; const opStatus = lodash.assign({ type: "METAINF", file: schemaFile, subDir: metainfSubDir }, pluginRootDir); const filepath = path.join(pluginRootDir.path, metainfSubDir, schemaFile); try { const metainfObject = loader(filepath, { stopWhenError: true }); const output = validateMetainf(ctx, metainfObject, metainfType); if (!output.valid) { L && L.has("dunce") && L.log("dunce", T && T.add({ validationResult: output }).toMessage({ text: " - validating schema fail: ${validationResult}" })); opStatus.hasError = true; } else if (metainfObject.enabled === false) { L && L.has("dunce") && L.log("dunce", T && T.toMessage({ text: " - schema is disabled" })); opStatus.isSkipped = true; } else { L && L.has("dunce") && L.log("dunce", T && T.toMessage({ text: " - schema validation pass" })); const typeName = metainfObject.type || schemaFile.replace(".js", "").toLowerCase(); const subtypeName = metainfObject.subtype || "default"; let crateScope = nameResolver.getOriginalNameOf(pluginRootDir.name, pluginRootDir.type); let pluginCode = nameResolver.getDefaultAliasOf(pluginRootDir.name, pluginRootDir.type); if (!_isUpgradeSupported("refining-name-resolver")) { crateScope = nameResolver.getOriginalName(pluginRootDir); pluginCode = nameResolver.getDefaultAlias(pluginRootDir); } const uniqueName = [crateScope, typeName].join(chores.getSeparator()); const entry = {}; entry[uniqueName] = entry[uniqueName] || {}; entry[uniqueName][subtypeName] = { crateScope: crateScope, pluginCode: pluginCode, type: typeName, subtype: subtypeName, schema: metainfObject.schema }; lodash.defaultsDeep(metainfMap, entry); } } catch (err) { L && L.has("dunce") && L.log("dunce", T && T.add({ filepath }).toMessage({ text: " - schema file ${filepath} loading has failed" })); L && L.has("dunce") && chores.printError(err); opStatus.hasError = true; opStatus.stack = err.stack; } issueInspector.collect(opStatus); } function validateMetainf(ctx, metainfObject) { const { schemaValidator } = ctx = ctx || this || {}; const metainfType = "METAINF"; const results = []; metainfObject = metainfObject || {}; results.push(schemaValidator.validate(metainfObject, constx[metainfType].SCHEMA_OBJECT)); return results.reduce(function (output, result) { output.valid = output.valid && result.valid !== false; output.errors = output.errors.concat(result.errors); return output; }, { valid: true, errors: [] }); } function loadAllGadgets(ctx, gadgetMap, gadgetType, pluginRootDirs) { gadgetMap = gadgetMap || {}; if (["SERVICE", "TRIGGER"].indexOf(gadgetType) < 0) return gadgetMap; pluginRootDirs.forEach(function (pluginRootDir) { loadGadgetEntries(ctx, gadgetMap, gadgetType, pluginRootDir); }); return gadgetMap; } function loadGadgetEntries(ctx, gadgetMap, gadgetType, pluginRootDir) { const { L, T } = ctx || this || {}; const gadgetSubDir = chores.getComponentDir(pluginRootDir, gadgetType); const gadgetFolder = path.join(pluginRootDir.path, gadgetSubDir); L && L.has("dunce") && L.log("dunce", T && T.add({ gadgetKey: constx[gadgetType].ROOT_KEY, gadgetFolder: gadgetFolder }).toMessage({ text: " - load ${gadgetKey}s from folder: ${gadgetFolder}" })); const gadgetFiles = chores.filterFiles(gadgetFolder, getFilterPattern(gadgetType)); gadgetFiles.forEach(function (gadgetFile) { loadGadgetEntry(ctx, gadgetMap, gadgetType, gadgetSubDir, gadgetFile, pluginRootDir); }); } function loadGadgetEntry(ctx, gadgetMap, gadgetType, gadgetSubDir, gadgetFile, pluginRootDir) { const { L, T, issueInspector } = ctx || this || {}; const opStatus = lodash.assign({ type: gadgetType, file: gadgetFile, subDir: gadgetSubDir }, pluginRootDir); const gadgetPath = path.join(pluginRootDir.path, gadgetSubDir, gadgetFile); try { const gadgetConstructor = loader(gadgetPath, { stopWhenError: true }); L && L.has("dunce") && L.log("dunce", T && T.add({ gadgetPath }).toMessage({ text: " - gadget file ${gadgetPath} loading has done" })); if (lodash.isFunction(gadgetConstructor)) { const gadgetName = chores.stringCamelCase(gadgetFile.replace(".js", "")); lodash.defaults(gadgetMap, buildGadgetWrapper(ctx, gadgetConstructor, gadgetType, gadgetName, pluginRootDir)); } else { L && L.has("dunce") && L.log("dunce", T && T.add({ gadgetPath }).toMessage({ text: " - gadget file ${gadgetPath} doesnot contain a function" })); throw new Error(`The module file ${gadgetPath} does not contain a function`); } } catch (err) { L && L.has("dunce") && L.log("dunce", T && T.add({ gadgetPath }).toMessage({ text: " - gadget file ${gadgetPath} loading has failed" })); L && L.has("dunce") && chores.printError(err); opStatus.hasError = true; opStatus.stack = err.stack; } issueInspector.collect(opStatus); } function buildGadgetWrapper(ctx, gadgetConstructor, gadgetType, wrapperName, pluginRootDir) { const { L, T, nameResolver, objectDecorator } = ctx || this || {}; const result = {}; if (!lodash.isFunction(gadgetConstructor)) { L && L.has("dunce") && L.log("dunce", T && T.toMessage({ text: " - gadgetConstructor is invalid" })); return result; } let pluginName = nameResolver.getOriginalNameOf(pluginRootDir.name, pluginRootDir.type); let pluginCode = nameResolver.getDefaultAliasOf(pluginRootDir.name, pluginRootDir.type); if (!_isUpgradeSupported("refining-name-resolver")) { pluginName = nameResolver.getOriginalName(pluginRootDir); pluginCode = nameResolver.getDefaultAlias(pluginRootDir); } const uniqueName = [pluginName, wrapperName].join(chores.getSeparator()); const referenceAlias = lodash.get(pluginRootDir, ["presets", "referenceAlias"], {}); const facingType = lodash.get(pluginRootDir, ["presets", "facingType"], "outgoing"); function wrapperConstructor(kwargs = {}) { const _ref_ = { kwargs: null }; function getWrappedParams(kwargs) { return _ref_.kwargs = _ref_.kwargs || lodash.clone(kwargs) || {}; } // crateScope & componentName kwargs.packageName = pluginRootDir.name; kwargs.componentName = wrapperName; kwargs.componentId = uniqueName; // resolve newFeatures const newFeatures = lodash.get(kwargs, ["profileConfig", "newFeatures", pluginCode], {}); L && L.has("dunce") && L.log("dunce", T && T.add({ pluginCode, newFeatures }).toMessage({ text: " - newFeatures[${pluginCode}]: ${newFeatures}" })); // resolve plugin configuration path if (newFeatures.sandboxConfig !== false) { kwargs = getWrappedParams(kwargs); if (chores.isSpecialBundle(pluginRootDir.type)) { kwargs.sandboxOrigin = lodash.get(kwargs, ["sandboxOrigin", pluginCode], {}); kwargs.sandboxConfig = lodash.get(kwargs, ["sandboxConfig", pluginCode], {}); } else { kwargs.sandboxOrigin = lodash.get(kwargs, ["sandboxOrigin", "plugins", pluginCode], {}); kwargs.sandboxConfig = lodash.get(kwargs, ["sandboxConfig", "plugins", pluginCode], {}); } } // wrap getLogger() and add getTracer() if (newFeatures.logoliteEnabled !== false) { kwargs = getWrappedParams(kwargs); kwargs.loggingFactory = kwargs.loggingFactory.branch(uniqueName); } // transform parameters by referenceAlias if (!lodash.isEmpty(referenceAlias)) { kwargs = getWrappedParams(kwargs); lodash.forOwn(referenceAlias, function (oldKey, newKey) { if (kwargs[oldKey]) { kwargs[newKey] = kwargs[oldKey]; } }); if (constx.LOADING.DELETE_OLD_REFERENCE_ALIAS) { // remove the old references const newKeys = lodash.keys(referenceAlias); const oldKeys = lodash.values(referenceAlias); lodash.forEach(oldKeys, function (oldKey) { if (newKeys.indexOf(oldKey) < 0) { delete kwargs[oldKey]; } }); } } // transform parameters by referenceHash (after referenceAlias) const referenceHash = gadgetConstructor.referenceHash; if (lodash.isObject(referenceHash)) { kwargs = getWrappedParams(kwargs); lodash.forOwn(referenceHash, function (fullname, shortname) { if (kwargs[fullname]) { kwargs[shortname] = kwargs[fullname]; } }); } // write around-log begin if (newFeatures.logoliteEnabled !== false && _isUpgradeSupported("gadget-around-log")) { this.logger = kwargs.loggingFactory.getLogger(); this.tracer = kwargs.loggingFactory.getTracer(); this.logger.has("silly") && this.logger.log("silly", this.tracer.toMessage({ tags: [uniqueName, "constructor-begin"], text: " + constructor begin ..." })); } // invoke original constructor gadgetConstructor.call(this, kwargs); // write around-log end if (newFeatures.logoliteEnabled !== false && _isUpgradeSupported("gadget-around-log")) { this.logger.has("silly") && this.logger.log("silly", this.tracer.toMessage({ tags: [uniqueName, "constructor-end"], text: " - constructor has finished" })); } } util.inherits(wrapperConstructor, gadgetConstructor); const wrappedArgumentFields = ["sandboxName", "sandboxOrigin", "sandboxConfig", "profileName", "profileConfig", "loggingFactory"]; if (gadgetConstructor.argumentSchema) { const wrappedArgumentSchema = { "$id": uniqueName, "type": "object", "properties": {} }; lodash.forEach(wrappedArgumentFields, function (fieldName) { if (["sandboxName", "profileName"].indexOf(fieldName) >= 0) { wrappedArgumentSchema.properties[fieldName] = { "type": "string" }; } else { wrappedArgumentSchema.properties[fieldName] = { "type": "object" }; } }); const originalArgumentSchema = lodash.omit(gadgetConstructor.argumentSchema, ["$id"]); wrapperConstructor.argumentSchema = lodash.merge(wrappedArgumentSchema, originalArgumentSchema); if (!lodash.isEmpty(referenceAlias)) { const properties = lodash.mapKeys(gadgetConstructor.argumentSchema.properties, function (val, key) { return referenceAlias[key] || key; }); wrapperConstructor.argumentSchema = lodash.merge(wrappedArgumentSchema, { properties }); } L && L.has("dunce") && L.log("dunce", T && T.add({ argumentSchema: wrapperConstructor.argumentSchema }).toMessage({ text: " - wrapperConstructor.argumentSchema: ${argumentSchema}" })); } else { let wrappedArgumentProps = gadgetConstructor.referenceList || []; if (gadgetConstructor.referenceHash) { wrappedArgumentProps = lodash.values(gadgetConstructor.referenceHash); } if (!lodash.isEmpty(referenceAlias)) { wrappedArgumentProps = lodash.map(wrappedArgumentProps, function (key) { return referenceAlias[key] || key; }); } wrappedArgumentProps = wrappedArgumentFields.concat(wrappedArgumentProps); wrapperConstructor.argumentProperties = lodash.uniq(wrappedArgumentProps); L && L.has("dunce") && L.log("dunce", T && T.add({ argumentProperties: wrapperConstructor.argumentProperties }).toMessage({ text: " - wrapperConstructor.argumentProperties: ${argumentProperties}" })); } let construktor = wrapperConstructor; const gadgetGroup = lodash.get(constx, [gadgetType, "GROUP"]); if (["reducers", "services", "triggers"].indexOf(gadgetGroup) >= 0) { construktor = objectDecorator.wrapPluginGadget(construktor, { pluginName: pluginName, gadgetType: gadgetGroup, gadgetName: wrapperName }); } result[uniqueName] = { crateScope: pluginName, name: wrapperName, facingType: facingType, construktor: construktor }; L && L.has("dunce") && L.log("dunce", T && T.add({ uniqueName: uniqueName, crateScope: pluginName, name: wrapperName }).toMessage({ text: " - build gadget wrapper (${name}) has done." })); return result; }