From 28b077b84efa378bda7a3c3041d99ee573be9511 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Thu, 25 Feb 2021 17:07:30 +0100 Subject: [PATCH] feat: Create initializer to instantiate dynamic pods --- src/index.ts | 1 + src/init/ConfigPodInitializer.ts | 47 +++++++++++++++++++++ test/unit/init/ConfigPodInitializer.test.ts | 44 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/init/ConfigPodInitializer.ts create mode 100644 test/unit/init/ConfigPodInitializer.test.ts diff --git a/src/index.ts b/src/index.ts index d015f9981..1facec8e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ export * from './authorization/WebAclAuthorizer'; // Init export * from './init/AclInitializer'; export * from './init/CliRunner'; +export * from './init/ConfigPodInitializer'; export * from './init/Initializer'; export * from './init/LoggerInitializer'; export * from './init/RootContainerInitializer'; diff --git a/src/init/ConfigPodInitializer.ts b/src/init/ConfigPodInitializer.ts new file mode 100644 index 000000000..7704a4ddb --- /dev/null +++ b/src/init/ConfigPodInitializer.ts @@ -0,0 +1,47 @@ +import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; +import { getLoggerFor } from '../logging/LogUtil'; +import type { ComponentsJsFactory } from '../pods/generate/ComponentsJsFactory'; +import { TEMPLATE, TEMPLATE_VARIABLE } from '../pods/generate/variables/Variables'; +import type { KeyValueStorage } from '../storage/keyvalue/KeyValueStorage'; +import type { ResourceStore } from '../storage/ResourceStore'; +import { Initializer } from './Initializer'; + +/** + * Initializes all pods that have been stored and loads them in memory. + * This reads the pod settings from a permanent storage and uses those + * to create the corresponding ResourceStores in memory, + * so this is required every time the server starts. + * + * Part of the dynamic pod creation. + * Reads the contents from the configuration storage, uses those values to instantiate ResourceStores, + * and then adds them to the routing storage. + * @see {@link ConfigPodManager}, {@link TemplatedPodGenerator}, {@link BaseUrlRouterRule} + */ +export class ConfigPodInitializer extends Initializer { + protected readonly logger = getLoggerFor(this); + private readonly storeFactory: ComponentsJsFactory; + private readonly configStorage: KeyValueStorage; + private readonly routingStorage: KeyValueStorage; + + public constructor(storeFactory: ComponentsJsFactory, + configStorage: KeyValueStorage, + routingStorage: KeyValueStorage) { + super(); + this.storeFactory = storeFactory; + this.configStorage = configStorage; + this.routingStorage = routingStorage; + } + + public async handle(): Promise { + let count = 0; + for await (const [ path, value ] of this.configStorage.entries()) { + const config = value as NodeJS.Dict; + const store: ResourceStore = + await this.storeFactory.generate(config[TEMPLATE_VARIABLE.templateConfig]!, TEMPLATE.ResourceStore, config); + await this.routingStorage.set({ path }, store); + this.logger.debug(`Initialized pod at ${path}`); + count += 1; + } + this.logger.info(`Initialized ${count} dynamic pods.`); + } +} diff --git a/test/unit/init/ConfigPodInitializer.test.ts b/test/unit/init/ConfigPodInitializer.test.ts new file mode 100644 index 000000000..c36b5e6cf --- /dev/null +++ b/test/unit/init/ConfigPodInitializer.test.ts @@ -0,0 +1,44 @@ +import { ConfigPodInitializer } from '../../../src/init/ConfigPodInitializer'; +import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; +import type { ComponentsJsFactory } from '../../../src/pods/generate/ComponentsJsFactory'; +import { TEMPLATE, TEMPLATE_VARIABLE } from '../../../src/pods/generate/variables/Variables'; +import type { KeyValueStorage } from '../../../src/storage/keyvalue/KeyValueStorage'; +import type { ResourceStore } from '../../../src/storage/ResourceStore'; + +describe('A ConfigPodInitializer', (): void => { + let storeFactory: ComponentsJsFactory; + let configStorage: KeyValueStorage; + let routingStorage: KeyValueStorage; + let initializer: ConfigPodInitializer; + const identifierA = { path: 'http://test.com/A' }; + const identifierB = { path: 'http://test.com/B' }; + const configA = { [TEMPLATE_VARIABLE.templateConfig]: 'templateA' }; + const configB = { [TEMPLATE_VARIABLE.templateConfig]: 'templateB' }; + + beforeEach(async(): Promise => { + storeFactory = { + generate: jest.fn().mockResolvedValue('store'), + } as any; + + configStorage = new Map() as any; + await configStorage.set(identifierA.path, configA); + await configStorage.set(identifierB.path, configB); + + const map = new Map(); + routingStorage = { + get: async(identifier: ResourceIdentifier): Promise => map.get(identifier.path), + set: async(identifier: ResourceIdentifier, value: ResourceStore): Promise => map.set(identifier.path, value), + } as any; + + initializer = new ConfigPodInitializer(storeFactory, configStorage, routingStorage); + }); + + it('generates a pod for every entry in the config storage.', async(): Promise => { + await initializer.handle(); + expect(storeFactory.generate).toHaveBeenCalledTimes(2); + expect(storeFactory.generate).toHaveBeenCalledWith('templateA', TEMPLATE.ResourceStore, configA); + expect(storeFactory.generate).toHaveBeenCalledWith('templateB', TEMPLATE.ResourceStore, configB); + await expect(routingStorage.get(identifierA)).resolves.toBe('store'); + await expect(routingStorage.get(identifierB)).resolves.toBe('store'); + }); +});