1 import fs from
'node:fs';
2 import path from
'node:path';
3 import { fileURLToPath
} from
'node:url';
5 import chalk from
'chalk';
6 import merge from
'just-merge';
7 import { WorkerChoiceStrategies
} from
'poolifier';
9 import Constants from
'./Constants';
11 type ConfigurationData
,
12 type StationTemplateUrl
,
13 type StorageConfiguration
,
14 SupervisionUrlDistribution
,
15 type UIServerConfiguration
,
16 type WorkerConfiguration
,
17 } from
'../types/ConfigurationData';
18 import type { EmptyObject
} from
'../types/EmptyObject';
19 import type { HandleErrorParams
} from
'../types/Error';
20 import { FileType
} from
'../types/FileType';
21 import { StorageType
} from
'../types/Storage';
22 import { ApplicationProtocol
} from
'../types/UIProtocol';
23 import { WorkerProcessType
} from
'../types/Worker';
24 import WorkerConstants from
'../worker/WorkerConstants';
26 export default class Configuration
{
27 private static configurationFile
= path
.join(
28 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
33 private static configurationFileWatcher
: fs
.FSWatcher
| undefined;
34 private static configuration
: ConfigurationData
| null = null;
35 private static configurationChangeCallback
: () => Promise
<void>;
37 private constructor() {
38 // This is intentional
41 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
42 Configuration
.configurationChangeCallback
= cb
;
45 static getLogStatisticsInterval(): number | undefined {
46 Configuration
.warnDeprecatedConfigurationKey(
47 'statisticsDisplayInterval',
49 "Use 'logStatisticsInterval' instead"
52 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
53 ? Configuration
.getConfig()?.logStatisticsInterval
54 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
57 static getUIServer(): UIServerConfiguration
{
58 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
60 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
63 let uiServerConfiguration
: UIServerConfiguration
= {
65 type: ApplicationProtocol
.WS
,
67 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
68 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
71 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
72 uiServerConfiguration
= merge
<UIServerConfiguration
>(
73 uiServerConfiguration
,
74 Configuration
.getConfig()?.uiServer
77 if (Configuration
.isCFEnvironment() === true) {
78 delete uiServerConfiguration
.options
?.host
;
79 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
81 return uiServerConfiguration
;
84 static getPerformanceStorage(): StorageConfiguration
{
85 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
86 let storageConfiguration
: StorageConfiguration
= {
88 type: StorageType
.JSON_FILE
,
89 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
91 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
92 storageConfiguration
= {
93 ...storageConfiguration
,
94 ...Configuration
.getConfig()?.performanceStorage
,
97 return storageConfiguration
;
100 static getAutoReconnectMaxRetries(): number | undefined {
101 Configuration
.warnDeprecatedConfigurationKey(
102 'autoReconnectTimeout',
104 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
106 Configuration
.warnDeprecatedConfigurationKey(
109 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
111 Configuration
.warnDeprecatedConfigurationKey(
112 'autoReconnectMaxRetries',
114 'Use it in charging station template instead'
117 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
118 return Configuration
.getConfig()?.autoReconnectMaxRetries
;
122 static getStationTemplateUrls(): StationTemplateUrl
[] | undefined {
123 Configuration
.warnDeprecatedConfigurationKey(
124 'stationTemplateURLs',
126 "Use 'stationTemplateUrls' instead"
128 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
129 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
130 'stationTemplateURLs'
131 ] as unknown
as StationTemplateUrl
[]);
132 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
133 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
135 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
137 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
142 return Configuration
.getConfig()?.stationTemplateUrls
;
145 static getWorker(): WorkerConfiguration
{
146 Configuration
.warnDeprecatedConfigurationKey(
149 "Use 'worker' section to define the type of worker process model instead"
151 Configuration
.warnDeprecatedConfigurationKey(
154 "Use 'worker' section to define the type of worker process model instead"
156 Configuration
.warnDeprecatedConfigurationKey(
159 "Use 'worker' section to define the worker start delay instead"
161 Configuration
.warnDeprecatedConfigurationKey(
162 'chargingStationsPerWorker',
164 "Use 'worker' section to define the number of element(s) per worker instead"
166 Configuration
.warnDeprecatedConfigurationKey(
169 "Use 'worker' section to define the worker's element start delay instead"
171 Configuration
.warnDeprecatedConfigurationKey(
174 "Use 'worker' section to define the worker pool minimum size instead"
176 Configuration
.warnDeprecatedConfigurationKey(
179 "Use 'worker' section to define the worker pool maximum size instead"
181 Configuration
.warnDeprecatedConfigurationKey(
182 'workerPoolMaxSize;',
184 "Use 'worker' section to define the worker pool maximum size instead"
186 Configuration
.warnDeprecatedConfigurationKey(
187 'workerPoolStrategy;',
189 "Use 'worker' section to define the worker pool strategy instead"
191 let workerConfiguration
: WorkerConfiguration
= {
192 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
193 ? Configuration
.getConfig()?.workerProcess
194 : WorkerProcessType
.WORKER_SET
,
195 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
196 ? Configuration
.getConfig()?.workerStartDelay
197 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
198 elementsPerWorker
: Configuration
.objectHasOwnProperty(
199 Configuration
.getConfig(),
200 'chargingStationsPerWorker'
202 ? Configuration
.getConfig()?.chargingStationsPerWorker
203 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
204 elementStartDelay
: Configuration
.objectHasOwnProperty(
205 Configuration
.getConfig(),
208 ? Configuration
.getConfig()?.elementStartDelay
209 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
210 poolMinSize
: Configuration
.objectHasOwnProperty(
211 Configuration
.getConfig(),
214 ? Configuration
.getConfig()?.workerPoolMinSize
215 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
216 poolMaxSize
: Configuration
.objectHasOwnProperty(
217 Configuration
.getConfig(),
220 ? Configuration
.getConfig()?.workerPoolMaxSize
221 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
223 Configuration
.getConfig()?.workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
225 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
226 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig()?.worker
};
228 return workerConfiguration
;
231 static getLogConsole(): boolean | undefined {
232 Configuration
.warnDeprecatedConfigurationKey(
235 "Use 'logConsole' instead"
237 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
238 ? Configuration
.getConfig()?.logConsole
242 static getLogFormat(): string | undefined {
243 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
244 ? Configuration
.getConfig()?.logFormat
248 static getLogRotate(): boolean | undefined {
249 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
250 ? Configuration
.getConfig()?.logRotate
254 static getLogMaxFiles(): number | string | false | undefined {
256 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
257 Configuration
.getConfig()?.logMaxFiles
261 static getLogMaxSize(): number | string | false | undefined {
263 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
264 Configuration
.getConfig()?.logMaxSize
268 static getLogLevel(): string | undefined {
269 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
270 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
274 static getLogFile(): string | undefined {
275 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
276 ? Configuration
.getConfig()?.logFile
280 static getLogErrorFile(): string | undefined {
281 Configuration
.warnDeprecatedConfigurationKey(
284 "Use 'logErrorFile' instead"
286 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
287 ? Configuration
.getConfig()?.logErrorFile
291 static getSupervisionUrls(): string | string[] | undefined {
292 Configuration
.warnDeprecatedConfigurationKey(
295 "Use 'supervisionUrls' instead"
297 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
298 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
302 return Configuration
.getConfig()?.supervisionUrls
;
305 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
306 Configuration
.warnDeprecatedConfigurationKey(
307 'distributeStationToTenantEqually',
309 "Use 'supervisionUrlDistribution' instead"
311 Configuration
.warnDeprecatedConfigurationKey(
312 'distributeStationsToTenantsEqually',
314 "Use 'supervisionUrlDistribution' instead"
316 return Configuration
.objectHasOwnProperty(
317 Configuration
.getConfig(),
318 'supervisionUrlDistribution'
320 ? Configuration
.getConfig()?.supervisionUrlDistribution
321 : SupervisionUrlDistribution
.ROUND_ROBIN
;
324 private static logPrefix
= (): string => {
325 return `${new Date().toLocaleString()} Simulator configuration |`;
328 private static warnDeprecatedConfigurationKey(
330 sectionName
?: string,
335 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
336 !Configuration
.isUndefined(
337 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
341 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
342 logMsgToAppend.trim().length !== 0 && `. ${logMsgToAppend}
`
345 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
347 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
348 logMsgToAppend.trim().length !== 0 && `. ${logMsgToAppend}
`
354 // Read the config file
355 private static getConfig(): ConfigurationData
| null {
356 if (!Configuration
.configuration
) {
358 Configuration
.configuration
= JSON
.parse(
359 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
360 ) as ConfigurationData
;
362 Configuration
.handleFileException(
363 Configuration
.configurationFile
,
364 FileType
.Configuration
,
365 error
as NodeJS
.ErrnoException
,
366 Configuration
.logPrefix()
369 if (!Configuration
.configurationFileWatcher
) {
370 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
373 return Configuration
.configuration
;
376 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
378 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
379 if (filename
?.trim().length
!== 0 && event
=== 'change') {
380 // Nullify to force configuration file reading
381 Configuration
.configuration
= null;
382 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
383 Configuration
.configurationChangeCallback().catch((error
) => {
384 throw typeof error
=== 'string' ? new Error(error
) : error
;
390 Configuration
.handleFileException(
391 Configuration
.configurationFile
,
392 FileType
.Configuration
,
393 error
as NodeJS
.ErrnoException
,
394 Configuration
.logPrefix()
399 private static isCFEnvironment(): boolean {
400 return process
.env
.VCAP_APPLICATION
!== undefined;
403 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
404 switch (storageType
) {
405 case StorageType
.JSON_FILE
:
406 return `file://${path.join(
407 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
408 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
410 case StorageType
.SQLITE
:
411 return `file://${path.join(
412 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
413 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
416 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
420 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
421 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
424 private static isUndefined(obj
: unknown
): boolean {
425 return obj
=== undefined;
428 private static handleFileException(
431 error
: NodeJS
.ErrnoException
,
433 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
435 const prefix
= logPrefix
?.trim().length
!== 0 ? `${logPrefix} ` : '';
437 switch (error
.code
) {
439 logMsg
= `${fileType} file ${filePath} not found: `;
442 logMsg
= `${fileType} file ${filePath} already exists: `;
445 logMsg
= `${fileType} file ${filePath} access denied: `;
448 logMsg
= `${fileType} file ${filePath} error: `;
450 console
.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error
);
451 if (params
?.throwError
) {