fix: ensure configuration section cache is always initialized
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
CommitLineData
d972af76
JB
1import { type FSWatcher, readFileSync, watch } from 'node:fs';
2import { dirname, join, resolve } from 'node:path';
130783a7 3import { fileURLToPath } from 'node:url';
8114d10e
JB
4
5import chalk from 'chalk';
088ee3c1 6import merge from 'just-merge';
8114d10e 7
878e026c 8import { Constants } from './Constants';
9bf0ef23 9import { hasOwnProp, isCFEnvironment, isNotEmptyString, isUndefined } from './Utils';
83e00df1 10import {
268a74bb 11 ApplicationProtocol,
83e00df1 12 type ConfigurationData,
5d049829 13 ConfigurationSection,
268a74bb 14 FileType,
3d48c1c1 15 type LogConfiguration,
83e00df1
JB
16 type StationTemplateUrl,
17 type StorageConfiguration,
268a74bb 18 StorageType,
e7aeea18 19 SupervisionUrlDistribution,
83e00df1
JB
20 type UIServerConfiguration,
21 type WorkerConfiguration,
268a74bb
JB
22} from '../types';
23import { WorkerConstants, WorkerProcessType } from '../worker';
7dde0b73 24
268a74bb 25export class Configuration {
d972af76
JB
26 private static configurationFile = join(
27 dirname(fileURLToPath(import.meta.url)),
e7aeea18 28 'assets',
5edd8ba0 29 'config.json',
e7aeea18 30 );
10068088 31
d972af76 32 private static configurationFileWatcher: FSWatcher | undefined;
f74e97ac 33 private static configurationData: ConfigurationData | null = null;
974efe6c
JB
34 private static configurationSectionCache = new Map<
35 ConfigurationSection,
36 LogConfiguration | StorageConfiguration | WorkerConfiguration | UIServerConfiguration
5d049829
JB
37 >([
38 [ConfigurationSection.log, Configuration.buildLogSection()],
39 [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
40 [ConfigurationSection.worker, Configuration.buildWorkerSection()],
41 [ConfigurationSection.uiServer, Configuration.buildUIServerSection()],
42 ]);
974efe6c 43
e57acf6a
JB
44 private static configurationChangeCallback: () => Promise<void>;
45
d5bd1c00
JB
46 private constructor() {
47 // This is intentional
48 }
49
aa7d6d95 50 public static setConfigurationChangeCallback(cb: () => Promise<void>): void {
e57acf6a
JB
51 Configuration.configurationChangeCallback = cb;
52 }
7dde0b73 53
5d049829 54 public static getConfigurationSection<T>(sectionName: ConfigurationSection): T {
c1c97db8
JB
55 if (!Configuration.configurationSectionCache.has(sectionName)) {
56 switch (sectionName) {
57 case ConfigurationSection.log:
58 Configuration.configurationSectionCache.set(sectionName, Configuration.buildLogSection());
59 break;
60 case ConfigurationSection.performanceStorage:
61 Configuration.configurationSectionCache.set(
62 sectionName,
63 Configuration.buildPerformanceStorageSection(),
64 );
65 break;
66 case ConfigurationSection.worker:
67 Configuration.configurationSectionCache.set(
68 sectionName,
69 Configuration.buildWorkerSection(),
70 );
71 break;
72 case ConfigurationSection.uiServer:
73 Configuration.configurationSectionCache.set(
74 sectionName,
75 Configuration.buildUIServerSection(),
76 );
77 break;
78 default:
79 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
80 throw new Error(`Unknown configuration section '${sectionName}'`);
81 }
82 }
5d049829
JB
83 return Configuration.configurationSectionCache.get(sectionName) as T;
84 }
85
86 public static getAutoReconnectMaxRetries(): number | undefined {
87 Configuration.warnDeprecatedConfigurationKey(
88 'autoReconnectTimeout',
89 undefined,
90 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead",
91 );
92 Configuration.warnDeprecatedConfigurationKey(
93 'connectionTimeout',
94 undefined,
95 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead",
96 );
97 Configuration.warnDeprecatedConfigurationKey(
98 'autoReconnectMaxRetries',
99 undefined,
100 'Use it in charging station template instead',
101 );
102 if (hasOwnProp(Configuration.getConfigurationData(), 'autoReconnectMaxRetries')) {
103 return Configuration.getConfigurationData()?.autoReconnectMaxRetries;
104 }
105 }
106
107 public static getStationTemplateUrls(): StationTemplateUrl[] | undefined {
108 Configuration.warnDeprecatedConfigurationKey(
109 'stationTemplateURLs',
110 undefined,
111 "Use 'stationTemplateUrls' instead",
112 );
113 // eslint-disable-next-line @typescript-eslint/dot-notation
114 !isUndefined(Configuration.getConfigurationData()!['stationTemplateURLs']) &&
115 (Configuration.getConfigurationData()!.stationTemplateUrls =
116 Configuration.getConfigurationData()![
117 // eslint-disable-next-line @typescript-eslint/dot-notation
118 'stationTemplateURLs'
119 ] as StationTemplateUrl[]);
120 Configuration.getConfigurationData()!.stationTemplateUrls.forEach(
121 (stationTemplateUrl: StationTemplateUrl) => {
122 // eslint-disable-next-line @typescript-eslint/dot-notation
123 if (!isUndefined(stationTemplateUrl['numberOfStation'])) {
124 console.error(
125 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
126 `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`,
127 )}`,
128 );
129 }
130 },
131 );
132 return Configuration.getConfigurationData()?.stationTemplateUrls;
133 }
134
5d049829
JB
135 public static getSupervisionUrls(): string | string[] | undefined {
136 Configuration.warnDeprecatedConfigurationKey(
137 'supervisionURLs',
138 undefined,
139 "Use 'supervisionUrls' instead",
140 );
141 // eslint-disable-next-line @typescript-eslint/dot-notation
142 if (!isUndefined(Configuration.getConfigurationData()!['supervisionURLs'])) {
143 Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
144 // eslint-disable-next-line @typescript-eslint/dot-notation
145 'supervisionURLs'
146 ] as string | string[];
147 }
148 return Configuration.getConfigurationData()?.supervisionUrls;
149 }
150
151 public static getSupervisionUrlDistribution(): SupervisionUrlDistribution | undefined {
152 Configuration.warnDeprecatedConfigurationKey(
153 'distributeStationToTenantEqually',
154 undefined,
155 "Use 'supervisionUrlDistribution' instead",
156 );
157 Configuration.warnDeprecatedConfigurationKey(
158 'distributeStationsToTenantsEqually',
159 undefined,
160 "Use 'supervisionUrlDistribution' instead",
161 );
162 return hasOwnProp(Configuration.getConfigurationData(), 'supervisionUrlDistribution')
163 ? Configuration.getConfigurationData()?.supervisionUrlDistribution
164 : SupervisionUrlDistribution.ROUND_ROBIN;
165 }
166
c831d2bc
JB
167 public static workerPoolInUse(): boolean {
168 return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
169 Configuration.buildWorkerSection().processType!,
170 );
171 }
172
173 public static workerDynamicPoolInUse(): boolean {
174 return Configuration.buildWorkerSection().processType === WorkerProcessType.dynamicPool;
175 }
176
5d049829 177 private static buildUIServerSection(): UIServerConfiguration {
f74e97ac 178 if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
66271092 179 console.error(
c5e52a07 180 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
974efe6c 181 `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`,
5edd8ba0 182 )}`,
66271092
JB
183 );
184 }
675fa8e3 185 let uiServerConfiguration: UIServerConfiguration = {
b803eefa 186 enabled: false,
1f7fa4de 187 type: ApplicationProtocol.WS,
c127bd64 188 options: {
adbddcb4
JB
189 host: Constants.DEFAULT_UI_SERVER_HOST,
190 port: Constants.DEFAULT_UI_SERVER_PORT,
c127bd64 191 },
6a49ad23 192 };
f74e97ac 193 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.uiServer)) {
598c886d
JB
194 uiServerConfiguration = merge<UIServerConfiguration>(
195 uiServerConfiguration,
f74e97ac 196 Configuration.getConfigurationData()!.uiServer!,
598c886d 197 );
6a49ad23 198 }
9bf0ef23 199 if (isCFEnvironment() === true) {
72092cfc 200 delete uiServerConfiguration.options?.host;
e1d9a0f4 201 uiServerConfiguration.options!.port = parseInt(process.env.PORT!);
b803eefa 202 }
5d049829 203 return uiServerConfiguration;
6a49ad23
JB
204 }
205
5d049829 206 private static buildPerformanceStorageSection(): StorageConfiguration {
974efe6c
JB
207 Configuration.warnDeprecatedConfigurationKey(
208 'URI',
209 ConfigurationSection.performanceStorage,
210 "Use 'uri' instead",
211 );
6a49ad23
JB
212 let storageConfiguration: StorageConfiguration = {
213 enabled: false,
214 type: StorageType.JSON_FILE,
e7aeea18 215 uri: this.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
6a49ad23 216 };
f74e97ac 217 if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
e7aeea18 218 storageConfiguration = {
1ba1e8fb 219 ...storageConfiguration,
f74e97ac
JB
220 ...Configuration.getConfigurationData()?.performanceStorage,
221 ...(Configuration.getConfigurationData()?.performanceStorage?.type ===
222 StorageType.JSON_FILE &&
223 Configuration.getConfigurationData()?.performanceStorage?.uri && {
e8044a69 224 uri: Configuration.buildPerformanceUriFilePath(
f74e97ac 225 new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname,
e8044a69 226 ),
f682b2dc 227 }),
72f041bd 228 };
72f041bd 229 }
5d049829 230 return storageConfiguration;
7dde0b73
JB
231 }
232
5d049829 233 private static buildLogSection(): LogConfiguration {
3d48c1c1
JB
234 Configuration.warnDeprecatedConfigurationKey(
235 'logEnabled',
236 undefined,
974efe6c 237 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`,
3d48c1c1
JB
238 );
239 Configuration.warnDeprecatedConfigurationKey(
240 'logFile',
241 undefined,
974efe6c 242 `Use '${ConfigurationSection.log}' section to define the log file instead`,
3d48c1c1
JB
243 );
244 Configuration.warnDeprecatedConfigurationKey(
245 'logErrorFile',
246 undefined,
974efe6c 247 `Use '${ConfigurationSection.log}' section to define the log error file instead`,
3d48c1c1
JB
248 );
249 Configuration.warnDeprecatedConfigurationKey(
250 'logConsole',
251 undefined,
974efe6c 252 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`,
3d48c1c1
JB
253 );
254 Configuration.warnDeprecatedConfigurationKey(
255 'logStatisticsInterval',
256 undefined,
974efe6c 257 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`,
3d48c1c1
JB
258 );
259 Configuration.warnDeprecatedConfigurationKey(
260 'logLevel',
261 undefined,
974efe6c 262 `Use '${ConfigurationSection.log}' section to define the log level instead`,
3d48c1c1
JB
263 );
264 Configuration.warnDeprecatedConfigurationKey(
265 'logFormat',
266 undefined,
974efe6c 267 `Use '${ConfigurationSection.log}' section to define the log format instead`,
3d48c1c1
JB
268 );
269 Configuration.warnDeprecatedConfigurationKey(
270 'logRotate',
271 undefined,
974efe6c 272 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`,
3d48c1c1
JB
273 );
274 Configuration.warnDeprecatedConfigurationKey(
275 'logMaxFiles',
276 undefined,
974efe6c 277 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`,
3d48c1c1
JB
278 );
279 Configuration.warnDeprecatedConfigurationKey(
280 'logMaxSize',
281 undefined,
974efe6c 282 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`,
3d48c1c1
JB
283 );
284 const defaultLogConfiguration: LogConfiguration = {
285 enabled: true,
286 file: 'logs/combined.log',
287 errorFile: 'logs/error.log',
288 statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
289 level: 'info',
290 format: 'simple',
291 rotate: true,
292 };
293 const deprecatedLogConfiguration: LogConfiguration = {
f74e97ac
JB
294 ...(hasOwnProp(Configuration.getConfigurationData(), 'logEnabled') && {
295 enabled: Configuration.getConfigurationData()?.logEnabled,
3d48c1c1 296 }),
f74e97ac
JB
297 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFile') && {
298 file: Configuration.getConfigurationData()?.logFile,
3d48c1c1 299 }),
f74e97ac
JB
300 ...(hasOwnProp(Configuration.getConfigurationData(), 'logErrorFile') && {
301 errorFile: Configuration.getConfigurationData()?.logErrorFile,
3d48c1c1 302 }),
f74e97ac
JB
303 ...(hasOwnProp(Configuration.getConfigurationData(), 'logStatisticsInterval') && {
304 statisticsInterval: Configuration.getConfigurationData()?.logStatisticsInterval,
3d48c1c1 305 }),
f74e97ac
JB
306 ...(hasOwnProp(Configuration.getConfigurationData(), 'logLevel') && {
307 level: Configuration.getConfigurationData()?.logLevel,
3d48c1c1 308 }),
f74e97ac
JB
309 ...(hasOwnProp(Configuration.getConfigurationData(), 'logConsole') && {
310 console: Configuration.getConfigurationData()?.logConsole,
3d48c1c1 311 }),
f74e97ac
JB
312 ...(hasOwnProp(Configuration.getConfigurationData(), 'logFormat') && {
313 format: Configuration.getConfigurationData()?.logFormat,
3d48c1c1 314 }),
f74e97ac
JB
315 ...(hasOwnProp(Configuration.getConfigurationData(), 'logRotate') && {
316 rotate: Configuration.getConfigurationData()?.logRotate,
3d48c1c1 317 }),
f74e97ac
JB
318 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxFiles') && {
319 maxFiles: Configuration.getConfigurationData()?.logMaxFiles,
3d48c1c1 320 }),
f74e97ac
JB
321 ...(hasOwnProp(Configuration.getConfigurationData(), 'logMaxSize') && {
322 maxSize: Configuration.getConfigurationData()?.logMaxSize,
3d48c1c1
JB
323 }),
324 };
325 const logConfiguration: LogConfiguration = {
326 ...defaultLogConfiguration,
327 ...deprecatedLogConfiguration,
f74e97ac
JB
328 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.log) &&
329 Configuration.getConfigurationData()?.log),
3d48c1c1 330 };
5d049829 331 return logConfiguration;
3d48c1c1
JB
332 }
333
5d049829 334 private static buildWorkerSection(): WorkerConfiguration {
e7aeea18 335 Configuration.warnDeprecatedConfigurationKey(
e80bc579 336 'useWorkerPool',
1895299d 337 undefined,
974efe6c 338 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`,
cf2a5d9b
JB
339 );
340 Configuration.warnDeprecatedConfigurationKey(
341 'workerProcess',
1895299d 342 undefined,
974efe6c 343 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`,
cf2a5d9b
JB
344 );
345 Configuration.warnDeprecatedConfigurationKey(
346 'workerStartDelay',
1895299d 347 undefined,
974efe6c 348 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`,
cf2a5d9b
JB
349 );
350 Configuration.warnDeprecatedConfigurationKey(
351 'chargingStationsPerWorker',
1895299d 352 undefined,
974efe6c 353 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`,
cf2a5d9b
JB
354 );
355 Configuration.warnDeprecatedConfigurationKey(
356 'elementStartDelay',
1895299d 357 undefined,
974efe6c 358 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`,
cf2a5d9b
JB
359 );
360 Configuration.warnDeprecatedConfigurationKey(
361 'workerPoolMinSize',
1895299d 362 undefined,
974efe6c 363 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`,
e7aeea18 364 );
e7aeea18
JB
365 Configuration.warnDeprecatedConfigurationKey(
366 'workerPoolSize;',
1895299d 367 undefined,
974efe6c 368 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
e7aeea18 369 );
cf2a5d9b
JB
370 Configuration.warnDeprecatedConfigurationKey(
371 'workerPoolMaxSize;',
1895299d 372 undefined,
974efe6c 373 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
cf2a5d9b
JB
374 );
375 Configuration.warnDeprecatedConfigurationKey(
376 'workerPoolStrategy;',
1895299d 377 undefined,
974efe6c 378 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`,
cf2a5d9b 379 );
3d48c1c1
JB
380 const defaultWorkerConfiguration: WorkerConfiguration = {
381 processType: WorkerProcessType.workerSet,
382 startDelay: WorkerConstants.DEFAULT_WORKER_START_DELAY,
8603c1ca 383 elementsPerWorker: 'auto',
3d48c1c1
JB
384 elementStartDelay: WorkerConstants.DEFAULT_ELEMENT_START_DELAY,
385 poolMinSize: WorkerConstants.DEFAULT_POOL_MIN_SIZE,
386 poolMaxSize: WorkerConstants.DEFAULT_POOL_MAX_SIZE,
3d48c1c1 387 };
f74e97ac
JB
388 hasOwnProp(Configuration.getConfigurationData(), 'workerPoolStrategy') &&
389 delete Configuration.getConfigurationData()?.workerPoolStrategy;
3d48c1c1 390 const deprecatedWorkerConfiguration: WorkerConfiguration = {
f74e97ac
JB
391 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerProcess') && {
392 processType: Configuration.getConfigurationData()?.workerProcess,
3d48c1c1 393 }),
f74e97ac
JB
394 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerStartDelay') && {
395 startDelay: Configuration.getConfigurationData()?.workerStartDelay,
3d48c1c1 396 }),
f74e97ac
JB
397 ...(hasOwnProp(Configuration.getConfigurationData(), 'chargingStationsPerWorker') && {
398 elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker,
3d48c1c1 399 }),
f74e97ac
JB
400 ...(hasOwnProp(Configuration.getConfigurationData(), 'elementStartDelay') && {
401 elementStartDelay: Configuration.getConfigurationData()?.elementStartDelay,
3d48c1c1 402 }),
f74e97ac
JB
403 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMinSize') && {
404 poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize,
3d48c1c1 405 }),
f74e97ac
JB
406 ...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMaxSize') && {
407 poolMaxSize: Configuration.getConfigurationData()?.workerPoolMaxSize,
3d48c1c1 408 }),
3d48c1c1 409 };
eda9c451
JB
410 Configuration.warnDeprecatedConfigurationKey(
411 'poolStrategy',
974efe6c 412 ConfigurationSection.worker,
eda9c451
JB
413 'Not publicly exposed to end users',
414 );
3d48c1c1
JB
415 const workerConfiguration: WorkerConfiguration = {
416 ...defaultWorkerConfiguration,
417 ...deprecatedWorkerConfiguration,
f74e97ac
JB
418 ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
419 Configuration.getConfigurationData()?.worker),
cf2a5d9b 420 };
b5b2c3e8
JB
421 if (!Object.values(WorkerProcessType).includes(workerConfiguration.processType!)) {
422 throw new SyntaxError(
423 `Invalid worker process type '${workerConfiguration.processType}' defined in configuration`,
424 );
425 }
5d049829 426 return workerConfiguration;
7dde0b73 427 }
eb3937cb 428
8b7072dc 429 private static logPrefix = (): string => {
14ecae6a 430 return `${new Date().toLocaleString()} Simulator configuration |`;
8b7072dc 431 };
23132a44 432
e7aeea18
JB
433 private static warnDeprecatedConfigurationKey(
434 key: string,
435 sectionName?: string,
5edd8ba0 436 logMsgToAppend = '',
e7aeea18 437 ) {
e7aeea18
JB
438 if (
439 sectionName &&
f74e97ac
JB
440 !isUndefined(Configuration.getConfigurationData()![sectionName]) &&
441 !isUndefined(
442 (Configuration.getConfigurationData()![sectionName] as Record<string, unknown>)[key],
443 )
e7aeea18
JB
444 ) {
445 console.error(
c5e52a07
JB
446 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
447 `Deprecated configuration key '${key}' usage in section '${sectionName}'${
448 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
5edd8ba0
JB
449 }`,
450 )}`,
e7aeea18 451 );
f74e97ac 452 } else if (!isUndefined(Configuration.getConfigurationData()![key])) {
e7aeea18 453 console.error(
c5e52a07
JB
454 `${chalk.green(Configuration.logPrefix())} ${chalk.red(
455 `Deprecated configuration key '${key}' usage${
456 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
5edd8ba0
JB
457 }`,
458 )}`,
e7aeea18 459 );
eb3937cb
JB
460 }
461 }
462
f74e97ac
JB
463 private static getConfigurationData(): ConfigurationData | null {
464 if (!Configuration.configurationData) {
23132a44 465 try {
f74e97ac 466 Configuration.configurationData = JSON.parse(
5edd8ba0 467 readFileSync(Configuration.configurationFile, 'utf8'),
e7aeea18 468 ) as ConfigurationData;
23132a44 469 } catch (error) {
69074173 470 Configuration.handleFileException(
a95873d8 471 Configuration.configurationFile,
7164966d
JB
472 FileType.Configuration,
473 error as NodeJS.ErrnoException,
5edd8ba0 474 Configuration.logPrefix(),
e7aeea18 475 );
23132a44 476 }
ded13d97
JB
477 if (!Configuration.configurationFileWatcher) {
478 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
479 }
eb3937cb 480 }
f74e97ac 481 return Configuration.configurationData;
eb3937cb 482 }
963ee397 483
d972af76 484 private static getConfigurationFileWatcher(): FSWatcher | undefined {
23132a44 485 try {
d972af76 486 return watch(Configuration.configurationFile, (event, filename): void => {
e1d9a0f4 487 if (filename!.trim()!.length > 0 && event === 'change') {
3ec10737 488 // Nullify to force configuration file reading
f74e97ac 489 Configuration.configurationData = null;
974efe6c 490 Configuration.configurationSectionCache.clear();
9bf0ef23 491 if (!isUndefined(Configuration.configurationChangeCallback)) {
72092cfc 492 Configuration.configurationChangeCallback().catch((error) => {
dcaf96dc
JB
493 throw typeof error === 'string' ? new Error(error) : error;
494 });
3ec10737 495 }
23132a44
JB
496 }
497 });
498 } catch (error) {
69074173 499 Configuration.handleFileException(
a95873d8 500 Configuration.configurationFile,
7164966d
JB
501 FileType.Configuration,
502 error as NodeJS.ErrnoException,
5edd8ba0 503 Configuration.logPrefix(),
e7aeea18 504 );
23132a44 505 }
ded13d97
JB
506 }
507
69074173
JB
508 private static handleFileException(
509 file: string,
510 fileType: FileType,
511 error: NodeJS.ErrnoException,
5edd8ba0 512 logPrefix: string,
69074173 513 ): void {
9bf0ef23 514 const prefix = isNotEmptyString(logPrefix) ? `${logPrefix} ` : '';
69074173
JB
515 let logMsg: string;
516 switch (error.code) {
517 case 'ENOENT':
518 logMsg = `${fileType} file ${file} not found:`;
519 break;
520 case 'EEXIST':
521 logMsg = `${fileType} file ${file} already exists:`;
522 break;
523 case 'EACCES':
524 logMsg = `${fileType} file ${file} access denied:`;
525 break;
7b5dbe91
JB
526 case 'EPERM':
527 logMsg = `${fileType} file ${file} permission denied:`;
528 break;
69074173
JB
529 default:
530 logMsg = `${fileType} file ${file} error:`;
531 }
7b5dbe91
JB
532 console.error(`${chalk.green(prefix)}${chalk.red(`${logMsg} `)}`, error);
533 throw error;
69074173
JB
534 }
535
1f5df42a 536 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
537 switch (storageType) {
538 case StorageType.JSON_FILE:
e8044a69 539 return Configuration.buildPerformanceUriFilePath(
5edd8ba0 540 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME}`,
e8044a69 541 );
d5603918 542 case StorageType.SQLITE:
e8044a69 543 return Configuration.buildPerformanceUriFilePath(
5edd8ba0 544 `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`,
e8044a69 545 );
d5603918
JB
546 default:
547 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
548 }
549 }
e8044a69
JB
550
551 private static buildPerformanceUriFilePath(file: string) {
d972af76 552 return `file://${join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), file)}`;
e8044a69 553 }
7dde0b73 554}