File

projects/wms-framework/src/lib/regionsframework/ModuleManager.ts

Description

Default implementation for the module manager

Implements

IModuleManager

Index

Properties
Methods

Constructor

constructor(moduleCatalog: IModuleCatalog, diContainer: DependencyContainer)
Parameters :
Name Type Optional
moduleCatalog IModuleCatalog No
diContainer DependencyContainer No

Properties

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

Methods

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 :
Name Type Optional
modulesToCheck Iterable<ModuleInfo> No
Returns : void
Private loadModule
loadModule(moduleInfo: ModuleInfo)
Parameters :
Name Type Optional
moduleInfo ModuleInfo No
Returns : Promise | null
Public LoadModule
LoadModule(moduleName: string)

Loads the specified module

Parameters :
Name Type Optional
moduleName string No
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 :
Name Type Optional
module ModuleLoadingNode No
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 :
Name Type Optional
module ModuleLoadingNode No
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;
};

result-matching ""

    No results matching ""