Prepare the code for ESM support
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
1 import ConfigurationData, {
2 ServerOptions,
3 StationTemplateUrl,
4 StorageConfiguration,
5 SupervisionUrlDistribution,
6 UIServerConfiguration,
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 type { WorkerChoiceStrategy } from 'poolifier';
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 getWorkerProcess(): WorkerProcessType {
165 Configuration.warnDeprecatedConfigurationKey(
166 'useWorkerPool',
167 null,
168 "Use 'workerProcess' to define the type of worker process model to use instead"
169 );
170 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerProcess')
171 ? Configuration.getConfig().workerProcess
172 : WorkerProcessType.WORKER_SET;
173 }
174
175 static getWorkerStartDelay(): number {
176 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerStartDelay')
177 ? Configuration.getConfig().workerStartDelay
178 : WorkerConstants.DEFAULT_WORKER_START_DELAY;
179 }
180
181 static getElementStartDelay(): number {
182 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'elementStartDelay')
183 ? Configuration.getConfig().elementStartDelay
184 : WorkerConstants.DEFAULT_ELEMENT_START_DELAY;
185 }
186
187 static getWorkerPoolMinSize(): number {
188 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerPoolMinSize')
189 ? Configuration.getConfig().workerPoolMinSize
190 : WorkerConstants.DEFAULT_POOL_MIN_SIZE;
191 }
192
193 static getWorkerPoolMaxSize(): number {
194 Configuration.warnDeprecatedConfigurationKey(
195 'workerPoolSize;',
196 null,
197 "Use 'workerPoolMaxSize' instead"
198 );
199 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'workerPoolMaxSize')
200 ? Configuration.getConfig().workerPoolMaxSize
201 : WorkerConstants.DEFAULT_POOL_MAX_SIZE;
202 }
203
204 static getWorkerPoolStrategy(): WorkerChoiceStrategy {
205 return Configuration.getConfig().workerPoolStrategy;
206 }
207
208 static getChargingStationsPerWorker(): number {
209 return Configuration.objectHasOwnProperty(
210 Configuration.getConfig(),
211 'chargingStationsPerWorker'
212 )
213 ? Configuration.getConfig().chargingStationsPerWorker
214 : WorkerConstants.DEFAULT_ELEMENTS_PER_WORKER;
215 }
216
217 static getLogConsole(): boolean {
218 Configuration.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
219 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logConsole')
220 ? Configuration.getConfig().logConsole
221 : false;
222 }
223
224 static getLogFormat(): string {
225 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFormat')
226 ? Configuration.getConfig().logFormat
227 : 'simple';
228 }
229
230 static getLogRotate(): boolean {
231 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logRotate')
232 ? Configuration.getConfig().logRotate
233 : true;
234 }
235
236 static getLogMaxFiles(): number {
237 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logMaxFiles')
238 ? Configuration.getConfig().logMaxFiles
239 : 7;
240 }
241
242 static getLogLevel(): string {
243 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logLevel')
244 ? Configuration.getConfig().logLevel.toLowerCase()
245 : 'info';
246 }
247
248 static getLogFile(): string {
249 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logFile')
250 ? Configuration.getConfig().logFile
251 : 'combined.log';
252 }
253
254 static getLogErrorFile(): string {
255 Configuration.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
256 return Configuration.objectHasOwnProperty(Configuration.getConfig(), 'logErrorFile')
257 ? Configuration.getConfig().logErrorFile
258 : 'error.log';
259 }
260
261 static getSupervisionUrls(): string | string[] {
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[]);
271 // Read conf
272 return Configuration.getConfig().supervisionUrls;
273 }
274
275 static getSupervisionUrlDistribution(): SupervisionUrlDistribution {
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;
292 }
293
294 private static logPrefix(): string {
295 return new Date().toLocaleString() + ' Simulator configuration |';
296 }
297
298 private static warnDeprecatedConfigurationKey(
299 key: string,
300 sectionName?: string,
301 logMsgToAppend = ''
302 ) {
303 if (
304 sectionName &&
305 !Configuration.isUndefined(Configuration.getConfig()[sectionName]) &&
306 !Configuration.isUndefined(
307 (Configuration.getConfig()[sectionName] as Record<string, unknown>)[key]
308 )
309 ) {
310 console.error(
311 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
312 logMsgToAppend && '. ' + logMsgToAppend
313 }}`
314 );
315 } else if (!Configuration.isUndefined(Configuration.getConfig()[key])) {
316 console.error(
317 chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
318 logMsgToAppend && '. ' + logMsgToAppend
319 }}`
320 );
321 }
322 }
323
324 // Read the config file
325 private static getConfig(): ConfigurationData {
326 if (!Configuration.configuration) {
327 try {
328 Configuration.configuration = JSON.parse(
329 fs.readFileSync(Configuration.configurationFile, 'utf8')
330 ) as ConfigurationData;
331 } catch (error) {
332 Configuration.handleFileException(
333 Configuration.logPrefix(),
334 FileType.Configuration,
335 Configuration.configurationFile,
336 error as NodeJS.ErrnoException
337 );
338 }
339 if (!Configuration.configurationFileWatcher) {
340 Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
341 }
342 }
343 return Configuration.configuration;
344 }
345
346 private static getConfigurationFileWatcher(): fs.FSWatcher {
347 try {
348 return fs.watch(Configuration.configurationFile, (event, filename): void => {
349 if (filename && event === 'change') {
350 // Nullify to force configuration file reading
351 Configuration.configuration = null;
352 if (!Configuration.isUndefined(Configuration.configurationChangeCallback)) {
353 Configuration.configurationChangeCallback().catch((error) => {
354 throw typeof error === 'string' ? new Error(error) : error;
355 });
356 }
357 }
358 });
359 } catch (error) {
360 Configuration.handleFileException(
361 Configuration.logPrefix(),
362 FileType.Configuration,
363 Configuration.configurationFile,
364 error as NodeJS.ErrnoException
365 );
366 }
367 }
368
369 private static getDefaultPerformanceStorageUri(storageType: StorageType) {
370 const SQLiteFileName = `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
371 switch (storageType) {
372 case StorageType.JSON_FILE:
373 return `file://${path.join(
374 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
375 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
376 )}`;
377 case StorageType.SQLITE:
378 return `file://${path.join(
379 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
380 SQLiteFileName
381 )}`;
382 default:
383 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
384 }
385 }
386
387 private static objectHasOwnProperty(object: unknown, property: string): boolean {
388 return Object.prototype.hasOwnProperty.call(object, property) as boolean;
389 }
390
391 private static isUndefined(obj: unknown): boolean {
392 return typeof obj === 'undefined';
393 }
394
395 private static handleFileException(
396 logPrefix: string,
397 fileType: FileType,
398 filePath: string,
399 error: NodeJS.ErrnoException,
400 params: HandleErrorParams<EmptyObject> = { throwError: true }
401 ): void {
402 const prefix = logPrefix.length !== 0 ? logPrefix + ' ' : '';
403 if (error.code === 'ENOENT') {
404 console.error(
405 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' not found: '),
406 error
407 );
408 } else if (error.code === 'EEXIST') {
409 console.error(
410 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' already exists: '),
411 error
412 );
413 } else if (error.code === 'EACCES') {
414 console.error(
415 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' access denied: '),
416 error
417 );
418 } else {
419 console.error(
420 chalk.green(prefix) + chalk.red(fileType + ' file ' + filePath + ' error: '),
421 error
422 );
423 }
424 if (params?.throwError) {
425 throw error;
426 }
427 }
428 }