3fec05a882ba2803339307c83a9edbcf625ea9f2
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
1 import ConfigurationData, {
2 ServerOptions,
3 StationTemplateUrl,
4 StorageConfiguration,
5 SupervisionUrlDistribution,
6 UIServerConfiguration,
7 WorkerConfiguration,
8 } from '../types/ConfigurationData';
9
10 import Constants from './Constants';
11 import { EmptyObject } from '../types/EmptyObject';
12 import { FileType } from '../types/FileType';
13 import { HandleErrorParams } from '../types/Error';
14 import { StorageType } from '../types/Storage';
15 import WorkerConstants from '../worker/WorkerConstants';
16 import { WorkerProcessType } from '../types/Worker';
17 import chalk from 'chalk';
18 import { fileURLToPath } from 'url';
19 import fs from 'fs';
20 import path from 'path';
21
22 export default class Configuration {
23 private static configurationFile = path.join(
24 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
25 'assets',
26 'config.json'
27 );
28
29 private static configurationFileWatcher: fs.FSWatcher;
30 private static configuration: ConfigurationData | null = null;
31 private static configurationChangeCallback: () => Promise<void>;
32
33 static setConfigurationChangeCallback(cb: () => Promise<void>): void {
34 Configuration.configurationChangeCallback = cb;
35 }
36
37 static getLogStatisticsInterval(): number {
38 Configuration.warnDeprecatedConfigurationKey(
39 'statisticsDisplayInterval',
40 null,
41 "Use 'logStatisticsInterval' instead"
42 );
43 // Read conf
44 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logStatisticsInterval')
45 ? Configuration.getConfig().logStatisticsInterval
46 : Constants.DEFAULT_LOG_STATISTICS_INTERVAL;
47 }
48
49 static getUIServer(): UIServerConfiguration {
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 }
55 let options: ServerOptions = {
56 host: Constants.DEFAULT_UI_WEBSOCKET_SERVER_HOST,
57 port: Constants.DEFAULT_UI_WEBSOCKET_SERVER_PORT,
58 };
59 let uiServerConfiguration: UIServerConfiguration = {
60 enabled: true,
61 options,
62 };
63 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'uiServer')) {
64 if (Configuration.objectHasOwnProperty(Configuration.getConfig().uiServer, 'options')) {
65 options = {
66 ...options,
67 ...(Configuration.objectHasOwnProperty(
68 Configuration.getConfig().uiServer.options,
69 'host'
70 ) && { host: Configuration.getConfig().uiServer.options.host }),
71 ...(Configuration.objectHasOwnProperty(
72 Configuration.getConfig().uiServer.options,
73 'port'
74 ) && { port: Configuration.getConfig().uiServer.options.port }),
75 };
76 }
77 uiServerConfiguration = {
78 ...uiServerConfiguration,
79 ...(Configuration.objectHasOwnProperty(Configuration.getConfig().uiServer, 'enabled') && {
80 enabled: Configuration.getConfig().uiServer.enabled,
81 }),
82 options,
83 };
84 }
85 return uiServerConfiguration;
86 }
87
88 static getPerformanceStorage(): StorageConfiguration {
89 Configuration.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
90 let storageConfiguration: StorageConfiguration = {
91 enabled: false,
92 type: StorageType.JSON_FILE,
93 uri: this.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
94 };
95 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'performanceStorage')) {
96 storageConfiguration = {
97 ...storageConfiguration,
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 }),
114 };
115 }
116 return storageConfiguration;
117 }
118
119 static getAutoReconnectMaxRetries(): number {
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 );
135 // Read conf
136 if (Configuration.objectHasOwnProperty(Configuration.getConfig(), 'autoReconnectMaxRetries')) {
137 return Configuration.getConfig().autoReconnectMaxRetries;
138 }
139 }
140
141 static getStationTemplateUrls(): StationTemplateUrl[] {
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[]);
151 Configuration.getConfig().stationTemplateUrls.forEach((stationUrl: StationTemplateUrl) => {
152 if (!Configuration.isUndefined(stationUrl['numberOfStation'])) {
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 );
158 }
159 });
160 // Read conf
161 return Configuration.getConfig().stationTemplateUrls;
162 }
163
164 static getWorker(): WorkerConfiguration {
165 Configuration.warnDeprecatedConfigurationKey(
166 'useWorkerPool',
167 null,
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"
194 );
195 Configuration.warnDeprecatedConfigurationKey(
196 'workerPoolSize;',
197 null,
198 "Use 'worker' section to define the worker pool maximum size instead"
199 );
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;
247 }
248
249 static getLogConsole(): boolean {
250 Configuration.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
251 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
252 ? Configuration.getConfig().logConsole
253 : false;
254 }
255
256 static getLogFormat(): string {
257 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
258 ? Configuration.getConfig().logFormat
259 : 'simple';
260 }
261
262 static getLogRotate(): boolean {
263 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logRotate')
264 ? Configuration.getConfig().logRotate
265 : true;
266 }
267
268 static getLogMaxFiles(): number {
269 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles')
270 ? Configuration.getConfig().logMaxFiles
271 : 7;
272 }
273
274 static getLogLevel(): string {
275 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
276 ? Configuration.getConfig().logLevel.toLowerCase()
277 : 'info';
278 }
279
280 static getLogFile(): string {
281 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFile')
282 ? Configuration.getConfig().logFile
283 : 'combined.log';
284 }
285
286 static getLogErrorFile(): string {
287 Configuration.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
288 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile')
289 ? Configuration.getConfig().logErrorFile
290 : 'error.log';
291 }
292
293 static getSupervisionUrls(): string | string[] {
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[]);
303 // Read conf
304 return Configuration.getConfig().supervisionUrls;
305 }
306
307 static getSupervisionUrlDistribution(): SupervisionUrlDistribution {
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;
324 }
325
326 private static logPrefix(): string {
327 return new Date().toLocaleString() + ' Simulator configuration |';
328 }
329
330 private static warnDeprecatedConfigurationKey(
331 key: string,
332 sectionName?: string,
333 logMsgToAppend = ''
334 ) {
335 if (
336 sectionName &&
337 !Configuration.isUndefined(Configuration.getConfig()[sectionName]) &&
338 !Configuration.isUndefined(
339 (Configuration.getConfig()[sectionName] as Record<string, unknown>)[key]
340 )
341 ) {
342 console.error(
343 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
344 logMsgToAppend && '. ' + logMsgToAppend
345 }}`
346 );
347 } else if (!Configuration.isUndefined(Configuration.getConfig()[key])) {
348 console.error(
349 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
350 logMsgToAppend && '. ' + logMsgToAppend
351 }}`
352 );
353 }
354 }
355
356 // Read the config file
357 private static getConfig(): ConfigurationData {
358 if (!Configuration.configuration) {
359 try {
360 Configuration.configuration = JSON.parse(
361 fs.readFileSync(Configuration.configurationFile, 'utf8')
362 ) as ConfigurationData;
363 } catch (error) {
364 Configuration.handleFileException(
365 Configuration.logPrefix(),
366 FileType.Configuration,
367 Configuration.configurationFile,
368 error as NodeJS.ErrnoException
369 );
370 }
371 if (!Configuration.configurationFileWatcher) {
372 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
373 }
374 }
375 return Configuration.configuration;
376 }
377
378 private static getConfigurationFileWatcher(): fs.FSWatcher {
379 try {
380 return fs.watch(Configuration.configurationFile, (event, filename): void => {
381 if (filename && event === 'change') {
382 // Nullify to force configuration file reading
383 Configuration.configuration = null;
384 if (!Configuration.isUndefined(Configuration.configurationChangeCallback)) {
385 Configuration.configurationChangeCallback().catch((error) => {
386 throw typeof error === 'string' ? new Error(error) : error;
387 });
388 }
389 }
390 });
391 } catch (error) {
392 Configuration.handleFileException(
393 Configuration.logPrefix(),
394 FileType.Configuration,
395 Configuration.configurationFile,
396 error as NodeJS.ErrnoException
397 );
398 }
399 }
400
401 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
402 const SQLiteFileName = `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
403 switch (storageType) {
404 case StorageType.JSON_FILE:
405 return `file://${path.join(
406 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
407 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
408 )}`;
409 case StorageType.SQLITE:
410 return `file://${path.join(
411 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
412 SQLiteFileName
413 )}`;
414 default:
415 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
416 }
417 }
418
419 private static objectHasOwnProperty(object: unknown, property: string): boolean {
420 return Object.prototype.hasOwnProperty.call(object, property) as boolean;
421 }
422
423 private static isUndefined(obj: unknown): boolean {
424 return typeof obj === 'undefined';
425 }
426
427 private static handleFileException(
428 logPrefix: string,
429 fileType: FileType,
430 filePath: string,
431 error: NodeJS.ErrnoException,
432 params: HandleErrorParams<EmptyObject> = { throwError: true }
433 ): void {
434 const prefix = logPrefix.length !== 0 ? logPrefix + ' ' : '';
435 if (error.code === 'ENOENT') {
436 console.error(
437 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' not found: '),
438 error
439 );
440 } else if (error.code === 'EEXIST') {
441 console.error(
442 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' already exists: '),
443 error
444 );
445 } else if (error.code === 'EACCES') {
446 console.error(
447 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' access denied: '),
448 error
449 );
450 } else {
451 console.error(
452 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' error: '),
453 error
454 );
455 }
456 if (params?.throwError) {
457 throw error;
458 }
459 }
460 }