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