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