refactor(simulator): explicitly export needed types only
[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
dada83ec 9import { Constants, FileUtils, Utils } from './internal';
83e00df1 10import {
268a74bb 11 ApplicationProtocol,
83e00df1 12 type ConfigurationData,
268a74bb 13 FileType,
83e00df1
JB
14 type StationTemplateUrl,
15 type StorageConfiguration,
268a74bb 16 StorageType,
e7aeea18 17 SupervisionUrlDistribution,
83e00df1
JB
18 type UIServerConfiguration,
19 type WorkerConfiguration,
268a74bb
JB
20} from '../types';
21import { WorkerConstants, WorkerProcessType } from '../worker';
7dde0b73 22
268a74bb 23export class Configuration {
a95873d8 24 private static configurationFile = path.join(
0d8140bd 25 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
e7aeea18
JB
26 'assets',
27 'config.json'
28 );
10068088 29
1895299d 30 private static configurationFileWatcher: fs.FSWatcher | undefined;
6e0964c8 31 private static configuration: ConfigurationData | null = null;
e57acf6a
JB
32 private static configurationChangeCallback: () => Promise<void>;
33
d5bd1c00
JB
34 private constructor() {
35 // This is intentional
36 }
37
e57acf6a
JB
38 static setConfigurationChangeCallback(cb: () => Promise<void>): void {
39 Configuration.configurationChangeCallback = cb;
40 }
7dde0b73 41
1895299d 42 static getLogStatisticsInterval(): number | undefined {
e7aeea18
JB
43 Configuration.warnDeprecatedConfigurationKey(
44 'statisticsDisplayInterval',
1895299d 45 undefined,
e7aeea18
JB
46 "Use 'logStatisticsInterval' instead"
47 );
7dde0b73 48 // Read conf
dada83ec 49 return Utils.objectHasOwnProperty(Configuration.getConfig(), 'logStatisticsInterval')
1895299d 50 ? Configuration.getConfig()?.logStatisticsInterval
25f5a959 51 : Constants.DEFAULT_LOG_STATISTICS_INTERVAL;
72f041bd
JB
52 }
53
675fa8e3 54 static getUIServer(): UIServerConfiguration {
dada83ec 55 if (Utils.objectHasOwnProperty(Configuration.getConfig(), 'uiWebSocketServer')) {
66271092
JB
56 console.error(
57 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
58 );
59 }
675fa8e3 60 let uiServerConfiguration: UIServerConfiguration = {
b803eefa 61 enabled: false,
1f7fa4de 62 type: ApplicationProtocol.WS,
c127bd64 63 options: {
adbddcb4
JB
64 host: Constants.DEFAULT_UI_SERVER_HOST,
65 port: Constants.DEFAULT_UI_SERVER_PORT,
c127bd64 66 },
6a49ad23 67 };
dada83ec 68 if (Utils.objectHasOwnProperty(Configuration.getConfig(), 'uiServer')) {
598c886d
JB
69 uiServerConfiguration = merge<UIServerConfiguration>(
70 uiServerConfiguration,
71 Configuration.getConfig()?.uiServer
72 );
6a49ad23 73 }
dada83ec 74 if (Utils.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 };
dada83ec 88 if (Utils.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
dada83ec 114 if (Utils.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 );
dada83ec 125 !Utils.isUndefined(Configuration.getConfig()['stationTemplateURLs']) &&
e7aeea18
JB
126 (Configuration.getConfig().stationTemplateUrls = Configuration.getConfig()[
127 'stationTemplateURLs'
1895299d 128 ] as unknown as StationTemplateUrl[]);
1f5df42a 129 Configuration.getConfig().stationTemplateUrls.forEach((stationUrl: StationTemplateUrl) => {
dada83ec 130 if (!Utils.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 = {
dada83ec 189 processType: Utils.objectHasOwnProperty(Configuration.getConfig(), 'workerProcess')
1895299d 190 ? Configuration.getConfig()?.workerProcess
cf2a5d9b 191 : WorkerProcessType.WORKER_SET,
dada83ec 192 startDelay: Utils.objectHasOwnProperty(Configuration.getConfig(), 'workerStartDelay')
1895299d 193 ? Configuration.getConfig()?.workerStartDelay
cf2a5d9b 194 : WorkerConstants.DEFAULT_WORKER_START_DELAY,
dada83ec 195 elementsPerWorker: Utils.objectHasOwnProperty(
cf2a5d9b
JB
196 Configuration.getConfig(),
197 'chargingStationsPerWorker'
198 )
1895299d 199 ? Configuration.getConfig()?.chargingStationsPerWorker
cf2a5d9b 200 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER,
dada83ec 201 elementStartDelay: Utils.objectHasOwnProperty(Configuration.getConfig(), 'elementStartDelay')
1895299d 202 ? Configuration.getConfig()?.elementStartDelay
cf2a5d9b 203 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY,
dada83ec 204 poolMinSize: Utils.objectHasOwnProperty(Configuration.getConfig(), 'workerPoolMinSize')
1895299d 205 ? Configuration.getConfig()?.workerPoolMinSize
cf2a5d9b 206 : WorkerConstants.DEFAULT_POOL_MIN_SIZE,
dada83ec 207 poolMaxSize: Utils.objectHasOwnProperty(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 };
dada83ec 213 if (Utils.objectHasOwnProperty(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 );
dada83ec 225 return Utils.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
1895299d 226 ? Configuration.getConfig()?.logConsole
e7aeea18 227 : false;
7dde0b73
JB
228 }
229
1895299d 230 static getLogFormat(): string | undefined {
dada83ec 231 return Utils.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
1895299d 232 ? Configuration.getConfig()?.logFormat
e7aeea18 233 : 'simple';
027b409a
JB
234 }
235
1895299d 236 static getLogRotate(): boolean | undefined {
dada83ec 237 return Utils.objectHasOwnProperty(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 (
dada83ec 244 Utils.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 245 Configuration.getConfig()?.logMaxFiles
9988696d
JB
246 );
247 }
248
1895299d 249 static getLogMaxSize(): number | string | false | undefined {
9988696d 250 return (
dada83ec 251 Utils.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles') &&
1895299d 252 Configuration.getConfig()?.logMaxSize
9988696d 253 );
6bf6769e
JB
254 }
255
1895299d 256 static getLogLevel(): string | undefined {
dada83ec 257 return Utils.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
1895299d 258 ? Configuration.getConfig()?.logLevel?.toLowerCase()
e7aeea18 259 : 'info';
2e6f5966
JB
260 }
261
1895299d 262 static getLogFile(): string | undefined {
dada83ec 263 return Utils.objectHasOwnProperty(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 );
dada83ec 274 return Utils.objectHasOwnProperty(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 );
dada83ec 304 return Utils.objectHasOwnProperty(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}'${
8d54dcc0 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${
8d54dcc0 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}