Update default configuration file for docker
[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,
cf2a5d9b 7 WorkerConfiguration,
e7aeea18 8} from '../types/ConfigurationData';
e118beaa 9
322c9192 10import Constants from './Constants';
717c1e56 11import { EmptyObject } from '../types/EmptyObject';
a95873d8 12import { FileType } from '../types/FileType';
e0a50bcd 13import { HandleErrorParams } from '../types/Error';
72f041bd 14import { StorageType } from '../types/Storage';
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
cf2a5d9b 164 static getWorker(): WorkerConfiguration {
e7aeea18 165 Configuration.warnDeprecatedConfigurationKey(
e80bc579 166 'useWorkerPool',
e7aeea18 167 null,
cf2a5d9b
JB
168 "Use 'worker' section to define the type of worker process model instead"
169 );
170 Configuration.warnDeprecatedConfigurationKey(
171 'workerProcess',
172 null,
173 "Use 'worker' section to define the type of worker process model instead"
174 );
175 Configuration.warnDeprecatedConfigurationKey(
176 'workerStartDelay',
177 null,
178 "Use 'worker' section to define the worker start delay instead"
179 );
180 Configuration.warnDeprecatedConfigurationKey(
181 'chargingStationsPerWorker',
182 null,
183 "Use 'worker' section to define the number of element(s) per worker instead"
184 );
185 Configuration.warnDeprecatedConfigurationKey(
186 'elementStartDelay',
187 null,
188 "Use 'worker' section to define the worker's element start delay instead"
189 );
190 Configuration.warnDeprecatedConfigurationKey(
191 'workerPoolMinSize',
192 null,
193 "Use 'worker' section to define the worker pool minimum size instead"
e7aeea18 194 );
e7aeea18
JB
195 Configuration.warnDeprecatedConfigurationKey(
196 'workerPoolSize;',
197 null,
cf2a5d9b 198 "Use 'worker' section to define the worker pool maximum size instead"
e7aeea18 199 );
cf2a5d9b
JB
200 Configuration.warnDeprecatedConfigurationKey(
201 'workerPoolMaxSize;',
202 null,
203 "Use 'worker' section to define the worker pool maximum size instead"
204 );
205 Configuration.warnDeprecatedConfigurationKey(
206 'workerPoolStrategy;',
207 null,
208 "Use 'worker' section to define the worker pool strategy instead"
209 );
210 const workerConfiguration: WorkerConfiguration = {
211 processType: Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerProcess')
212 ? Configuration.getConfig().workerProcess
213 : WorkerProcessType.WORKER_SET,
214 startDelay: Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerStartDelay')
215 ? Configuration.getConfig().workerStartDelay
216 : WorkerConstants.DEFAULT_WORKER_START_DELAY,
217 elementsPerWorker: Configuration.objectHasOwnProperty(
218 Configuration.getConfig(),
219 'chargingStationsPerWorker'
220 )
221 ? Configuration.getConfig().chargingStationsPerWorker
222 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER,
223 elementStartDelay: Configuration.objectHasOwnProperty(
224 Configuration.getConfig(),
225 'elementStartDelay'
226 )
227 ? Configuration.getConfig().elementStartDelay
228 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY,
229 poolMinSize: Configuration.objectHasOwnProperty(
230 Configuration.getConfig(),
231 'workerPoolMinSize'
232 )
233 ? Configuration.getConfig().workerPoolMinSize
234 : WorkerConstants.DEFAULT_POOL_MIN_SIZE,
235 poolMaxSize: Configuration.objectHasOwnProperty(
236 Configuration.getConfig(),
237 'workerPoolMaxSize'
238 )
239 ? Configuration.getConfig().workerPoolMaxSize
240 : WorkerConstants.DEFAULT_POOL_MAX_SIZE,
241 poolStrategy: Configuration.getConfig().workerPoolStrategy,
242 };
243 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'worker')) {
244 return { ...workerConfiguration, ...Configuration.getConfig().worker };
245 }
246 return workerConfiguration;
3d2ff9e4
J
247 }
248
7ec46a9a 249 static getLogConsole(): boolean {
e7aeea18
JB
250 Configuration.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
251 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
252 ? Configuration.getConfig().logConsole
253 : false;
7dde0b73
JB
254 }
255
a4a21709 256 static getLogFormat(): string {
e7aeea18
JB
257 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
258 ? Configuration.getConfig().logFormat
259 : 'simple';
027b409a
JB
260 }
261
6bf6769e 262 static getLogRotate(): boolean {
e7aeea18
JB
263 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logRotate')
264 ? Configuration.getConfig().logRotate
265 : true;
6bf6769e
JB
266 }
267
268 static getLogMaxFiles(): number {
e7aeea18
JB
269 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles')
270 ? Configuration.getConfig().logMaxFiles
271 : 7;
6bf6769e
JB
272 }
273
324fd4ee 274 static getLogLevel(): string {
e7aeea18
JB
275 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
276 ? Configuration.getConfig().logLevel.toLowerCase()
277 : 'info';
2e6f5966
JB
278 }
279
a4a21709 280 static getLogFile(): string {
e7aeea18
JB
281 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFile')
282 ? Configuration.getConfig().logFile
283 : 'combined.log';
7dde0b73
JB
284 }
285
7ec46a9a 286 static getLogErrorFile(): string {
e7aeea18
JB
287 Configuration.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
288 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile')
289 ? Configuration.getConfig().logErrorFile
290 : 'error.log';
7dde0b73
JB
291 }
292
2dcfe98e 293 static getSupervisionUrls(): string | string[] {
e7aeea18
JB
294 Configuration.warnDeprecatedConfigurationKey(
295 'supervisionURLs',
296 null,
297 "Use 'supervisionUrls' instead"
298 );
299 !Configuration.isUndefined(Configuration.getConfig()['supervisionURLs']) &&
300 (Configuration.getConfig().supervisionUrls = Configuration.getConfig()[
301 'supervisionURLs'
302 ] as string[]);
7dde0b73 303 // Read conf
1f5df42a 304 return Configuration.getConfig().supervisionUrls;
7dde0b73
JB
305 }
306
2dcfe98e 307 static getSupervisionUrlDistribution(): SupervisionUrlDistribution {
e7aeea18
JB
308 Configuration.warnDeprecatedConfigurationKey(
309 'distributeStationToTenantEqually',
310 null,
311 "Use 'supervisionUrlDistribution' instead"
312 );
313 Configuration.warnDeprecatedConfigurationKey(
314 'distributeStationsToTenantsEqually',
315 null,
316 "Use 'supervisionUrlDistribution' instead"
317 );
318 return Configuration.objectHasOwnProperty(
319 Configuration.getConfig(),
320 'supervisionUrlDistribution'
321 )
322 ? Configuration.getConfig().supervisionUrlDistribution
323 : SupervisionUrlDistribution.ROUND_ROBIN;
7dde0b73 324 }
eb3937cb 325
23132a44
JB
326 private static logPrefix(): string {
327 return new Date().toLocaleString() + ' Simulator configuration |';
328 }
329
e7aeea18
JB
330 private static warnDeprecatedConfigurationKey(
331 key: string,
332 sectionName?: string,
333 logMsgToAppend = ''
334 ) {
e7aeea18
JB
335 if (
336 sectionName &&
337 !Configuration.isUndefined(Configuration.getConfig()[sectionName]) &&
455ee9cf
JB
338 !Configuration.isUndefined(
339 (Configuration.getConfig()[sectionName] as Record<string, unknown>)[key]
340 )
e7aeea18
JB
341 ) {
342 console.error(
343 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
344 logMsgToAppend && '. ' + logMsgToAppend
345 }}`
346 );
912136b1 347 } else if (!Configuration.isUndefined(Configuration.getConfig()[key])) {
e7aeea18
JB
348 console.error(
349 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
350 logMsgToAppend && '. ' + logMsgToAppend
351 }}`
352 );
eb3937cb
JB
353 }
354 }
355
356 // Read the config file
357 private static getConfig(): ConfigurationData {
358 if (!Configuration.configuration) {
23132a44 359 try {
e7aeea18 360 Configuration.configuration = JSON.parse(
a95873d8 361 fs.readFileSync(Configuration.configurationFile, 'utf8')
e7aeea18 362 ) as ConfigurationData;
23132a44 363 } catch (error) {
e7aeea18
JB
364 Configuration.handleFileException(
365 Configuration.logPrefix(),
a95873d8
JB
366 FileType.Configuration,
367 Configuration.configurationFile,
3fa0f0ed 368 error as NodeJS.ErrnoException
e7aeea18 369 );
23132a44 370 }
ded13d97
JB
371 if (!Configuration.configurationFileWatcher) {
372 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
373 }
eb3937cb
JB
374 }
375 return Configuration.configuration;
376 }
963ee397 377
ded13d97 378 private static getConfigurationFileWatcher(): fs.FSWatcher {
23132a44 379 try {
a95873d8 380 return fs.watch(Configuration.configurationFile, (event, filename): void => {
3ec10737
JB
381 if (filename && event === 'change') {
382 // Nullify to force configuration file reading
383 Configuration.configuration = null;
384 if (!Configuration.isUndefined(Configuration.configurationChangeCallback)) {
dcaf96dc
JB
385 Configuration.configurationChangeCallback().catch((error) => {
386 throw typeof error === 'string' ? new Error(error) : error;
387 });
3ec10737 388 }
23132a44
JB
389 }
390 });
391 } catch (error) {
e7aeea18
JB
392 Configuration.handleFileException(
393 Configuration.logPrefix(),
a95873d8
JB
394 FileType.Configuration,
395 Configuration.configurationFile,
396 error as NodeJS.ErrnoException
e7aeea18 397 );
23132a44 398 }
ded13d97
JB
399 }
400
1f5df42a 401 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
d5603918
JB
402 const SQLiteFileName = `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
403 switch (storageType) {
404 case StorageType.JSON_FILE:
e7aeea18 405 return `file://${path.join(
0d8140bd 406 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
e7aeea18
JB
407 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
408 )}`;
d5603918 409 case StorageType.SQLITE:
0d8140bd
JB
410 return `file://${path.join(
411 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
412 SQLiteFileName
413 )}`;
d5603918
JB
414 default:
415 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
416 }
417 }
418
73d09045 419 private static objectHasOwnProperty(object: unknown, property: string): boolean {
23132a44 420 return Object.prototype.hasOwnProperty.call(object, property) as boolean;
963ee397
JB
421 }
422
73d09045 423 private static isUndefined(obj: unknown): boolean {
963ee397
JB
424 return typeof obj === 'undefined';
425 }
23132a44 426
e7aeea18
JB
427 private static handleFileException(
428 logPrefix: string,
a95873d8 429 fileType: FileType,
e7aeea18
JB
430 filePath: string,
431 error: NodeJS.ErrnoException,
432 params: HandleErrorParams<EmptyObject> = { throwError: true }
433 ): void {
23132a44
JB
434 const prefix = logPrefix.length !== 0 ? logPrefix + ' ' : '';
435 if (error.code === 'ENOENT') {
e7aeea18
JB
436 console.error(
437 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' not found: '),
438 error
439 );
72f041bd 440 } else if (error.code === 'EEXIST') {
e7aeea18
JB
441 console.error(
442 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' already exists: '),
443 error
444 );
72f041bd 445 } else if (error.code === 'EACCES') {
e7aeea18
JB
446 console.error(
447 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' access denied: '),
448 error
449 );
23132a44 450 } else {
e7aeea18
JB
451 console.error(
452 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' error: '),
453 error
454 );
23132a44 455 }
e0a50bcd
JB
456 if (params?.throwError) {
457 throw error;
458 }
23132a44 459 }
7dde0b73 460}