build(deps): apply updates
[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
878e026c
JB
9// import { Constants, FileUtils, Utils } from './internal';
10import { Constants } from './Constants';
11import { FileUtils } from './FileUtils';
12import { Utils } from './Utils';
83e00df1 13import {
268a74bb 14 ApplicationProtocol,
83e00df1 15 type ConfigurationData,
268a74bb 16 FileType,
83e00df1
JB
17 type StationTemplateUrl,
18 type StorageConfiguration,
268a74bb 19 StorageType,
e7aeea18 20 SupervisionUrlDistribution,
83e00df1
JB
21 type UIServerConfiguration,
22 type WorkerConfiguration,
268a74bb
JB
23} from '../types';
24import { WorkerConstants, WorkerProcessType } from '../worker';
7dde0b73 25
268a74bb 26export 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
ef4932d8 52 return Utils.hasOwnProp(Configuration.getConfig(), 'logStatisticsInterval')
1895299d 53 ? Configuration.getConfig()?.logStatisticsInterval
25f5a959 54 : Constants.DEFAULT_LOG_STATISTICS_INTERVAL;
72f041bd
JB
55 }
56
675fa8e3 57 static getUIServer(): UIServerConfiguration {
ef4932d8 58 if (Utils.hasOwnProp(Configuration.getConfig(), 'uiWebSocketServer')) {
66271092
JB
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 };
ef4932d8 71 if (Utils.hasOwnProp(Configuration.getConfig(), 'uiServer')) {
598c886d
JB
72 uiServerConfiguration = merge<UIServerConfiguration>(
73 uiServerConfiguration,
74 Configuration.getConfig()?.uiServer
75 );
6a49ad23 76 }
dada83ec 77 if (Utils.isCFEnvironment() === true) {
72092cfc 78 delete uiServerConfiguration.options?.host;
b803eefa
JB
79 uiServerConfiguration.options.port = parseInt(process.env.PORT);
80 }
675fa8e3 81 return uiServerConfiguration;
6a49ad23
JB
82 }
83
72f041bd 84 static getPerformanceStorage(): StorageConfiguration {
e7aeea18 85 Configuration.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
6a49ad23
JB
86 let storageConfiguration: StorageConfiguration = {
87 enabled: false,
88 type: StorageType.JSON_FILE,
e7aeea18 89 uri: this.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
6a49ad23 90 };
ef4932d8 91 if (Utils.hasOwnProp(Configuration.getConfig(), 'performanceStorage')) {
e7aeea18 92 storageConfiguration = {
1ba1e8fb 93 ...storageConfiguration,
1895299d 94 ...Configuration.getConfig()?.performanceStorage,
f682b2dc
JB
95 ...(Configuration.getConfig()?.performanceStorage?.type === StorageType.JSON_FILE &&
96 Configuration.getConfig()?.performanceStorage?.uri && {
e8044a69 97 uri: Configuration.buildPerformanceUriFilePath(
f682b2dc 98 new URL(Configuration.getConfig()?.performanceStorage?.uri).pathname
e8044a69 99 ),
f682b2dc 100 }),
72f041bd 101 };
72f041bd
JB
102 }
103 return storageConfiguration;
7dde0b73
JB
104 }
105
1895299d 106 static getAutoReconnectMaxRetries(): number | undefined {
e7aeea18
JB
107 Configuration.warnDeprecatedConfigurationKey(
108 'autoReconnectTimeout',
1895299d 109 undefined,
e7aeea18
JB
110 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
111 );
112 Configuration.warnDeprecatedConfigurationKey(
113 'connectionTimeout',
1895299d 114 undefined,
e7aeea18
JB
115 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
116 );
117 Configuration.warnDeprecatedConfigurationKey(
118 'autoReconnectMaxRetries',
1895299d 119 undefined,
e7aeea18
JB
120 'Use it in charging station template instead'
121 );
7dde0b73 122 // Read conf
ef4932d8 123 if (Utils.hasOwnProp(Configuration.getConfig(), 'autoReconnectMaxRetries')) {
1895299d 124 return Configuration.getConfig()?.autoReconnectMaxRetries;
3574dfd3 125 }
7dde0b73
JB
126 }
127
1895299d 128 static getStationTemplateUrls(): StationTemplateUrl[] | undefined {
e7aeea18
JB
129 Configuration.warnDeprecatedConfigurationKey(
130 'stationTemplateURLs',
1895299d 131 undefined,
e7aeea18
JB
132 "Use 'stationTemplateUrls' instead"
133 );
dada83ec 134 !Utils.isUndefined(Configuration.getConfig()['stationTemplateURLs']) &&
e7aeea18
JB
135 (Configuration.getConfig().stationTemplateUrls = Configuration.getConfig()[
136 'stationTemplateURLs'
617cad0c 137 ] as StationTemplateUrl[]);
7436ee0d
JB
138 Configuration.getConfig().stationTemplateUrls.forEach(
139 (stationTemplateUrl: StationTemplateUrl) => {
140 if (!Utils.isUndefined(stationTemplateUrl['numberOfStation'])) {
141 console.error(
142 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
143 stationTemplateUrl.file
144 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
145 );
146 }
eb3937cb 147 }
7436ee0d 148 );
7dde0b73 149 // Read conf
1895299d 150 return Configuration.getConfig()?.stationTemplateUrls;
7dde0b73
JB
151 }
152
cf2a5d9b 153 static getWorker(): WorkerConfiguration {
e7aeea18 154 Configuration.warnDeprecatedConfigurationKey(
e80bc579 155 'useWorkerPool',
1895299d 156 undefined,
cf2a5d9b
JB
157 "Use 'worker' section to define the type of worker process model instead"
158 );
159 Configuration.warnDeprecatedConfigurationKey(
160 'workerProcess',
1895299d 161 undefined,
cf2a5d9b
JB
162 "Use 'worker' section to define the type of worker process model instead"
163 );
164 Configuration.warnDeprecatedConfigurationKey(
165 'workerStartDelay',
1895299d 166 undefined,
cf2a5d9b
JB
167 "Use 'worker' section to define the worker start delay instead"
168 );
169 Configuration.warnDeprecatedConfigurationKey(
170 'chargingStationsPerWorker',
1895299d 171 undefined,
cf2a5d9b
JB
172 "Use 'worker' section to define the number of element(s) per worker instead"
173 );
174 Configuration.warnDeprecatedConfigurationKey(
175 'elementStartDelay',
1895299d 176 undefined,
cf2a5d9b
JB
177 "Use 'worker' section to define the worker's element start delay instead"
178 );
179 Configuration.warnDeprecatedConfigurationKey(
180 'workerPoolMinSize',
1895299d 181 undefined,
cf2a5d9b 182 "Use 'worker' section to define the worker pool minimum size instead"
e7aeea18 183 );
e7aeea18
JB
184 Configuration.warnDeprecatedConfigurationKey(
185 'workerPoolSize;',
1895299d 186 undefined,
cf2a5d9b 187 "Use 'worker' section to define the worker pool maximum size instead"
e7aeea18 188 );
cf2a5d9b
JB
189 Configuration.warnDeprecatedConfigurationKey(
190 'workerPoolMaxSize;',
1895299d 191 undefined,
cf2a5d9b
JB
192 "Use 'worker' section to define the worker pool maximum size instead"
193 );
194 Configuration.warnDeprecatedConfigurationKey(
195 'workerPoolStrategy;',
1895299d 196 undefined,
cf2a5d9b
JB
197 "Use 'worker' section to define the worker pool strategy instead"
198 );
c127bd64 199 let workerConfiguration: WorkerConfiguration = {
ef4932d8 200 processType: Utils.hasOwnProp(Configuration.getConfig(), 'workerProcess')
1895299d 201 ? Configuration.getConfig()?.workerProcess
721646e9 202 : WorkerProcessType.workerSet,
ef4932d8 203 startDelay: Utils.hasOwnProp(Configuration.getConfig(), 'workerStartDelay')
1895299d 204 ? Configuration.getConfig()?.workerStartDelay
cf2a5d9b 205 : WorkerConstants.DEFAULT_WORKER_START_DELAY,
ef4932d8 206 elementsPerWorker: Utils.hasOwnProp(Configuration.getConfig(), 'chargingStationsPerWorker')
1895299d 207 ? Configuration.getConfig()?.chargingStationsPerWorker
cf2a5d9b 208 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER,
ef4932d8 209 elementStartDelay: Utils.hasOwnProp(Configuration.getConfig(), 'elementStartDelay')
1895299d 210 ? Configuration.getConfig()?.elementStartDelay
cf2a5d9b 211 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY,
ef4932d8 212 poolMinSize: Utils.hasOwnProp(Configuration.getConfig(), 'workerPoolMinSize')
1895299d 213 ? Configuration.getConfig()?.workerPoolMinSize
cf2a5d9b 214 : WorkerConstants.DEFAULT_POOL_MIN_SIZE,
ef4932d8 215 poolMaxSize: Utils.hasOwnProp(Configuration.getConfig(), 'workerPoolMaxSize')
1895299d 216 ? Configuration.getConfig()?.workerPoolMaxSize
cf2a5d9b 217 : WorkerConstants.DEFAULT_POOL_MAX_SIZE,
156cab2c 218 poolStrategy:
1895299d 219 Configuration.getConfig()?.workerPoolStrategy ?? WorkerChoiceStrategies.ROUND_ROBIN,
cf2a5d9b 220 };
ef4932d8 221 if (Utils.hasOwnProp(Configuration.getConfig(), 'worker')) {
1895299d 222 workerConfiguration = { ...workerConfiguration, ...Configuration.getConfig()?.worker };
cf2a5d9b
JB
223 }
224 return workerConfiguration;
3d2ff9e4
J
225 }
226
1895299d
JB
227 static getLogConsole(): boolean | undefined {
228 Configuration.warnDeprecatedConfigurationKey(
229 'consoleLog',
230 undefined,
231 "Use 'logConsole' instead"
232 );
ef4932d8 233 return Utils.hasOwnProp(Configuration.getConfig(), 'logConsole')
1895299d 234 ? Configuration.getConfig()?.logConsole
e7aeea18 235 : false;
7dde0b73
JB
236 }
237
1895299d 238 static getLogFormat(): string | undefined {
ef4932d8 239 return Utils.hasOwnProp(Configuration.getConfig(), 'logFormat')
1895299d 240 ? Configuration.getConfig()?.logFormat
e7aeea18 241 : 'simple';
027b409a
JB
242 }
243
1895299d 244 static getLogRotate(): boolean | undefined {
ef4932d8 245 return Utils.hasOwnProp(Configuration.getConfig(), 'logRotate')
1895299d 246 ? Configuration.getConfig()?.logRotate
e7aeea18 247 : true;
6bf6769e
JB
248 }
249
1895299d 250 static getLogMaxFiles(): number | string | false | undefined {
9988696d 251 return (
ef4932d8 252 Utils.hasOwnProp(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 253 Configuration.getConfig()?.logMaxFiles
9988696d
JB
254 );
255 }
256
1895299d 257 static getLogMaxSize(): number | string | false | undefined {
9988696d 258 return (
ef4932d8 259 Utils.hasOwnProp(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 260 Configuration.getConfig()?.logMaxSize
9988696d 261 );
6bf6769e
JB
262 }
263
1895299d 264 static getLogLevel(): string | undefined {
ef4932d8 265 return Utils.hasOwnProp(Configuration.getConfig(), 'logLevel')
1895299d 266 ? Configuration.getConfig()?.logLevel?.toLowerCase()
e7aeea18 267 : 'info';
2e6f5966
JB
268 }
269
1895299d 270 static getLogFile(): string | undefined {
ef4932d8 271 return Utils.hasOwnProp(Configuration.getConfig(), 'logFile')
1895299d 272 ? Configuration.getConfig()?.logFile
e7aeea18 273 : 'combined.log';
7dde0b73
JB
274 }
275
1895299d
JB
276 static getLogErrorFile(): string | undefined {
277 Configuration.warnDeprecatedConfigurationKey(
278 'errorFile',
279 undefined,
280 "Use 'logErrorFile' instead"
281 );
ef4932d8 282 return Utils.hasOwnProp(Configuration.getConfig(), 'logErrorFile')
1895299d 283 ? Configuration.getConfig()?.logErrorFile
e7aeea18 284 : 'error.log';
7dde0b73
JB
285 }
286
1895299d 287 static getSupervisionUrls(): string | string[] | undefined {
e7aeea18
JB
288 Configuration.warnDeprecatedConfigurationKey(
289 'supervisionURLs',
1895299d 290 undefined,
e7aeea18
JB
291 "Use 'supervisionUrls' instead"
292 );
dada83ec 293 !Utils.isUndefined(Configuration.getConfig()['supervisionURLs']) &&
1895299d
JB
294 (Configuration.getConfig().supervisionUrls = Configuration.getConfig()['supervisionURLs'] as
295 | string
296 | string[]);
7dde0b73 297 // Read conf
1895299d 298 return Configuration.getConfig()?.supervisionUrls;
7dde0b73
JB
299 }
300
1895299d 301 static getSupervisionUrlDistribution(): SupervisionUrlDistribution | undefined {
e7aeea18
JB
302 Configuration.warnDeprecatedConfigurationKey(
303 'distributeStationToTenantEqually',
1895299d 304 undefined,
e7aeea18
JB
305 "Use 'supervisionUrlDistribution' instead"
306 );
307 Configuration.warnDeprecatedConfigurationKey(
308 'distributeStationsToTenantsEqually',
1895299d 309 undefined,
e7aeea18
JB
310 "Use 'supervisionUrlDistribution' instead"
311 );
ef4932d8 312 return Utils.hasOwnProp(Configuration.getConfig(), 'supervisionUrlDistribution')
1895299d 313 ? Configuration.getConfig()?.supervisionUrlDistribution
e7aeea18 314 : SupervisionUrlDistribution.ROUND_ROBIN;
7dde0b73 315 }
eb3937cb 316
8b7072dc 317 private static logPrefix = (): string => {
14ecae6a 318 return `${new Date().toLocaleString()} Simulator configuration |`;
8b7072dc 319 };
23132a44 320
e7aeea18
JB
321 private static warnDeprecatedConfigurationKey(
322 key: string,
323 sectionName?: string,
324 logMsgToAppend = ''
325 ) {
e7aeea18
JB
326 if (
327 sectionName &&
dada83ec
JB
328 !Utils.isUndefined(Configuration.getConfig()[sectionName]) &&
329 !Utils.isUndefined((Configuration.getConfig()[sectionName] as Record<string, unknown>)[key])
e7aeea18
JB
330 ) {
331 console.error(
332 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
e302df1d 333 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
e7aeea18
JB
334 }}`
335 );
dada83ec 336 } else if (!Utils.isUndefined(Configuration.getConfig()[key])) {
e7aeea18
JB
337 console.error(
338 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
e302df1d 339 logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
e7aeea18
JB
340 }}`
341 );
eb3937cb
JB
342 }
343 }
344
345 // Read the config file
1895299d 346 private static getConfig(): ConfigurationData | null {
eb3937cb 347 if (!Configuration.configuration) {
23132a44 348 try {
e7aeea18 349 Configuration.configuration = JSON.parse(
a95873d8 350 fs.readFileSync(Configuration.configurationFile, 'utf8')
e7aeea18 351 ) as ConfigurationData;
23132a44 352 } catch (error) {
dada83ec 353 FileUtils.handleFileException(
a95873d8 354 Configuration.configurationFile,
7164966d
JB
355 FileType.Configuration,
356 error as NodeJS.ErrnoException,
dada83ec
JB
357 Configuration.logPrefix(),
358 { consoleOut: true }
e7aeea18 359 );
23132a44 360 }
ded13d97
JB
361 if (!Configuration.configurationFileWatcher) {
362 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
363 }
eb3937cb
JB
364 }
365 return Configuration.configuration;
366 }
963ee397 367
1895299d 368 private static getConfigurationFileWatcher(): fs.FSWatcher | undefined {
23132a44 369 try {
a95873d8 370 return fs.watch(Configuration.configurationFile, (event, filename): void => {
8d54dcc0 371 if (filename?.trim().length > 0 && event === 'change') {
3ec10737
JB
372 // Nullify to force configuration file reading
373 Configuration.configuration = null;
dada83ec 374 if (!Utils.isUndefined(Configuration.configurationChangeCallback)) {
72092cfc 375 Configuration.configurationChangeCallback().catch((error) => {
dcaf96dc
JB
376 throw typeof error === 'string' ? new Error(error) : error;
377 });
3ec10737 378 }
23132a44
JB
379 }
380 });
381 } catch (error) {
dada83ec 382 FileUtils.handleFileException(
a95873d8 383 Configuration.configurationFile,
7164966d
JB
384 FileType.Configuration,
385 error as NodeJS.ErrnoException,
dada83ec
JB
386 Configuration.logPrefix(),
387 { consoleOut: true }
e7aeea18 388 );
23132a44 389 }
ded13d97
JB
390 }
391
1f5df42a 392 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
393 switch (storageType) {
394 case StorageType.JSON_FILE:
e8044a69 395 return Configuration.buildPerformanceUriFilePath(
e7aeea18 396 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
e8044a69 397 );
d5603918 398 case StorageType.SQLITE:
e8044a69 399 return Configuration.buildPerformanceUriFilePath(
5bcb75d4 400 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`
e8044a69 401 );
d5603918
JB
402 default:
403 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
404 }
405 }
e8044a69
JB
406
407 private static buildPerformanceUriFilePath(file: string) {
408 return `file://${path.join(
409 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
410 file
411 )}`;
412 }
7dde0b73 413}