nope/lib/loader/nopePackageLoader.ts
Martin Karkowski b359adaf56 fixing formats
2020-11-24 15:14:56 +01:00

582 lines
18 KiB
TypeScript

/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2018-07-01 09:10:35
* @modify date 2020-11-23 08:14:00
* @desc [description]
*/
import { Container, injectable, interfaces } from "inversify";
import { flatten } from "lodash";
import "reflect-metadata";
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";
import { INopePackageLoader } from "../types/nope/nopePackageLoader.interface";
/**
* 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?.isDebugEnabled) {
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?.isDebugEnabled) {
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());
}
}
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 = [];
}
}