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