UNPKG

14.6 kBJavaScriptView Raw
1"use strict";
2// Shutdown order
3// Factory beans
4// Profile support
5Object.defineProperty(exports, "__esModule", { value: true });
6const fs = require("fs");
7const path = require("path");
8const globby = require("globby");
9const ConfigProvider_1 = require("../config/ConfigProvider");
10const LogManager_1 = require("../log/LogManager");
11const PromiseUtil_1 = require("../util/PromiseUtil");
12const Lifecycle_1 = require("./Lifecycle");
13const IoCException_1 = require("./IoCException");
14const ObjectDefinition_1 = require("./objectdefinition/ObjectDefinition");
15const BaseSingletonDefinition_1 = require("./objectdefinition/BaseSingletonDefinition");
16const PreinstantiatedSingletonDefinition_1 = require("./objectdefinition/PreinstantiatedSingletonDefinition");
17class Context extends Lifecycle_1.Lifecycle {
18 constructor(name, parentContext, options = {}) {
19 super(name, options.logger || LogManager_1.LogManager.getLogger(__filename));
20 this.config = options.config || new ConfigProvider_1.default();
21 this.shutdownTimer = options.shutdownTimer || 100;
22 this.parentContext = parentContext;
23 this.objectDefinitions = new Map();
24 this.startedObjects = new Map();
25 this.objectDefinitionInspector = [];
26 this.objectGroups = new Map();
27 this.registerDefinition(new PreinstantiatedSingletonDefinition_1.PreinstantiatedSingletonDefinition(this, '__CONTEXT__'));
28 }
29 // ************************************
30 // Lifecycle related methods
31 // ************************************
32 lcStart() {
33 if (this.parentContext) {
34 return this.parentContext.lcStart().then(() => super.lcStart());
35 }
36 return super.lcStart();
37 }
38 lcStop() {
39 const stopPromise = super.lcStop();
40 if (this.parentContext) {
41 return stopPromise.then(() => this.parentContext.lcStop());
42 }
43 return stopPromise;
44 }
45 doStart() {
46 this.applyObjectDefinitionModifiers();
47 return PromiseUtil_1.PromiseUtil.map(Array.from(this.objectDefinitions.values()).filter((o) => !o.isLazy()), (objectDefinition) => objectDefinition.getInstance())
48 .catch((err) => {
49 this.getLogger().error({ err }, 'There was an error starting context. At least one non-lazy object threw an exception during startup. Stopping context');
50 return this.lcStop().then(() => {
51 throw err;
52 });
53 })
54 .then(() => {
55 this.getLogger().debug(`Context ${this.getName()} started`);
56 });
57 }
58 async doStop() {
59 this.getLogger().debug(`Waiting ${this.shutdownTimer}ms to stop context`);
60 await PromiseUtil_1.PromiseUtil.sleepPromise(this.shutdownTimer);
61 return PromiseUtil_1.PromiseUtil.map(Array.from(this.startedObjects.values()), (startedObject) => startedObject.lcStop()).then(() => {
62 /* donothing */
63 });
64 }
65 // ************************************
66 // Context composition methods
67 // ************************************
68 clone(name) {
69 this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
70 const copy = new Context(name, this.parentContext, { logger: this.logger, config: this.config });
71 this.objectDefinitions.forEach((objectDefinition) => {
72 if (objectDefinition.getName() !== '__CONTEXT__') {
73 copy.registerDefinition(objectDefinition.copy());
74 }
75 });
76 return copy;
77 }
78 importContext(otherContext, overwrite = false) {
79 this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
80 otherContext.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
81 otherContext.objectDefinitions.forEach((objectDefinition) => {
82 if (objectDefinition.getName() !== '__CONTEXT__') {
83 this.registerDefinition(objectDefinition, overwrite);
84 }
85 });
86 }
87 // ************************************
88 // Object Definition inspector methods
89 // ************************************
90 addObjectDefinitionInspector(inspector) {
91 this.objectDefinitionInspector.push(inspector);
92 }
93 // ************************************
94 // Object Definition registration methods
95 // ************************************
96 registerDefinition(objectDefinition, overwrite = false) {
97 this.assertState(Lifecycle_1.LifecycleState.NOT_STARTED);
98 if (!(objectDefinition instanceof ObjectDefinition_1.ObjectDefinition)) {
99 throw new IoCException_1.IoCException('Provided input for registration is not an instance of ObjectDefinition');
100 }
101 if (!overwrite && this.objectDefinitions.has(objectDefinition.getName())) {
102 throw new IoCException_1.IoCException(`Object definition with name ${objectDefinition.getName()} already exists in this context`);
103 }
104 if (this.parentContext && this.parentContext.objectDefinitions.has(objectDefinition.getName()) && objectDefinition.getName() !== '__CONTEXT__') {
105 throw new IoCException_1.IoCException(`Parent context already has an object definition with name ${objectDefinition.getName()}`);
106 }
107 this.objectDefinitions.set(objectDefinition.getName(), objectDefinition);
108 objectDefinition.setContext(this);
109 objectDefinition.onStateOnce(Lifecycle_1.LifecycleState.STARTED, () => this.startedObjects.set(objectDefinition.getName(), objectDefinition));
110 objectDefinition.onStateOnce(Lifecycle_1.LifecycleState.STOPPED, () => this.startedObjects.delete(objectDefinition.getName()));
111 }
112 registerSingletons(...singletons) {
113 singletons.forEach((singleton) => {
114 if (singleton instanceof ObjectDefinition_1.ObjectDefinition) {
115 this.registerDefinition(singleton);
116 }
117 else if (singleton instanceof Function) {
118 this.registerDefinition(new BaseSingletonDefinition_1.BaseSingletonDefinition(singleton));
119 this.logger.debug(`Registering singleton ${singleton.name}`);
120 }
121 else {
122 throw new IoCException_1.IoCException(`Not sure how to convert input into SingletonDefinition: ${singleton}`);
123 }
124 });
125 }
126 registerSingletonsInDir(patterns, options) {
127 Context.findMatchingFiles(patterns, options).filter((file) => ['.js', '.ts'].indexOf(path.extname(file)) >= 0).forEach((file) => {
128 if (file.includes('.d.ts')) {
129 return; // Ignore type definition files
130 }
131 let expectedClass = path.basename(file);
132 expectedClass = expectedClass.substr(0, expectedClass.length - 3);
133 const loaded = require(file);
134 if (loaded) {
135 if (typeof loaded === 'object' && loaded.__esModule && loaded.default && loaded.default.constructor) {
136 this.registerSingletons(loaded.default);
137 }
138 else if (typeof loaded === 'object' &&
139 !(loaded instanceof Function) &&
140 loaded[expectedClass] &&
141 loaded[expectedClass].constructor) {
142 this.registerSingletons(loaded[expectedClass]);
143 }
144 else if (loaded instanceof Function && loaded.name && loaded.name === expectedClass) {
145 this.registerSingletons(loaded);
146 }
147 else {
148 throw new IoCException_1.IoCException(`Couldn't register singleton for ${file}`);
149 }
150 }
151 });
152 }
153 static requireFilesInDir(dir) {
154 Context.walkDirSync(dir).filter((file) => path.extname(file) === '.js').forEach((file) => {
155 // eslint-disable-next-line global-require
156 require(file);
157 });
158 }
159 /**
160 * Walks a directory recursively and returns an array with all the files
161 *
162 * @private
163 * @param dir The directory to walk through
164 * @param filelist The carried-over list of files
165 * @return {Array} The list of files in this directory and subdirs
166 */
167 static walkDirSync(dir, filelist = []) {
168 fs.readdirSync(dir).forEach((file) => {
169 filelist = fs.statSync(path.join(dir, file)).isDirectory()
170 ? Context.walkDirSync(path.join(dir, file), filelist)
171 : filelist.concat(path.join(dir, file));
172 });
173 return filelist;
174 }
175 /**
176 * find matching files based on the given path or glob
177 *
178 * @param {string} patterns - glob pattern(s) or relative path
179 * @param {boolean} [isGlob] - pass true to treat the path as a glob
180 * @param {Object} [globOptions] - options to pass to globby
181 * @returns {Array<string>} files
182 */
183 static findMatchingFiles(patterns, { isGlob, globOptions }) {
184 // Try to treat the patterns as globs first
185 if (globby.hasMagic(patterns) || Array.isArray(patterns) || isGlob) {
186 return globby.sync(patterns, globOptions);
187 }
188 // Fallback to the legacy implementation for non-glob patterns to avoid code breaks
189 return Context.walkDirSync(patterns);
190 }
191 // ************************************
192 // Config functions
193 // ************************************
194 /**
195 * Get an element from the configuration.
196 * Can be both a leaf of the configuration, or an intermediate path. In the latter case it will return
197 * an object with all the configs under that path.
198 * It will throw an exception if the key doesn't exist.
199 *
200 * @param {string} key The key of the config we want
201 * @param {*} defaultValue A default value to use if the key doesn't exist
202 * @return {*} The requested configuration
203 * @throws {Error} If the given key doesn't exist and a default value is not provided
204 * @see {@link Context.hasConfig}
205 */
206 getConfig(key, defaultValue) {
207 return this.config.getConfig(key, defaultValue);
208 }
209 /**
210 * Indicates whether a given key exists in the configuration
211 * @param key
212 * @return {*}
213 */
214 hasConfig(key) {
215 return this.config.hasConfig(key);
216 }
217 // ************************************
218 // Group functions
219 // ************************************
220 addObjectNameToGroup(groupName, objectName) {
221 if (!this.objectGroups.has(groupName)) {
222 this.objectGroups.set(groupName, []);
223 }
224 this.objectGroups.get(groupName).push(objectName);
225 }
226 getGroupObjectNames(groupName) {
227 if (!this.objectGroups.has(groupName)) {
228 return [];
229 }
230 return this.objectGroups.get(groupName);
231 }
232 // ************************************
233 // Get Bean Functions
234 // ************************************
235 getObjectByName(beanName) {
236 const beanDefinition = this.getDefinitionByName(beanName);
237 return beanDefinition.getInstance();
238 }
239 getObjectByType(className) {
240 const beanDefinition = this.getDefinitionByType(className);
241 return beanDefinition.getInstance();
242 }
243 getObjectsByType(className) {
244 const beanDefinitions = this.getDefinitionsByType(className);
245 const instances = PromiseUtil_1.PromiseUtil.map(beanDefinitions, (bd) => bd.getInstance());
246 return instances.then((arr) => {
247 arr.sort((a, b) => {
248 const aPos = Object.hasOwnProperty.call(a, 'getOrder') ? a.getOrder() : 0;
249 const bPos = Object.hasOwnProperty.call(b, 'getOrder') ? b.getOrder() : 0;
250 if (aPos === bPos) {
251 return 0;
252 }
253 else if (aPos < bPos) {
254 return -1;
255 }
256 return 1;
257 });
258 return arr;
259 });
260 }
261 getObjectsByGroup(groupName) {
262 const objectNames = this.getGroupObjectNames(groupName);
263 const objectDefinitions = objectNames.map((objectName) => this.getDefinitionByName(objectName));
264 return objectDefinitions.reduce(async (acum, def) => {
265 (await acum).push(await def.getInstance());
266 return Promise.resolve(acum);
267 }, Promise.resolve([]));
268 }
269 // ************************************
270 // Get Bean Definition Functions
271 // ************************************
272 getDefinitionByName(objectName) {
273 const val = this.objectDefinitions.get(objectName);
274 if (val) {
275 return val;
276 }
277 if (this.parentContext) {
278 return this.parentContext.getDefinitionByName(objectName);
279 }
280 throw new IoCException_1.IoCException(`No object definition with name ${objectName} registered in the context`);
281 }
282 getDefinitionByType(className) {
283 const resp = this.getDefinitionsByType(className);
284 if (resp.length > 1) {
285 throw new IoCException_1.IoCException(`Found more than one object definition in the context that produces a ${className}`);
286 }
287 return resp[0];
288 }
289 getDefinitionsByType(className, failOnMissing = true) {
290 const resp = new Map();
291 if (this.parentContext) {
292 this.parentContext
293 .getDefinitionsByType(className, false)
294 .forEach((objectDefinition) => resp.set(objectDefinition.getName(), objectDefinition));
295 }
296 this.objectDefinitions.forEach((objectDefinition) => {
297 if (objectDefinition.getProducedClass().name === className && objectDefinition.autowireCandidate) {
298 resp.set(objectDefinition.getName(), objectDefinition);
299 }
300 });
301 if (resp.size === 0 && failOnMissing) {
302 throw new IoCException_1.IoCException(`Couldn't find a bean that produces class ${className}`);
303 }
304 return Array.from(resp.values());
305 }
306 getDefinitionsByGroup(groupName) {
307 const objectNames = this.getGroupObjectNames(groupName);
308 return objectNames.map((objectName) => this.getDefinitionByName(objectName));
309 }
310 applyObjectDefinitionModifiers() {
311 // Let's allow the inspectors to modify the bean definitions
312 this.objectDefinitionInspector.forEach((inspector) => {
313 this.objectDefinitions.forEach((objectDefinition, key) => {
314 const result = inspector.inspect(objectDefinition);
315 if (result) {
316 this.objectDefinitions.set(key, result);
317 }
318 });
319 });
320 }
321}
322exports.Context = Context;
323//# sourceMappingURL=Context.js.map
\No newline at end of file