nope/lib/loader/nopePackageLoader.ts

712 lines
22 KiB
TypeScript
Raw Normal View History

2020-11-06 13:19:16 +00:00
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @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";
2020-11-23 06:09:31 +00:00
import { arraysEqual } from "../helpers/arrayMethods";
2021-09-04 11:45:52 +00:00
import { sleep } from "../helpers/async";
2020-11-23 06:09:31 +00:00
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 {
2022-06-24 05:45:01 +00:00
IClassDescriptor,
INopeActivationHanlder,
2021-12-04 07:25:26 +00:00
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
/**
* 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>
2021-11-12 07:57:03 +00:00
): Promise<void> {
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;
2021-11-12 07:57:03 +00:00
public get dispatcher(): INopeDispatcher {
// 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
*/
2021-11-12 07:57:03 +00:00
public addContainers(container: Container): void {
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
*/
2021-11-12 07:57:03 +00:00
protected _onActivation(_context: interfaces.Context, _element: any): any {
2021-12-04 07:25:26 +00:00
// Perform the Handlers on the Object
for (const _handler of this._actionHandlers.values()) {
_element = _handler(_context, _element);
}
return _element;
}
2022-06-24 05:45:01 +00:00
public availableElements: IClassDescriptor[];
2022-06-24 05:45:01 +00:00
protected _compareElements(
_a: IClassDescriptor,
_b: IClassDescriptor
): boolean {
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
2022-06-24 05:45:01 +00:00
* @param {IClassDescriptor} _item
* @return {*} {boolean}
* @memberof NopePackageLoader
*/
2022-06-24 05:45:01 +00:00
protected _hasElement(_item: IClassDescriptor): 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
*
2022-06-24 05:45:01 +00:00
* @param {IClassDescriptor[]} _elements Definition containing the Elements that should be added
* @memberof NopePackageLoader
*/
public async addDescription(
2022-06-24 05:45:01 +00:00
_elements: IClassDescriptor[],
_instance: NopePackageLoader | null = null
2021-11-12 07:57:03 +00:00
): Promise<void> {
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
2022-06-24 05:45:01 +00:00
* @param {IClassDescriptor} _element
* @param {NopePackageLoader} _instance
* @memberof NopePackageLoader
*/
protected _linkExternalBuilder(
2022-06-24 05:45:01 +00:00
_element: IClassDescriptor,
_instance: NopePackageLoader
2021-11-12 07:57:03 +00:00
): void {
2021-12-04 07:25:26 +00:00
// 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
>;
}
2021-12-04 07:25:26 +00:00
// 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
2022-06-24 05:45:01 +00:00
* @param {IClassDescriptor} _element
* @memberof NopePackageLoader
*/
2022-06-24 05:45:01 +00:00
protected _addElement(_element: IClassDescriptor): void {
if (_element) {
2021-11-12 07:57:03 +00:00
const _this = this;
const _dict = {
whenTargetTagged: "getTagged",
2021-12-04 07:25:26 +00:00
whenTargetNamed: "getNamed",
};
2021-11-12 07:57:03 +00:00
// Define Option if required
// And use the line below to update them and
// put them into the correct format.
if (!_element.options) {
_element.options = {};
}
if (_element.selector) {
if (!Array.isArray(_element.selector)) {
_element.selector = [_element.selector];
}
} else {
_element.selector = [];
}
if (
_element.factorySelector &&
!Array.isArray(_element.factorySelector)
) {
_element.factorySelector = [_element.factorySelector];
}
2021-11-12 07:57:03 +00:00
// Select the Method
const _method = _element.options.toConstant ? "toConstantValue" : "to";
2021-11-12 07:57:03 +00:00
// Check if a specific Scope is given
if (_element.options.scope != undefined) {
2021-11-12 07:57:03 +00:00
// A specified Scope for Inversify is given
if (_element.options.addition) {
2021-11-12 07:57:03 +00:00
// Add all instances.
2021-09-04 11:45:52 +00:00
for (const [_index, _selector] of (
_element.selector as Array<symbol | string>
).entries()) {
2021-11-12 07:57:03 +00:00
// Firstly bind to the First Selector
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
2021-12-04 07:25:26 +00:00
[_method](_element.type)
[_element.options.scope]()
[_element.options.addition.name](
..._element.options.addition.args
)
.onActivation((_context, _element) => {
2021-11-12 07:57:03 +00:00
return _this._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
2021-12-04 07:25:26 +00:00
// Afterwards redirect to the Se
this.container.bind(_selector).toDynamicValue((context) => {
2021-12-04 07:25:26 +00:00
// 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 {
2021-12-04 07:25:26 +00:00
// Add all instances.
2021-09-04 11:45:52 +00:00
for (const [_index, _selector] of (
_element.selector as Array<symbol | string>
).entries()) {
2021-12-04 07:25:26 +00:00
// Firstly bind to the First Selector
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
2021-12-04 07:25:26 +00:00
[_method](_element.type)
[_element.options.scope as string]()
.onActivation((_context, _element) => {
2021-11-12 07:57:03 +00:00
return _this._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
2021-11-12 07:57:03 +00:00
// Afterwards redirect to the Get the Element with the First Tag.
this.container.bind(_selector).toDynamicValue((context) => {
2021-12-04 07:25:26 +00:00
// Create the First Element and return it.
return context.container.get(
(_element.selector as Array<symbol | string>)[0]
);
});
}
}
}
} else {
2021-12-04 07:25:26 +00:00
// No Scope is given
if (_element.options.addition) {
2021-12-04 07:25:26 +00:00
// Add all instances.
2021-09-04 11:45:52 +00:00
for (const [_index, _selector] of (
_element.selector as Array<symbol | string>
).entries()) {
2021-12-04 07:25:26 +00:00
// Firstly bind to the First Selector
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
2021-12-04 07:25:26 +00:00
[_method](_element.type)
[_element.options.addition.name as string](
..._element.options.addition.args
)
.onActivation((_context, _element) => {
2021-11-12 07:57:03 +00:00
return _this._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
2021-12-04 07:25:26 +00:00
// Afterwards redirect to the Se
this.container.bind(_selector).toDynamicValue((context) => {
2021-12-04 07:25:26 +00:00
// 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 {
2021-12-04 07:25:26 +00:00
// Add all instances.
2021-09-04 11:45:52 +00:00
for (const [_index, _selector] of (
_element.selector as Array<symbol | string>
).entries()) {
2021-12-04 07:25:26 +00:00
// Firstly bind to the First Selector
if (_index === 0) {
this._logger.debug("adding selector", _selector.toString());
this.container
.bind(_selector)
2021-12-04 07:25:26 +00:00
[_method](_element.type)
.onActivation((_context, _element) => {
2021-11-12 07:57:03 +00:00
return _this._onActivation(_context, _element);
});
} else {
this._logger.debug("adding selector", _selector.toString());
2021-11-12 07:57:03 +00:00
// Afterwards redirect to the Get the Element with the First Tag.
this.container.bind(_selector).toDynamicValue((context) => {
2021-12-04 07:25:26 +00:00
// Create the First Element and return it.
return context.container.get(
(_element.selector as Array<symbol | string>)[0]
);
});
}
}
}
}
2021-12-04 07:25:26 +00:00
// Add the Factory if neccessary
if (_element.factorySelector) {
2021-12-04 07:25:26 +00:00
// 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)) {
2021-11-12 07:57:03 +00:00
// TODO
} 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)
2021-12-04 07:25:26 +00:00
[_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)) {
2021-11-12 07:57:03 +00:00
// TODO
} 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 () => {
2021-12-04 07:25:26 +00:00
// Call get function on the Container
return context.container[
_dict[_element.options.addition.name]
](
2021-12-04 07:25:26 +00:00
// Return the First Element
(_element.selector as Array<symbol | string>)[0],
..._element.options.addition.args
);
};
})
2021-12-04 07:25:26 +00:00
[_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> {
2021-12-04 07:25:26 +00:00
// Generate the container
this.container = new Container();
const _globalConfig: any = {};
this.container.bind("global.config").toConstantValue(_globalConfig);
2022-06-24 05:45:01 +00:00
this.availableElements = new Array<IClassDescriptor>();
}
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(
2021-12-04 07:25:26 +00:00
'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.instanceManager.registerConstructor(
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.rpcManager.registerService(
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(
2021-12-04 07:25:26 +00:00
'Requesting Generating Instance "' +
definition.options.identifier +
'" of type "' +
definition.options.type +
'"'
);
}
const instance = await this.dispatcher.instanceManager.createInstance(
definition.options
);
2021-10-18 06:02:40 +00:00
if (this._logger) {
this._logger.info(
2021-12-04 07:25:26 +00:00
'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
2021-08-17 15:52:46 +00:00
if (definition.options.identifier in this.packages[name].autostart) {
2021-02-12 14:54:57 +00:00
// There are autostart Tasks in the Package for the considered Instance,
// which has been recently defined.
try {
2021-09-04 11:45:52 +00:00
const autostart =
this.packages[name].autostart[definition.options.identifier];
2021-08-17 15:52:46 +00:00
for (const task of autostart) {
if (task.delay) {
2021-02-12 14:54:57 +00:00
await sleep(task.delay);
}
await instance[task.service](...task.params);
2021-02-12 14:54:57 +00:00
}
2021-08-17 15:52:46 +00:00
} catch (e) {
2021-09-04 11:45:52 +00:00
this._logger.error(
"Failed with autostart tasks for " + instance.identifier
);
2021-02-12 14:54:57 +00:00
}
} 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 = [];
}
}