projects/wms-framework/src/lib/regionsframework/ModuleManager.ts
Default implementation for the module manager
Properties |
|
Methods |
|
constructor(moduleCatalog: IModuleCatalog, diContainer: DependencyContainer)
|
|||||||||
Parameters :
|
loadModulesCompleted |
Default value : new SubscriptionEvent()
|
Private modulesDictionary |
Type : literal type
|
Default value : null
|
Private modulesQueue |
Type : Array<string>
|
Default value : null
|
Private promisesDictionary |
Type : literal type
|
Default value : null
|
Private getFirstModuleInQueue |
getFirstModuleInQueue()
|
Gets the first module information object registered in the modules queue
Returns :
ModuleLoadingNode
the element representing a module to be loaded |
Private initializeModulesDictionary | ||||||
initializeModulesDictionary(modulesToCheck: Iterable<ModuleInfo>)
|
||||||
Parameters :
Returns :
void
|
Private loadModule | ||||||
loadModule(moduleInfo: ModuleInfo)
|
||||||
Parameters :
Returns :
Promise | null
|
Public LoadModule | ||||||
LoadModule(moduleName: string)
|
||||||
Loads the specified module
Parameters :
Returns :
void
|
Private loadQueue |
loadQueue()
|
Returns :
void
|
Private moduleHasDependencyWaitingDownload | ||||||
moduleHasDependencyWaitingDownload(module: ModuleLoadingNode)
|
||||||
Verifies if there is a pending promise to be resolved for a dependency of the given module
Parameters :
Returns :
boolean
{boolean} true if there is a promise to be resolved on a dependency of the given module |
Run |
Run()
|
Executes the module manager initialization
Returns :
void
|
Private triggerModulesLoadedIfRequired |
triggerModulesLoadedIfRequired()
|
Triggers the 'loadModulesCompleted if appropriate
Returns :
void
|
Private tryToStartModuleLoad | ||||||
tryToStartModuleLoad(module: ModuleLoadingNode)
|
||||||
Tries to start module loading code
Parameters :
Returns :
boolean
{boolean} a value to determine if a pending async operation must be resolved first |
import { DependencyContainer, inject, injectable } from 'tsyringe';
import { iuAny, iuWhere } from '../baseframework/collections';
import { TypeResolver } from '../helpers';
import { SubscriptionEvent } from '../utils';
import { ExternalFragmentRegistry } from './ExternalFragmentRegistry';
import { IModuleCatalog } from './IModuleCatalog';
import { ModuleInitializationMode } from './InitializationMode';
import {
containerInjectionToken,
moduleCatalogInjectionToken,
} from './injectionTokens';
import { ModuleInfo, ModuleState } from './ModuleInfo';
/**
* Interface for the module manager
*
* @export
* @interface IModuleManager
* @wInterface Microsoft.Practices.Prism.Modularity.IModuleManager
*/
export interface IModuleManager {
/**
* Execute the initialization of modules
*
* @memberof IModuleManager
*/
Run(): void;
/**
* Loads an `on demand` module
*
* @param {string} moduleName name of module to load
* @memberof IModuleManager
*/
LoadModule(moduleName: string): void;
}
/**
* Default implementation for the module manager
*
* @export
* @class ModuleManager
* @implements {IModuleManager}
* @wType Microsoft.Practices.Prism.Modularity.ModuleManager
*/
@injectable()
export class ModuleManager implements IModuleManager {
loadModulesCompleted = new SubscriptionEvent();
private modulesDictionary: { [id: string]: ModuleLoadingNode } = null;
private promisesDictionary: { [id: string]: any } = null;
private modulesQueue: Array<string> = null;
constructor(
@inject(moduleCatalogInjectionToken) private moduleCatalog: IModuleCatalog,
@inject(containerInjectionToken) private diContainer: DependencyContainer
) {}
/**
* Executes the module manager initialization
*
* @memberof ModuleManager
*/
Run(): void {
if (this.modulesQueue) {
throw new Error('Module queue not empty');
}
this.modulesQueue = [];
this.promisesDictionary = {};
this.initializeModulesDictionary(
iuWhere(
(obj) => obj.InitializationMode !== ModuleInitializationMode.OnDemand,
this.moduleCatalog.Modules
)
);
for (const key of Object.keys(this.modulesDictionary)) {
const obj = this.modulesDictionary[key];
if (Object.keys(obj.parents).length === 0) {
addToQueue(obj, this.modulesQueue);
}
}
if (
this.modulesQueue.length === 0 &&
Object.keys(this.modulesDictionary).length > 0
) {
// Review scenario where there are not any module registered
throw new Error('No module roots were found');
} else {
this.loadQueue();
}
}
/**
* Loads the specified module
*
* @param {string} moduleName
* @memberof ModuleManager
*/
public LoadModule(moduleName: string): void {
let locatedModuleInfo: ModuleLoadingNode = null;
this.initializeModulesDictionary(this.moduleCatalog.Modules);
locatedModuleInfo = this.modulesDictionary[moduleName];
if (
locatedModuleInfo &&
locatedModuleInfo.module.State === ModuleState.NotStarted
) {
this.modulesQueue = this.modulesQueue ?? [];
addToQueue(locatedModuleInfo, this.modulesQueue);
this.loadQueue();
}
}
private initializeModulesDictionary(
modulesToCheck: Iterable<ModuleInfo>
): void {
this.modulesDictionary = {};
for (const module of modulesToCheck) {
this.modulesDictionary[module.ModuleName] = {
parents: {},
children: {},
module,
name: module.ModuleName,
};
}
for (const moduleKey in this.modulesDictionary) {
if (this.modulesDictionary[moduleKey].module.DependsOn) {
const module = this.modulesDictionary[moduleKey];
for (const depModuleName of module.module.DependsOn) {
const foundModuleInfo = this.modulesDictionary[depModuleName];
if (foundModuleInfo) {
module.parents[depModuleName] = foundModuleInfo;
foundModuleInfo.children[moduleKey] = module;
} else {
throw new Error(
`Unable to find dependent module definition ${depModuleName} for ${module.module.ModuleName}`
);
}
}
}
}
}
private loadQueue(): void {
if (this.modulesQueue?.length > 0) {
while (this.modulesQueue.length > 0) {
const module = this.getFirstModuleInQueue();
if (module.module.State === ModuleState.NotStarted) {
if (this.tryToStartModuleLoad(module)) {
return;
}
} else if (module.module.State === ModuleState.Initializing) {
if (this.promisesDictionary[module.name]) {
this.promisesDictionary[module.name].then(() => {
this.loadQueue();
});
return;
}
} else {
console.log('Skipping loading for ' + this.modulesQueue[0]);
}
}
} else {
this.triggerModulesLoadedIfRequired();
}
}
/**
* Gets the first module information object registered in the modules queue
*
* @private
* @return {*} the element representing a module to be loaded
* @memberof ModuleManager
*/
private getFirstModuleInQueue(): ModuleLoadingNode {
const module = this.modulesDictionary[this.modulesQueue[0]];
this.modulesQueue = this.modulesQueue.splice(1);
if (!module) {
throw new Error(
`Fatal error: module information missing for ${this.modulesQueue[0]}`
);
}
return module;
}
/**
* Tries to start module loading code
*
* @private
* @param {ModuleLoadingNode} module
* @return {*} {boolean} a value to determine if a pending async operation must be resolved first
* @memberof ModuleManager
*/
private tryToStartModuleLoad(module: ModuleLoadingNode): boolean {
let operationInProgress = false;
if (this.moduleHasDependencyWaitingDownload(module)) {
this.modulesQueue = [module.name, ...this.modulesQueue];
operationInProgress = true;
} else {
module.module.State = ModuleState.Initializing;
const loadresult = this.loadModule(module.module);
if (loadresult) {
this.promisesDictionary[module.name] = loadresult;
this.promisesDictionary[module.name].then(() => {
this.loadQueue();
});
operationInProgress = true;
}
}
return operationInProgress;
}
/**
* Triggers the 'loadModulesCompleted if appropriate
*
* @private
* @memberof ModuleManager
*/
private triggerModulesLoadedIfRequired() {
if (
!this.promisesDictionary ||
Object.getOwnPropertyNames(this.promisesDictionary).length === 0
) {
this.modulesQueue = null;
this.loadModulesCompleted.fire([]);
}
}
/**
* Verifies if there is a pending promise to be resolved for a dependency of the given module
*
* @private
* @param {ModuleLoadingNode} module
* @return {*} {boolean} true if there is a promise to be resolved on a dependency of the given module
* @memberof ModuleManager
*/
private moduleHasDependencyWaitingDownload(
module: ModuleLoadingNode
): boolean {
return (
this.promisesDictionary &&
module.module.DependsOn &&
iuAny(
module.module.DependsOn,
(moduleName) => moduleName in this.promisesDictionary
)
);
}
private loadModule(moduleInfo: ModuleInfo): Promise<unknown> | null {
moduleInfo.State = ModuleState.Initializing;
if (moduleInfo.Ref) {
const importfunc =
ExternalFragmentRegistry.registrations[moduleInfo.ModuleName] ??
ExternalFragmentRegistry.registrations[
moduleInfo.Ref.replace(/^.*\/([^/]+)\.xap$/, '$1')
] ??
ExternalFragmentRegistry.registrations[
moduleInfo.Ref.replace(/^([^/]+)\.xap$/, '$1')
];
if (importfunc) {
return importfunc().then((m) => {
console.log('Initializing module: ' + moduleInfo.ModuleType);
const theClass =
m[moduleInfo.ModuleType.replace(/^[^,]*\.([^\.,]+)\,.*$/, '$1')];
if (!theClass) {
console.error(
'Could not resolve class name for module type: ' +
moduleInfo.ModuleType
);
}
const moduleInstance = this.diContainer.resolve(theClass) as any;
moduleInstance.Initialize();
moduleInfo.State = ModuleState.Initialized;
delete this.promisesDictionary[moduleInfo.ModuleName];
});
} else {
console.error(
`Unable to find module registration for ${moduleInfo.ModuleName}, type: ${moduleInfo.ModuleType}, ref: ${moduleInfo.Ref}`
);
}
} else if (moduleInfo.RuntimeTypeInfo) {
const moduleInstance = this.diContainer.resolve(
moduleInfo.RuntimeTypeInfo.JSType
) as any;
moduleInstance.Initialize();
moduleInfo.State = ModuleState.Initialized;
delete this.promisesDictionary[moduleInfo.ModuleName];
} else if (!moduleInfo.RuntimeTypeInfo && moduleInfo.ModuleType) {
const theClassName = moduleInfo.ModuleType.split(',')[0];
const theClass = TypeResolver.getClassType(theClassName);
if (!theClass) {
console.error(
'Could not resolve class name for module type: ' +
moduleInfo.ModuleType
);
return null;
}
const moduleInstance = this.diContainer.resolve(theClass) as any;
moduleInstance.Initialize();
moduleInfo.State = ModuleState.Initialized;
delete this.promisesDictionary[moduleInfo.ModuleName];
} else {
return null;
}
}
}
function addToQueue(obj: ModuleLoadingNode, queue: Array<string>): void {
if (!queue.includes(obj.name)) {
for (const parent of Object.keys(obj.parents)) {
addToQueue(obj.parents[parent], queue);
}
if (!queue.includes(obj.name)) {
queue.push(obj.name);
}
for (const child of Object.keys(obj.children)) {
if (
obj.children[child].module.InitializationMode !=
ModuleInitializationMode.OnDemand
) {
addToQueue(obj.children[child], queue);
}
}
}
}
type ModuleLoadingNode = {
parents: { [id: string]: ModuleLoadingNode };
children: { [id: string]: ModuleLoadingNode };
module: ModuleInfo;
name: string;
};