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