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