inceptum
Version:
hipages take on the foundational library for enterprise-grade apps written in NodeJS
303 lines • 13.6 kB
JavaScript
;
// Shutdown order
// Factory beans
// Profile support
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const ConfigProvider_1 = require("../config/ConfigProvider");
const LogManager_1 = require("../log/LogManager");
const PromiseUtil_1 = require("../util/PromiseUtil");
const Lifecycle_1 = require("./Lifecycle");
const IoCException_1 = require("./IoCException");
const ObjectDefinition_1 = require("./objectdefinition/ObjectDefinition");
const BaseSingletonDefinition_1 = require("./objectdefinition/BaseSingletonDefinition");
const PreinstantiatedSingletonDefinition_1 = require("./objectdefinition/PreinstantiatedSingletonDefinition");
class Context extends Lifecycle_1.Lifecycle {
constructor(name, parentContext, options = {}) {
super(name, options.logger || LogManager_1.LogManager.getLogger(__filename));
this.config = options.config || new ConfigProvider_1.default();
this.parentContext = parentContext;
this.objectDefinitions = new Map();
this.startedObjects = new Map();
this.objectDefinitionInspector = [];
this.objectGroups = new Map();
this.registerDefinition(new PreinstantiatedSingletonDefinition_1.PreinstantiatedSingletonDefinition(this, '__CONTEXT__'));
}
// ************************************
// Lifecycle related methods
// ************************************
lcStart() {
if (this.parentContext) {
return this.parentContext.lcStart().then(() => super.lcStart());
}
return super.lcStart();
}
lcStop() {
const stopPromise = super.lcStop();
if (this.parentContext) {
return stopPromise.then(() => this.parentContext.lcStop());
}
return stopPromise;
}
doStart() {
this.applyObjectDefinitionModifiers();
return PromiseUtil_1.PromiseUtil.map(Array.from(this.objectDefinitions.values()).filter((o) => !o.isLazy()), (objectDefinition) => objectDefinition.getInstance())
.catch((err) => {
this.getLogger().error({ err }, 'There was an error starting context. At least one non-lazy object threw an exception during startup. Stopping context');
return this.lcStop().then(() => {
throw err;
});
})
.then(() => {
this.getLogger().debug(`Context ${this.getName()} started`);
});
}
doStop() {
return PromiseUtil_1.PromiseUtil.map(Array.from(this.startedObjects.values()), (startedObject) => startedObject.lcStop()).then(() => {
/* donothing */
});
}
// ************************************
// Context composition methods
// ************************************
clone(name) {
this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
const copy = new Context(name, this.parentContext, { logger: this.logger, config: this.config });
this.objectDefinitions.forEach((objectDefinition) => {
if (objectDefinition.getName() !== '__CONTEXT__') {
copy.registerDefinition(objectDefinition.copy());
}
});
return copy;
}
importContext(otherContext, overwrite = false) {
this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
otherContext.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
otherContext.objectDefinitions.forEach((objectDefinition) => {
if (objectDefinition.getName() !== '__CONTEXT__') {
this.registerDefinition(objectDefinition, overwrite);
}
});
}
// ************************************
// Object Definition inspector methods
// ************************************
addObjectDefinitionInspector(inspector) {
this.objectDefinitionInspector.push(inspector);
}
// ************************************
// Object Definition registration methods
// ************************************
registerDefinition(objectDefinition, overwrite = false) {
this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
if (!(objectDefinition instanceof ObjectDefinition_1.ObjectDefinition)) {
throw new IoCException_1.IoCException('Provided input for registration is not an instance of ObjectDefinition');
}
if (!overwrite && this.objectDefinitions.has(objectDefinition.getName())) {
throw new IoCException_1.IoCException(`Object definition with name ${objectDefinition.getName()} already exists in this context`);
}
if (this.parentContext && this.parentContext.objectDefinitions.has(objectDefinition.getName()) && objectDefinition.getName() !== '__CONTEXT__') {
throw new IoCException_1.IoCException(`Parent context already has an object definition with name ${objectDefinition.getName()}`);
}
this.objectDefinitions.set(objectDefinition.getName(), objectDefinition);
objectDefinition.setContext(this);
objectDefinition.onStateOnce(Lifecycle_1.LifecycleState.STARTED, () => this.startedObjects.set(objectDefinition.getName(), objectDefinition));
objectDefinition.onStateOnce(Lifecycle_1.LifecycleState.STOPPED, () => this.startedObjects.delete(objectDefinition.getName()));
}
registerSingletons(...singletons) {
singletons.forEach((singleton) => {
if (singleton instanceof ObjectDefinition_1.ObjectDefinition) {
this.registerDefinition(singleton);
}
else if (singleton instanceof Function) {
this.registerDefinition(new BaseSingletonDefinition_1.BaseSingletonDefinition(singleton));
this.logger.debug(`Registering singleton ${singleton.name}`);
}
else {
throw new IoCException_1.IoCException(`Not sure how to convert input into SingletonDefinition: ${singleton}`);
}
});
}
registerSingletonsInDir(dir) {
Context.walkDirSync(dir).filter((file) => ['.js', '.ts'].indexOf(path.extname(file)) >= 0).forEach((file) => {
if (file.includes('.d.ts')) {
return; // Ignore type definition files
}
let expectedClass = path.basename(file);
expectedClass = expectedClass.substr(0, expectedClass.length - 3);
const loaded = require(file);
if (loaded) {
if (typeof loaded === 'object' && loaded.__esModule && loaded.default && loaded.default.constructor) {
this.registerSingletons(loaded.default);
}
else if (typeof loaded === 'object' &&
!(loaded instanceof Function) &&
loaded[expectedClass] &&
loaded[expectedClass].constructor) {
this.registerSingletons(loaded[expectedClass]);
}
else if (loaded instanceof Function && loaded.name && loaded.name === expectedClass) {
this.registerSingletons(loaded);
}
else {
throw new IoCException_1.IoCException(`Couldn't register singleton for ${file}`);
}
}
});
}
static requireFilesInDir(dir) {
Context.walkDirSync(dir).filter((file) => path.extname(file) === '.js').forEach((file) => {
// eslint-disable-next-line global-require
require(file);
});
}
/**
* Walks a directory recursively and returns an array with all the files
*
* @private
* @param dir The directory to walk through
* @param filelist The carried-over list of files
* @return {Array} The list of files in this directory and subdirs
*/
static walkDirSync(dir, filelist = []) {
fs.readdirSync(dir).forEach((file) => {
filelist = fs.statSync(path.join(dir, file)).isDirectory()
? Context.walkDirSync(path.join(dir, file), filelist)
: filelist.concat(path.join(dir, file));
});
return filelist;
}
// ************************************
// Config functions
// ************************************
/**
* Get an element from the configuration.
* Can be both a leaf of the configuration, or an intermediate path. In the latter case it will return
* an object with all the configs under that path.
* It will throw an exception if the key doesn't exist.
*
* @param {string} key The key of the config we want
* @param {*} defaultValue A default value to use if the key doesn't exist
* @return {*} The requested configuration
* @throws {Error} If the given key doesn't exist and a default value is not provided
* @see {@link Context.hasConfig}
*/
getConfig(key, defaultValue) {
return this.config.getConfig(key, defaultValue);
}
/**
* Indicates whether a given key exists in the configuration
* @param key
* @return {*}
*/
hasConfig(key) {
return this.config.hasConfig(key);
}
// ************************************
// Group functions
// ************************************
addObjectNameToGroup(groupName, objectName) {
if (!this.objectGroups.has(groupName)) {
this.objectGroups.set(groupName, []);
}
this.objectGroups.get(groupName).push(objectName);
}
getGroupObjectNames(groupName) {
if (!this.objectGroups.has(groupName)) {
return [];
}
return this.objectGroups.get(groupName);
}
// ************************************
// Get Bean Functions
// ************************************
getObjectByName(beanName) {
const beanDefinition = this.getDefinitionByName(beanName);
return beanDefinition.getInstance();
}
getObjectByType(className) {
const beanDefinition = this.getDefinitionByType(className);
return beanDefinition.getInstance();
}
getObjectsByType(className) {
const beanDefinitions = this.getDefinitionsByType(className);
const instances = PromiseUtil_1.PromiseUtil.map(beanDefinitions, (bd) => bd.getInstance());
return instances.then((arr) => {
arr.sort((a, b) => {
const aPos = Object.hasOwnProperty.call(a, 'getOrder') ? a.getOrder() : 0;
const bPos = Object.hasOwnProperty.call(b, 'getOrder') ? b.getOrder() : 0;
if (aPos === bPos) {
return 0;
}
else if (aPos < bPos) {
return -1;
}
return 1;
});
return arr;
});
}
async getObjectsByGroup(groupName) {
const objectNames = this.getGroupObjectNames(groupName);
const objectDefinitions = objectNames.map((objectName) => this.getDefinitionByName(objectName));
return await objectDefinitions.reduce(async (acum, def) => {
(await acum).push(await def.getInstance());
return Promise.resolve(acum);
}, Promise.resolve([]));
}
// ************************************
// Get Bean Definition Functions
// ************************************
getDefinitionByName(objectName) {
const val = this.objectDefinitions.get(objectName);
if (val) {
return val;
}
if (this.parentContext) {
return this.parentContext.getDefinitionByName(objectName);
}
throw new IoCException_1.IoCException(`No object definition with name ${objectName} registered in the context`);
}
getDefinitionByType(className) {
const resp = this.getDefinitionsByType(className);
if (resp.length > 1) {
throw new IoCException_1.IoCException(`Found more than one object definition in the context that produces a ${className}`);
}
return resp[0];
}
getDefinitionsByType(className, failOnMissing = true) {
const resp = new Map();
if (this.parentContext) {
this.parentContext
.getDefinitionsByType(className, false)
.forEach((objectDefinition) => resp.set(objectDefinition.getName(), objectDefinition));
}
this.objectDefinitions.forEach((objectDefinition) => {
if (objectDefinition.getProducedClass().name === className && objectDefinition.autowireCandidate) {
resp.set(objectDefinition.getName(), objectDefinition);
}
});
if (resp.size === 0 && failOnMissing) {
throw new IoCException_1.IoCException(`Couldn't find a bean that produces class ${className}`);
}
return Array.from(resp.values());
}
getDefinitionsByGroup(groupName) {
const objectNames = this.getGroupObjectNames(groupName);
return objectNames.map((objectName) => this.getDefinitionByName(objectName));
}
applyObjectDefinitionModifiers() {
// Let's allow the inspectors to modify the bean definitions
this.objectDefinitionInspector.forEach((inspector) => {
this.objectDefinitions.forEach((objectDefinition, key) => {
const result = inspector.inspect(objectDefinition);
if (result) {
this.objectDefinitions.set(key, result);
}
});
});
}
}
exports.Context = Context;
//# sourceMappingURL=Context.js.map