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