|
| 1 | +import type { Resource, RdfObjectLoader } from 'rdf-object'; |
| 2 | +import { ComponentFactory } from './factory/ComponentFactory'; |
| 3 | +import type { ComponentFactoryOptions } from './factory/ComponentFactoryOptions'; |
| 4 | +import type { ICreationSettings, ICreationSettingsInner, IComponentFactory } from './factory/IComponentFactory'; |
| 5 | +import type { IInstancePool } from './IInstancePool'; |
| 6 | +import type { IModuleState } from './ModuleStateBuilder'; |
| 7 | +import Util = require('./Util'); |
| 8 | +import { resourceIdToString, resourceToString } from './Util'; |
| 9 | + |
| 10 | +/** |
| 11 | + * Manages and creates instances of components. |
| 12 | + */ |
| 13 | +export class InstancePool implements IInstancePool { |
| 14 | + private readonly objectLoader: RdfObjectLoader; |
| 15 | + private readonly componentResources: Record<string, Resource>; |
| 16 | + private readonly moduleState: IModuleState; |
| 17 | + private readonly overrideRequireNames: Record<string, string>; |
| 18 | + |
| 19 | + private readonly runTypeConfigs: Record<string, Resource[]> = {}; |
| 20 | + private readonly instances: Record<string, any> = {}; |
| 21 | + |
| 22 | + public constructor(options: IInstancePoolOptions) { |
| 23 | + this.objectLoader = options.objectLoader; |
| 24 | + this.componentResources = options.componentResources; |
| 25 | + this.moduleState = options.moduleState; |
| 26 | + this.overrideRequireNames = options.overrideRequireNames; |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * Let then given config inherit parameter values from referenced passed configs. |
| 31 | + * @param configResource The config |
| 32 | + * @param componentResource The component |
| 33 | + */ |
| 34 | + public inheritParameterValues(configResource: Resource, componentResource: Resource): void { |
| 35 | + // Inherit parameter values from passed instances of the given types |
| 36 | + if (componentResource.property.parameters) { |
| 37 | + for (const parameter of componentResource.properties.parameters) { |
| 38 | + // Collect all owl:Restriction's |
| 39 | + const restrictions: Resource[] = parameter.properties.inheritValues |
| 40 | + .reduce((acc: Resource[], clazz: Resource) => { |
| 41 | + if (clazz.properties.types.reduce((subAcc: boolean, type: Resource) => subAcc || |
| 42 | + type.value === `${Util.PREFIXES.owl}Restriction`, false)) { |
| 43 | + acc.push(clazz); |
| 44 | + } |
| 45 | + return acc; |
| 46 | + }, []); |
| 47 | + |
| 48 | + for (const restriction of restrictions) { |
| 49 | + if (restriction.property.from) { |
| 50 | + if (!restriction.property.onParameter) { |
| 51 | + throw new Error(`Parameters that inherit values must refer to a property: ${resourceToString(parameter)}`); |
| 52 | + } |
| 53 | + |
| 54 | + for (const componentType of restriction.properties.from) { |
| 55 | + if (componentType.type !== 'NamedNode') { |
| 56 | + throw new Error(`Parameter inheritance values must refer to component type identifiers, not literals: ${resourceToString(componentType)}`); |
| 57 | + } |
| 58 | + |
| 59 | + const typeInstances: Resource[] = this.runTypeConfigs[componentType.value]; |
| 60 | + if (typeInstances) { |
| 61 | + for (const instance of typeInstances) { |
| 62 | + for (const parentParameter of restriction.properties.onParameter) { |
| 63 | + // TODO: this might be a bug in the JSON-LD parser |
| 64 | + // if (parentParameter.termType !== 'NamedNode') { |
| 65 | + // throw new Error('Parameters that inherit values must refer to sub properties as URI\'s: ' |
| 66 | + // + JSON.stringify(parentParameter)); |
| 67 | + // } |
| 68 | + if (instance.property[parentParameter.value]) { |
| 69 | + // Copy the parameters |
| 70 | + for (const value of instance.properties[parentParameter.value]) { |
| 71 | + configResource.properties[parentParameter.value].push(value); |
| 72 | + } |
| 73 | + |
| 74 | + // Also add the parameter to the parameter type list |
| 75 | + if (!componentResource.properties.parameters.includes(parentParameter)) { |
| 76 | + componentResource.properties.parameters.push(parentParameter); |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Get a component config constructor based on a Resource. |
| 91 | + * @param configResource A config resource. |
| 92 | + * @returns The component factory. |
| 93 | + */ |
| 94 | + public getConfigConstructor(configResource: Resource): IComponentFactory { |
| 95 | + const allTypes: string[] = []; |
| 96 | + const componentTypes: Resource[] = configResource.properties.types |
| 97 | + .reduce((types: Resource[], typeUri: Resource) => { |
| 98 | + const componentResource: Resource = this.componentResources[typeUri.value]; |
| 99 | + allTypes.push(typeUri.value); |
| 100 | + if (componentResource) { |
| 101 | + types.push(componentResource); |
| 102 | + if (!this.runTypeConfigs[componentResource.value]) { |
| 103 | + this.runTypeConfigs[componentResource.value] = []; |
| 104 | + } |
| 105 | + this.runTypeConfigs[componentResource.value].push(configResource); |
| 106 | + } |
| 107 | + return types; |
| 108 | + }, []); |
| 109 | + if (componentTypes.length !== 1 && |
| 110 | + !configResource.property.requireName && |
| 111 | + !configResource.property.requireElement) { |
| 112 | + throw new Error(`Could not run config ${resourceIdToString(configResource, this.objectLoader)} because exactly one valid component type ` + |
| 113 | + `was expected, while ${componentTypes.length} were found in the defined types [${allTypes}]. ` + |
| 114 | + `Alternatively, the requireName and requireElement must be provided.\nFound: ${ |
| 115 | + resourceToString(configResource)}\nAll available usable types: [\n${ |
| 116 | + Object.keys(this.componentResources).join(',\n')}\n]`); |
| 117 | + } |
| 118 | + |
| 119 | + let options: ComponentFactoryOptions = { |
| 120 | + objectLoader: this.objectLoader, |
| 121 | + config: configResource, |
| 122 | + overrideRequireNames: this.overrideRequireNames, |
| 123 | + instancePool: this, |
| 124 | + constructable: !configResource.isA(Util.DF.namedNode(`${Util.PREFIXES.oo}ComponentInstance`)), |
| 125 | + }; |
| 126 | + |
| 127 | + if (componentTypes.length > 0) { |
| 128 | + const componentResource = componentTypes[0]; |
| 129 | + const moduleResource = componentResource.property.module; |
| 130 | + if (!moduleResource) { |
| 131 | + throw new Error(`No module was found for the component ${resourceIdToString(componentResource, this.objectLoader)}`); |
| 132 | + } |
| 133 | + |
| 134 | + this.inheritParameterValues(configResource, componentResource); |
| 135 | + |
| 136 | + options = { |
| 137 | + ...options, |
| 138 | + moduleDefinition: moduleResource, |
| 139 | + componentDefinition: componentResource, |
| 140 | + }; |
| 141 | + } |
| 142 | + |
| 143 | + return new ComponentFactory(options); |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Instantiate a component based on a Resource. |
| 148 | + * @param configResource A config resource. |
| 149 | + * @param settings The settings for creating the instance. |
| 150 | + * @returns {any} The run instance. |
| 151 | + */ |
| 152 | + public async instantiate(configResource: Resource, settings: ICreationSettings): Promise<any> { |
| 153 | + const settingsInner: ICreationSettingsInner = { ...settings, moduleState: this.moduleState }; |
| 154 | + // Check if this resource is required as argument in its own chain, |
| 155 | + // if so, return a dummy value, to avoid infinite recursion. |
| 156 | + const resourceBlacklist = settingsInner.resourceBlacklist || {}; |
| 157 | + if (resourceBlacklist[configResource.value]) { |
| 158 | + return {}; |
| 159 | + } |
| 160 | + |
| 161 | + // Before instantiating, first check if the resource is a variable |
| 162 | + if (configResource.isA(Util.IRI_VARIABLE)) { |
| 163 | + if (settingsInner.serializations) { |
| 164 | + if (settingsInner.asFunction) { |
| 165 | + return `getVariableValue('${configResource.value}')`; |
| 166 | + } |
| 167 | + throw new Error(`Detected a variable during config compilation: ${resourceIdToString(configResource, this.objectLoader)}. Variables are not supported, but require the -f flag to expose the compiled config as function.`); |
| 168 | + } else { |
| 169 | + const value = settingsInner.variables ? settingsInner.variables[configResource.value] : undefined; |
| 170 | + if (value === undefined) { |
| 171 | + throw new Error(`Undefined variable: ${resourceIdToString(configResource, this.objectLoader)}`); |
| 172 | + } |
| 173 | + return value; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + if (!this.instances[configResource.value]) { |
| 178 | + const subBlackList: Record<string, boolean> = { ...resourceBlacklist }; |
| 179 | + subBlackList[configResource.value] = true; |
| 180 | + this.instances[configResource.value] = await this.getConfigConstructor(configResource).create( |
| 181 | + { resourceBlacklist: subBlackList, ...settingsInner }, |
| 182 | + ); |
| 183 | + } |
| 184 | + return this.instances[configResource.value]; |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +export interface IInstancePoolOptions { |
| 189 | + objectLoader: RdfObjectLoader; |
| 190 | + componentResources: Record<string, Resource>; |
| 191 | + moduleState: IModuleState; |
| 192 | + overrideRequireNames: Record<string, string>; |
| 193 | +} |
0 commit comments