Fix nullish checks
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
CommitLineData
130783a7
JB
1import fs from 'node:fs';
2import path from 'node:path';
3import { fileURLToPath } from 'node:url';
8114d10e
JB
4
5import chalk from 'chalk';
088ee3c1 6import merge from 'just-merge';
156cab2c 7import { WorkerChoiceStrategies } from 'poolifier';
8114d10e 8
78202038 9import Constants from './Constants';
83e00df1
JB
10import {
11 type ConfigurationData,
12 type StationTemplateUrl,
13 type StorageConfiguration,
e7aeea18 14 SupervisionUrlDistribution,
83e00df1
JB
15 type UIServerConfiguration,
16 type WorkerConfiguration,
e7aeea18 17} from '../types/ConfigurationData';
6c1761d4
JB
18import type { EmptyObject } from '../types/EmptyObject';
19import type { HandleErrorParams } from '../types/Error';
8114d10e 20import { FileType } from '../types/FileType';
72f041bd 21import { StorageType } from '../types/Storage';
1f7fa4de 22import { ApplicationProtocol } from '../types/UIProtocol';
a4624c96 23import { WorkerProcessType } from '../types/Worker';
8114d10e 24import WorkerConstants from '../worker/WorkerConstants';
7dde0b73 25
3f40bc9c 26export default class Configuration {
a95873d8 27 private static configurationFile = path.join(
0d8140bd 28 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
e7aeea18
JB
29 'assets',
30 'config.json'
31 );
10068088 32
1895299d 33 private static configurationFileWatcher: fs.FSWatcher | undefined;
6e0964c8 34 private static configuration: ConfigurationData | null = null;
e57acf6a
JB
35 private static configurationChangeCallback: () => Promise<void>;
36
d5bd1c00
JB
37 private constructor() {
38 // This is intentional
39 }
40
e57acf6a
JB
41 static setConfigurationChangeCallback(cb: () => Promise<void>): void {
42 Configuration.configurationChangeCallback = cb;
43 }
7dde0b73 44
1895299d 45 static getLogStatisticsInterval(): number | undefined {
e7aeea18
JB
46 Configuration.warnDeprecatedConfigurationKey(
47 'statisticsDisplayInterval',
1895299d 48 undefined,
e7aeea18
JB
49 "Use 'logStatisticsInterval' instead"
50 );
7dde0b73 51 // Read conf
e7aeea18 52 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logStatisticsInterval')
1895299d 53 ? Configuration.getConfig()?.logStatisticsInterval
25f5a959 54 : Constants.DEFAULT_LOG_STATISTICS_INTERVAL;
72f041bd
JB
55 }
56
675fa8e3 57 static getUIServer(): UIServerConfiguration {
66271092
JB
58 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiWebSocketServer')) {
59 console.error(
60 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
61 );
62 }
675fa8e3 63 let uiServerConfiguration: UIServerConfiguration = {
b803eefa 64 enabled: false,
1f7fa4de 65 type: ApplicationProtocol.WS,
c127bd64 66 options: {
adbddcb4
JB
67 host: Constants.DEFAULT_UI_SERVER_HOST,
68 port: Constants.DEFAULT_UI_SERVER_PORT,
c127bd64 69 },
6a49ad23 70 };
675fa8e3 71 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiServer')) {
1895299d 72 uiServerConfiguration = merge(uiServerConfiguration, Configuration.getConfig()?.uiServer);
6a49ad23 73 }
b803eefa 74 if (Configuration.isCFEnvironment() === true) {
72092cfc 75 delete uiServerConfiguration.options?.host;
b803eefa
JB
76 uiServerConfiguration.options.port = parseInt(process.env.PORT);
77 }
675fa8e3 78 return uiServerConfiguration;
6a49ad23
JB
79 }
80
72f041bd 81 static getPerformanceStorage(): StorageConfiguration {
e7aeea18 82 Configuration.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
6a49ad23
JB
83 let storageConfiguration: StorageConfiguration = {
84 enabled: false,
85 type: StorageType.JSON_FILE,
e7aeea18 86 uri: this.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
6a49ad23 87 };
72f041bd 88 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'performanceStorage')) {
e7aeea18 89 storageConfiguration = {
1ba1e8fb 90 ...storageConfiguration,
1895299d 91 ...Configuration.getConfig()?.performanceStorage,
72f041bd 92 };
72f041bd
JB
93 }
94 return storageConfiguration;
7dde0b73
JB
95 }
96
1895299d 97 static getAutoReconnectMaxRetries(): number | undefined {
e7aeea18
JB
98 Configuration.warnDeprecatedConfigurationKey(
99 'autoReconnectTimeout',
1895299d 100 undefined,
e7aeea18
JB
101 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
102 );
103 Configuration.warnDeprecatedConfigurationKey(
104 'connectionTimeout',
1895299d 105 undefined,
e7aeea18
JB
106 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
107 );
108 Configuration.warnDeprecatedConfigurationKey(
109 'autoReconnectMaxRetries',
1895299d 110 undefined,
e7aeea18
JB
111 'Use it in charging station template instead'
112 );
7dde0b73 113 // Read conf
963ee397 114 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'autoReconnectMaxRetries')) {
1895299d 115 return Configuration.getConfig()?.autoReconnectMaxRetries;
3574dfd3 116 }
7dde0b73
JB
117 }
118
1895299d 119 static getStationTemplateUrls(): StationTemplateUrl[] | undefined {
e7aeea18
JB
120 Configuration.warnDeprecatedConfigurationKey(
121 'stationTemplateURLs',
1895299d 122 undefined,
e7aeea18
JB
123 "Use 'stationTemplateUrls' instead"
124 );
125 !Configuration.isUndefined(Configuration.getConfig()['stationTemplateURLs']) &&
126 (Configuration.getConfig().stationTemplateUrls = Configuration.getConfig()[
127 'stationTemplateURLs'
1895299d 128 ] as unknown as StationTemplateUrl[]);
1f5df42a
JB
129 Configuration.getConfig().stationTemplateUrls.forEach((stationUrl: StationTemplateUrl) => {
130 if (!Configuration.isUndefined(stationUrl['numberOfStation'])) {
e7aeea18
JB
131 console.error(
132 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
133 stationUrl.file
134 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
135 );
eb3937cb
JB
136 }
137 });
7dde0b73 138 // Read conf
1895299d 139 return Configuration.getConfig()?.stationTemplateUrls;
7dde0b73
JB
140 }
141
cf2a5d9b 142 static getWorker(): WorkerConfiguration {
e7aeea18 143 Configuration.warnDeprecatedConfigurationKey(
e80bc579 144 'useWorkerPool',
1895299d 145 undefined,
cf2a5d9b
JB
146 "Use 'worker' section to define the type of worker process model instead"
147 );
148 Configuration.warnDeprecatedConfigurationKey(
149 'workerProcess',
1895299d 150 undefined,
cf2a5d9b
JB
151 "Use 'worker' section to define the type of worker process model instead"
152 );
153 Configuration.warnDeprecatedConfigurationKey(
154 'workerStartDelay',
1895299d 155 undefined,
cf2a5d9b
JB
156 "Use 'worker' section to define the worker start delay instead"
157 );
158 Configuration.warnDeprecatedConfigurationKey(
159 'chargingStationsPerWorker',
1895299d 160 undefined,
cf2a5d9b
JB
161 "Use 'worker' section to define the number of element(s) per worker instead"
162 );
163 Configuration.warnDeprecatedConfigurationKey(
164 'elementStartDelay',
1895299d 165 undefined,
cf2a5d9b
JB
166 "Use 'worker' section to define the worker's element start delay instead"
167 );
168 Configuration.warnDeprecatedConfigurationKey(
169 'workerPoolMinSize',
1895299d 170 undefined,
cf2a5d9b 171 "Use 'worker' section to define the worker pool minimum size instead"
e7aeea18 172 );
e7aeea18
JB
173 Configuration.warnDeprecatedConfigurationKey(
174 'workerPoolSize;',
1895299d 175 undefined,
cf2a5d9b 176 "Use 'worker' section to define the worker pool maximum size instead"
e7aeea18 177 );
cf2a5d9b
JB
178 Configuration.warnDeprecatedConfigurationKey(
179 'workerPoolMaxSize;',
1895299d 180 undefined,
cf2a5d9b
JB
181 "Use 'worker' section to define the worker pool maximum size instead"
182 );
183 Configuration.warnDeprecatedConfigurationKey(
184 'workerPoolStrategy;',
1895299d 185 undefined,
cf2a5d9b
JB
186 "Use 'worker' section to define the worker pool strategy instead"
187 );
c127bd64 188 let workerConfiguration: WorkerConfiguration = {
cf2a5d9b 189 processType: Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerProcess')
1895299d 190 ? Configuration.getConfig()?.workerProcess
cf2a5d9b
JB
191 : WorkerProcessType.WORKER_SET,
192 startDelay: Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerStartDelay')
1895299d 193 ? Configuration.getConfig()?.workerStartDelay
cf2a5d9b
JB
194 : WorkerConstants.DEFAULT_WORKER_START_DELAY,
195 elementsPerWorker: Configuration.objectHasOwnProperty(
196 Configuration.getConfig(),
197 'chargingStationsPerWorker'
198 )
1895299d 199 ? Configuration.getConfig()?.chargingStationsPerWorker
cf2a5d9b
JB
200 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER,
201 elementStartDelay: Configuration.objectHasOwnProperty(
202 Configuration.getConfig(),
203 'elementStartDelay'
204 )
1895299d 205 ? Configuration.getConfig()?.elementStartDelay
cf2a5d9b
JB
206 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY,
207 poolMinSize: Configuration.objectHasOwnProperty(
208 Configuration.getConfig(),
209 'workerPoolMinSize'
210 )
1895299d 211 ? Configuration.getConfig()?.workerPoolMinSize
cf2a5d9b
JB
212 : WorkerConstants.DEFAULT_POOL_MIN_SIZE,
213 poolMaxSize: Configuration.objectHasOwnProperty(
214 Configuration.getConfig(),
215 'workerPoolMaxSize'
216 )
1895299d 217 ? Configuration.getConfig()?.workerPoolMaxSize
cf2a5d9b 218 : WorkerConstants.DEFAULT_POOL_MAX_SIZE,
156cab2c 219 poolStrategy:
1895299d 220 Configuration.getConfig()?.workerPoolStrategy ?? WorkerChoiceStrategies.ROUND_ROBIN,
cf2a5d9b
JB
221 };
222 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'worker')) {
1895299d 223 workerConfiguration = { ...workerConfiguration, ...Configuration.getConfig()?.worker };
cf2a5d9b
JB
224 }
225 return workerConfiguration;
3d2ff9e4
J
226 }
227
1895299d
JB
228 static getLogConsole(): boolean | undefined {
229 Configuration.warnDeprecatedConfigurationKey(
230 'consoleLog',
231 undefined,
232 "Use 'logConsole' instead"
233 );
e7aeea18 234 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
1895299d 235 ? Configuration.getConfig()?.logConsole
e7aeea18 236 : false;
7dde0b73
JB
237 }
238
1895299d 239 static getLogFormat(): string | undefined {
e7aeea18 240 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
1895299d 241 ? Configuration.getConfig()?.logFormat
e7aeea18 242 : 'simple';
027b409a
JB
243 }
244
1895299d 245 static getLogRotate(): boolean | undefined {
e7aeea18 246 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logRotate')
1895299d 247 ? Configuration.getConfig()?.logRotate
e7aeea18 248 : true;
6bf6769e
JB
249 }
250
1895299d 251 static getLogMaxFiles(): number | string | false | undefined {
9988696d
JB
252 return (
253 Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 254 Configuration.getConfig()?.logMaxFiles
9988696d
JB
255 );
256 }
257
1895299d 258 static getLogMaxSize(): number | string | false | undefined {
9988696d
JB
259 return (
260 Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 261 Configuration.getConfig()?.logMaxSize
9988696d 262 );
6bf6769e
JB
263 }
264
1895299d 265 static getLogLevel(): string | undefined {
e7aeea18 266 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
1895299d 267 ? Configuration.getConfig()?.logLevel?.toLowerCase()
e7aeea18 268 : 'info';
2e6f5966
JB
269 }
270
1895299d 271 static getLogFile(): string | undefined {
e7aeea18 272 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFile')
1895299d 273 ? Configuration.getConfig()?.logFile
e7aeea18 274 : 'combined.log';
7dde0b73
JB
275 }
276
1895299d
JB
277 static getLogErrorFile(): string | undefined {
278 Configuration.warnDeprecatedConfigurationKey(
279 'errorFile',
280 undefined,
281 "Use 'logErrorFile' instead"
282 );
e7aeea18 283 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile')
1895299d 284 ? Configuration.getConfig()?.logErrorFile
e7aeea18 285 : 'error.log';
7dde0b73
JB
286 }
287
1895299d 288 static getSupervisionUrls(): string | string[] | undefined {
e7aeea18
JB
289 Configuration.warnDeprecatedConfigurationKey(
290 'supervisionURLs',
1895299d 291 undefined,
e7aeea18
JB
292 "Use 'supervisionUrls' instead"
293 );
294 !Configuration.isUndefined(Configuration.getConfig()['supervisionURLs']) &&
1895299d
JB
295 (Configuration.getConfig().supervisionUrls = Configuration.getConfig()['supervisionURLs'] as
296 | string
297 | string[]);
7dde0b73 298 // Read conf
1895299d 299 return Configuration.getConfig()?.supervisionUrls;
7dde0b73
JB
300 }
301
1895299d 302 static getSupervisionUrlDistribution(): SupervisionUrlDistribution | undefined {
e7aeea18
JB
303 Configuration.warnDeprecatedConfigurationKey(
304 'distributeStationToTenantEqually',
1895299d 305 undefined,
e7aeea18
JB
306 "Use 'supervisionUrlDistribution' instead"
307 );
308 Configuration.warnDeprecatedConfigurationKey(
309 'distributeStationsToTenantsEqually',
1895299d 310 undefined,
e7aeea18
JB
311 "Use 'supervisionUrlDistribution' instead"
312 );
313 return Configuration.objectHasOwnProperty(
314 Configuration.getConfig(),
315 'supervisionUrlDistribution'
316 )
1895299d 317 ? Configuration.getConfig()?.supervisionUrlDistribution
e7aeea18 318 : SupervisionUrlDistribution.ROUND_ROBIN;
7dde0b73 319 }
eb3937cb 320
8b7072dc 321 private static logPrefix = (): string => {
14ecae6a 322 return `${new Date().toLocaleString()} Simulator configuration |`;
8b7072dc 323 };
23132a44 324
e7aeea18
JB
325 private static warnDeprecatedConfigurationKey(
326 key: string,
327 sectionName?: string,
328 logMsgToAppend = ''
329 ) {
e7aeea18
JB
330 if (
331 sectionName &&
332 !Configuration.isUndefined(Configuration.getConfig()[sectionName]) &&
455ee9cf
JB
333 !Configuration.isUndefined(
334 (Configuration.getConfig()[sectionName] as Record<string, unknown>)[key]
335 )
e7aeea18
JB
336 ) {
337 console.error(
338 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
27e40a3b 339 logMsgToAppend.trim().length !== 0 && `. ${logMsgToAppend}`
e7aeea18
JB
340 }}`
341 );
912136b1 342 } else if (!Configuration.isUndefined(Configuration.getConfig()[key])) {
e7aeea18
JB
343 console.error(
344 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
27e40a3b 345 logMsgToAppend.trim().length !== 0 && `. ${logMsgToAppend}`
e7aeea18
JB
346 }}`
347 );
eb3937cb
JB
348 }
349 }
350
351 // Read the config file
1895299d 352 private static getConfig(): ConfigurationData | null {
eb3937cb 353 if (!Configuration.configuration) {
23132a44 354 try {
e7aeea18 355 Configuration.configuration = JSON.parse(
a95873d8 356 fs.readFileSync(Configuration.configurationFile, 'utf8')
e7aeea18 357 ) as ConfigurationData;
23132a44 358 } catch (error) {
e7aeea18 359 Configuration.handleFileException(
a95873d8 360 Configuration.configurationFile,
7164966d
JB
361 FileType.Configuration,
362 error as NodeJS.ErrnoException,
363 Configuration.logPrefix()
e7aeea18 364 );
23132a44 365 }
ded13d97
JB
366 if (!Configuration.configurationFileWatcher) {
367 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
368 }
eb3937cb
JB
369 }
370 return Configuration.configuration;
371 }
963ee397 372
1895299d 373 private static getConfigurationFileWatcher(): fs.FSWatcher | undefined {
23132a44 374 try {
a95873d8 375 return fs.watch(Configuration.configurationFile, (event, filename): void => {
6b060f3d 376 if (filename?.trim().length !== 0 && event === 'change') {
3ec10737
JB
377 // Nullify to force configuration file reading
378 Configuration.configuration = null;
379 if (!Configuration.isUndefined(Configuration.configurationChangeCallback)) {
72092cfc 380 Configuration.configurationChangeCallback().catch((error) => {
dcaf96dc
JB
381 throw typeof error === 'string' ? new Error(error) : error;
382 });
3ec10737 383 }
23132a44
JB
384 }
385 });
386 } catch (error) {
e7aeea18 387 Configuration.handleFileException(
a95873d8 388 Configuration.configurationFile,
7164966d
JB
389 FileType.Configuration,
390 error as NodeJS.ErrnoException,
391 Configuration.logPrefix()
e7aeea18 392 );
23132a44 393 }
ded13d97
JB
394 }
395
b803eefa
JB
396 private static isCFEnvironment(): boolean {
397 return process.env.VCAP_APPLICATION !== undefined;
398 }
399
1f5df42a 400 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
401 switch (storageType) {
402 case StorageType.JSON_FILE:
e7aeea18 403 return `file://${path.join(
0d8140bd 404 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
e7aeea18
JB
405 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
406 )}`;
d5603918 407 case StorageType.SQLITE:
0d8140bd
JB
408 return `file://${path.join(
409 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
5bcb75d4 410 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`
0d8140bd 411 )}`;
d5603918
JB
412 default:
413 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
414 }
415 }
416
b803eefa
JB
417 private static objectHasOwnProperty(object: unknown, property: string): boolean {
418 return Object.prototype.hasOwnProperty.call(object, property) as boolean;
419 }
420
421 private static isUndefined(obj: unknown): boolean {
1895299d 422 return obj === undefined;
b803eefa
JB
423 }
424
e7aeea18 425 private static handleFileException(
e7aeea18 426 filePath: string,
7164966d 427 fileType: FileType,
e7aeea18 428 error: NodeJS.ErrnoException,
7164966d 429 logPrefix: string,
e7aeea18
JB
430 params: HandleErrorParams<EmptyObject> = { throwError: true }
431 ): void {
6b060f3d 432 const prefix = logPrefix?.trim().length !== 0 ? `${logPrefix} ` : '';
6d8b0b0e
JB
433 let logMsg: string;
434 switch (error.code) {
435 case 'ENOENT':
436 logMsg = `${fileType} file ${filePath} not found: `;
437 break;
438 case 'EEXIST':
439 logMsg = `${fileType} file ${filePath} already exists: `;
440 break;
441 case 'EACCES':
442 logMsg = `${fileType} file ${filePath} access denied: `;
443 break;
444 default:
445 logMsg = `${fileType} file ${filePath} error: `;
23132a44 446 }
6d8b0b0e 447 console.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error);
e0a50bcd
JB
448 if (params?.throwError) {
449 throw error;
450 }
23132a44 451 }
7dde0b73 452}