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