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