nope/lib/loader/nopePackageLoader.ts

704 lines
22 KiB
TypeScript
Raw Normal View History

2020-11-06 13:19:16 +00:00
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2018-07-01 09:10:35
* @modify date 2021-02-12 15:56:12
2020-11-06 13:19:16 +00:00
* @desc [description]
*/
2020-11-06 13:19:16 +00:00
2020-11-23 06:09:31 +00:00
import { Container, injectable, interfaces } from "inversify";
import { flatten } from "lodash";
2020-11-07 00:45:20 +00:00
import "reflect-metadata";
2021-02-12 14:54:57 +00:00
import { promisify } from "util";
2020-11-23 06:09:31 +00:00
import { arraysEqual } from "../helpers/arrayMethods";
import { RUNNINGINNODE } from "../helpers/runtimeMethods";
import { getNopeLogger } from "../logger/getLogger";
import { DISPATCHER_INSTANCE } from "../symbols/identifiers";
import { INopeDispatcher } from "../types/nope/nopeDispatcher.interface";
import { INopeModule } from "../types/nope/nopeModule.interface";
import {
IDescriptor,
INopeActivationHanlder,
IPackageDescription
} from "../types/nope/nopePackage.interface";
2020-11-23 06:09:31 +00:00
import { INopePackageLoader } from "../types/nope/nopePackageLoader.interface";
2020-11-06 13:19:16 +00:00
2021-02-12 14:54:57 +00:00
const sleep = promisify(setTimeout);
2020-11-06 13:19:16 +00:00
/**
* Helper Class to Build an inversify Container.
*
* @export
* @class NopePackageLoader
* @implements {INopePackageLoader}
*/
@injectable()
export class NopePackageLoader implements INopePackageLoader {
/**
* Array containing multipl Activation Handlers
*
* @protected
* @memberof NopePackageLoader
*/
protected _actionHandlers = new Map<string, INopeActivationHanlder>();
public _logger = getNopeLogger("package-loader", "debug");
public get activationHandlers() {
return Array.from(this._actionHandlers.values());
}
/**
* Adds an Activation Handler. (Those are called, after an Object has been created)
*
* @param {(context: interfaces.Context, element: any) => any} func The Corresponding Method which will be called.
* @memberof NopePackageLoader
*/
public async addActivationHandler(
func: INopeActivationHanlder | Array<INopeActivationHanlder>
) {
if (!Array.isArray(func)) {
func = [func];
}
for (const _func of func) {
const _name = _func.name;
if (!this._actionHandlers.has(_name))
this._actionHandlers.set(_name, _func);
else {
this._logger.warn("Trying to Add Activation Handler twice!");
}
}
}
private _dispatcher: INopeDispatcher = null;
public get dispatcher() {
// Define a Lazy - Getter for an Modul:
if (this._dispatcher === null) {
this._dispatcher = this.container.get(DISPATCHER_INSTANCE);
}
return this._dispatcher;
}
/**
* Adds the Container to the given Container.
*
* @param {Container} container the Container, that should be merged
* @memberof NopePackageLoader
*/
public addContainers(container: Container) {
this.container = Container.merge(this.container, container) as Container;
}
/**
* Function which will perform all Activation Handlers.
*
* @protected
* @param {interfaces.Context} _context
* @param {*} _element
* @returns
* @memberof NopePackageLoader
*/
protected _onActivation(_context: interfaces.Context, _element: any) {
/** Perform the Handlers on the Object */
for (const _handler of this._actionHandlers.values()) {
_element = _handler(_context, _element);
}
return _element;
}
public availableElements: IDescriptor[];
protected _compareElements(_a: IDescriptor, _b: IDescriptor) {
if (
_a.options &&
_a.options.addition &&
_b.options &&
_b.options.addition
) {
return (
_b.options.addition.name === _a.options.addition.name &&
arraysEqual(_b.options.addition.args, _a.options.addition.args)
);
}
return true;
}
/**
* Internal Method to Test, whether an Element exists or not.
*
* @protected
* @param {IDescriptor} _item
* @return {*} {boolean}
* @memberof NopePackageLoader
*/
protected _hasElement(_item: IDescriptor): boolean {
for (const _element of this.availableElements) {
try {
if (Array.isArray(_element.selector)) {
if (
arraysEqual(
_element.selector,
Array.isArray(_item.selector) ? _item.selector : [_item.selector]
) &&
this._compareElements(_element, _item)
) {
return true;
}
} else if (
!Array.isArray(_item.selector) &&
_element.selector === _item.selector &&
this._compareElements(_element, _item)
) {
return true;
} else if (
_item.factorySelector &&
_item.factorySelector === _element.factorySelector &&
this._compareElements(_element, _item)
) {
return true;
}
} catch (e) {
this._logger.error(
e,
"Error During Check",
_item.factorySelector,
_element.factorySelector
);
}
}
return false;
}
/**
* Method to add an Element to the Build
*
* @param {IDescriptor[]} _elements Definition containing the Elements that should be added
* @memberof NopePackageLoader
*/
public async addDescription(
_elements: IDescriptor[],
_instance: NopePackageLoader | null = null
) {
if (_instance === null) {
for (const _element of _elements) {
if (!this._hasElement(_element)) {
this.availableElements.push(_element);
this._addElement(_element);
} else {
this._logger.warn("Using the Same Selector / Factory of", _element);
if (RUNNINGINNODE) {
this.availableElements.push(_element);
this._addElement(_element);
}
}
}
} else {
for (const _element of _elements) {
if (!this._hasElement(_element)) {
this.availableElements.push(_element);
this._linkExternalBuilder(_element, _instance);
} else {
this._logger.warn("Using the Same Selector / Factory of", _element);
}
}
}
}
/**
* Internal Helper Function to Merge an external Builder for creating Tasks.
*
* @protected
* @param {IDescriptor} _element
* @param {NopePackageLoader} _instance
* @memberof NopePackageLoader
*/
protected _linkExternalBuilder(
_element: IDescriptor,
_instance: NopePackageLoader
) {
/** Bind Factory */
if (!Array.isArray(_element.selector)) {
_element.selector = [_element.selector] as Array<symbol | string>;
}
if (_element.factorySelector && !Array.isArray(_element.factorySelector)) {
_element.factorySelector = [_element.factorySelector] as Array<
symbol | string
>;
}
/** Add al instances. */
for (const _selector of _element.selector as
| Array<symbol>
| Array<string>) {
this.container.bind(_selector).toDynamicValue(() => {
return _instance.container.get(_selector);
});
}
for (const _selector of _element.factorySelector as
| Array<symbol>
| Array<string>) {
this.container.bind(_selector).toDynamicValue(() => {
return _instance.container.get(_selector);
});
}
}
/**
* Internal Funcitont to add an Description.
*
* @protected
* @param {IDescriptor} _element
* @memberof NopePackageLoader
*/
protected _addElement(_element: IDescriptor): void {
if (_element) {
const _self = this;
const _dict = {
whenTargetTagged: "getTagged",
whenTargetNamed: "getNamed"
};
/** Define Option if required */
if (!_element.options) {
_element.options = {};
}
if (_element.selector) {
/** Make an Array */
if (!Array.isArray(_element.selector)) {
_element.selector = [_element.selector];
}
} else {
_element.selector = [];
}
/** Make an Array */
if (
_element.factorySelector &&
!Array.isArray(_element.factorySelector)
) {
_element.factorySelector = [_element.factorySelector];
}
/** Select the Method */
const _method = _element.options.toConstant ? "toConstantValue" : "to";
/** Check if a specific Scope is given */
if (_element.options.scope != undefined) {
/** A specified Scope for Inversify is given */
if (_element.options.addition) {
/** Add all instances. */
for (const [_index, _selector] of (_element.selector as Array<
symbol | string
>).entries()) {
/** Firstly bind to the First Selector */
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
[_method](_element.type)
[_element.options.scope]()
[_element.options.addition.name](
..._element.options.addition.args
)
.onActivation((_context, _element) => {
return _self._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
/** Afterwards redirect to the Se */
this.container.bind(_selector).toDynamicValue((context) => {
/** Create the First Element and return it. */
return context.container[_dict[_element.options.addition.name]](
(_element.selector as Array<symbol | string>)[0],
..._element.options.addition.args
);
});
}
}
} else {
/** Add all instances. */
for (const [_index, _selector] of (_element.selector as Array<
symbol | string
>).entries()) {
/** Firstly bind to the First Selector */
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
[_method](_element.type)
[_element.options.scope as string]()
.onActivation((_context, _element) => {
return _self._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
/** Afterwards redirect to the Get the Element with the First Tag.*/
this.container.bind(_selector).toDynamicValue((context) => {
/** Create the First Element and return it. */
return context.container.get(
(_element.selector as Array<symbol | string>)[0]
);
});
}
}
}
} else {
/** No Scope is given */
if (_element.options.addition) {
/** Add all instances. */
for (const [_index, _selector] of (_element.selector as Array<
symbol | string
>).entries()) {
/** Firstly bind to the First Selector */
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
[_method](_element.type)
[_element.options.addition.name as string](
..._element.options.addition.args
)
.onActivation((_context, _element) => {
return _self._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
/** Afterwards redirect to the Se */
this.container.bind(_selector).toDynamicValue((context) => {
/** Create the First Element and return it. */
return context.container[_dict[_element.options.addition.name]](
(_element.selector as Array<symbol | string>)[0],
..._element.options.addition.args
);
});
}
}
} else {
/** Add all instances. */
for (const [_index, _selector] of (_element.selector as Array<
symbol | string
>).entries()) {
/** Firstly bind to the First Selector */
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
[_method](_element.type)
.onActivation((_context, _element) => {
return _self._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
/** Afterwards redirect to the Get the Element with the First Tag.*/
this.container.bind(_selector).toDynamicValue((context) => {
/** Create the First Element and return it. */
return context.container.get(
(_element.selector as Array<symbol | string>)[0]
);
});
}
}
}
}
/** Add the Factory if neccessary */
if (_element.factorySelector) {
/** Generate a Dict for converting the Instanciation Values to the Factory Methods. */
if (_element.options.factoryCallback) {
if (_element.options.addition) {
if (Array.isArray(_element.options.addition)) {
} else {
const firstArg = _element.options.addition.args[0];
const secondArg = _element.options.addition.args[1];
for (const _selector of _element.factorySelector as Array<
symbol | string
>) {
this.container
.bind(_selector)
.toFactory(_element.options.factoryCallback)
[_element.options.addition.name](firstArg, secondArg);
}
}
} else {
for (const _selector of _element.factorySelector as Array<
symbol | string
>) {
console.log(_selector, _element);
this.container
.bind(_selector)
.toFactory(_element.options.factoryCallback);
}
}
} else {
if (_element.options.addition) {
if (Array.isArray(_element.options.addition)) {
} else {
const firstArg = _element.options.addition.args[0];
const secondArg = _element.options.addition.args[1];
for (const _selector of _element.factorySelector as Array<
symbol | string
>) {
this.container
.bind(_selector)
.toFactory((context: interfaces.Context) => {
return () => {
/** Call get function on the Container */
return context.container[
_dict[_element.options.addition.name]
](
/** Return the First Element */
(_element.selector as Array<symbol | string>)[0],
..._element.options.addition.args
);
};
})
[_element.options.addition.name](firstArg, secondArg);
}
}
} else {
for (const _selector of _element.factorySelector as Array<
symbol | string
>) {
this.container
.bind(_selector)
.toFactory((context: interfaces.Context) => {
return () => {
return context.container.get(
(_element.selector as Array<symbol | string>)[0]
);
};
});
}
}
}
}
}
}
/**
* The Inversify-Container see https://github.com/inversify/InversifyJS
*
* @type {Container}
* @memberof NopePackageLoader
*/
public container: Container;
public async reset(): Promise<void> {
/** Generate the container */
this.container = new Container();
const _globalConfig: any = {};
this.container.bind("global.config").toConstantValue(_globalConfig);
this.availableElements = new Array<IDescriptor>();
}
constructor() {
this.reset();
}
public packages: { [index: string]: IPackageDescription<any> } = {};
protected _instances = new Map<string | symbol, number>();
/**
* Loader Function. This function will register all provided functions,
* create the desired instances. Additionally it will add all descriptors.
*
* @param {IPackageDescription<any>} element
* @memberof NopePackageLoader
*/
async addPackage(element: IPackageDescription<any>): Promise<void> {
if (this.packages[element.nameOfPackage] !== undefined) {
throw Error(
"Already loaded a Package with the name \"" +
element.nameOfPackage +
"\" !"
);
}
this._logger.info("loading package " + element.nameOfPackage);
// Store the Package
this.packages[element.nameOfPackage] = element;
// Firstly add all Descriptors:
await this.addDescription(
element.providedClasses.map((item) => item.description)
);
// Load the Activation Handlers:
await this.addActivationHandler(element.activationHandlers);
const _this = this;
// Based on the provided settings register a generator Function for the Instances:
for (const cl of element.providedClasses) {
// Based on the Defintion extract the corresponding selector:
let selector: string | symbol = null;
let factory = false;
if (cl.description.factorySelector) {
// Firstly try to use a Factory Selector => new instances are generated instead of only
// one singleton Object.
selector = Array.isArray(cl.description.factorySelector)
? cl.description.factorySelector[0]
: cl.description.factorySelector;
factory = true;
} else if (cl.description.selector) {
// Otherwise select the Selector of the Singleton.
selector = Array.isArray(cl.description.selector)
? cl.description.selector[0]
: cl.description.selector;
}
if (selector && cl.settings?.allowInstanceGeneration) {
this._logger.info(
"Adding an instance generator for " + cl.description.name
);
// Register an Instance Generator, only if allowed
await this.dispatcher.provideInstanceGeneratorForExternalDispatchers(
cl.description.name,
async (_, identifier) => {
const currentAmount = _this._instances.get(selector) || 0;
if (
cl.settings.allowInstanceGeneration &&
currentAmount < (cl.settings.maxAmountOfInstance || Infinity)
) {
// Define the Instance:
const instance = factory
? _this.container.get<() => INopeModule>(selector)()
: _this.container.get<INopeModule>(selector);
instance.identifier = identifier;
// Update the Used Instance
_this._instances.set(selector, currentAmount + 1);
// Return the instance.
return instance;
}
throw Error("Not allowed to create instances");
}
);
}
}
// Iterate over the provided Functions:
for (const func of element.providedFunctions) {
await this.dispatcher.registerFunction(func.function, func.options);
}
}
/**
* Function to initialize all the instances.
*
* @param {boolean} [testRequirements=true]
* @memberof NopePackageLoader
*/
async generateInstances(testRequirements = true): Promise<void> {
const _this = this;
if (this._logger) {
this._logger.info("Package Loader generates the instances.");
}
if (testRequirements) {
const availablePackages = Object.getOwnPropertyNames(this.packages);
// First extract all required Packages
const reuqiredPackages = Array.from(
new Set(
flatten(
availablePackages.map(
(name) => _this.packages[name].requiredPackages
)
)
)
);
// Now Check if every Package is present.
reuqiredPackages.map((_package) => {
if (!availablePackages.includes(_package)) {
throw Error("Packages are not known");
}
});
}
for (const name in this.packages) {
const definitions = this.packages[name].defaultInstances;
for (const definition of definitions) {
if (this._logger?.enabledFor) {
this._logger.debug(
"Requesting Generating Instance \"" +
definition.options.identifier +
"\" of type \"" +
definition.options.type +
"\""
);
}
const instance = await this.dispatcher.generateInstance(
definition.options
);
if (this._logger?.enabledFor) {
this._logger.debug(
"Sucessfully Generated the Instance \"" +
definition.options.identifier +
"\" of type \"" +
definition.options.type +
"\""
);
}
// Store the Function, that the instance will be disposed on leaving.
this._disposeDefaultInstance.push(() => instance.dispose());
2021-02-12 14:54:57 +00:00
if (definition.options.identifier in this.packages[name].autostart){
// There are autostart Tasks in the Package for the considered Instance,
// which has been recently defined.
try {
const autostart = this.packages[name].autostart[definition.options.identifier];
for (const task of autostart ){
if (task.delay){
await sleep(task.delay);
}
await instance[task.service](task.params);
}
} catch(e) {
this._logger.error("Failed with autostart tasks for " + instance.identifier);
}
} else {
this._logger.info("No autostart for " + instance.identifier);
2021-02-12 14:54:57 +00:00
}
}
}
if (this._logger) {
this._logger.info("generated all defined Instances");
}
}
protected _disposeDefaultInstance: Array<() => Promise<void>> = [];
async dispose(): Promise<void> {
// Start all dispose Values
const promises = this._disposeDefaultInstance.map((cb) => cb());
// Wait for all to finish!
await Promise.all(promises);
this._disposeDefaultInstance = [];
}
}