2 import path from
'path';
3 import { fileURLToPath
} from
'url';
5 import chalk from
'chalk';
8 type ConfigurationData
,
9 type StationTemplateUrl
,
10 type StorageConfiguration
,
11 SupervisionUrlDistribution
,
12 type UIServerConfiguration
,
13 type WorkerConfiguration
,
14 } from
'../types/ConfigurationData';
15 import type { EmptyObject
} from
'../types/EmptyObject';
16 import type { HandleErrorParams
} from
'../types/Error';
17 import { FileType
} from
'../types/FileType';
18 import { StorageType
} from
'../types/Storage';
19 import { ApplicationProtocol
} from
'../types/UIProtocol';
20 import { WorkerProcessType
} from
'../types/Worker';
21 import WorkerConstants from
'../worker/WorkerConstants';
22 import Constants from
'./Constants';
24 export default class Configuration
{
25 private static configurationFile
= path
.join(
26 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
31 private static configurationFileWatcher
: fs
.FSWatcher
;
32 private static configuration
: ConfigurationData
| null = null;
33 private static configurationChangeCallback
: () => Promise
<void>;
35 private constructor() {
36 // This is intentional
39 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
40 Configuration
.configurationChangeCallback
= cb
;
43 static getLogStatisticsInterval(): number {
44 Configuration
.warnDeprecatedConfigurationKey(
45 'statisticsDisplayInterval',
47 "Use 'logStatisticsInterval' instead"
50 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
51 ? Configuration
.getConfig().logStatisticsInterval
52 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
55 static getUIServer(): UIServerConfiguration
{
56 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
58 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
61 let uiServerConfiguration
: UIServerConfiguration
= {
63 type: ApplicationProtocol
.WS
,
65 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
66 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
69 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
70 uiServerConfiguration
= Configuration
.deepMerge(
71 uiServerConfiguration
,
72 Configuration
.getConfig().uiServer
75 return uiServerConfiguration
;
78 static getPerformanceStorage(): StorageConfiguration
{
79 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
80 let storageConfiguration
: StorageConfiguration
= {
82 type: StorageType
.JSON_FILE
,
83 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
85 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
86 storageConfiguration
= {
87 ...storageConfiguration
,
88 ...Configuration
.getConfig().performanceStorage
,
91 return storageConfiguration
;
94 static getAutoReconnectMaxRetries(): number {
95 Configuration
.warnDeprecatedConfigurationKey(
96 'autoReconnectTimeout',
98 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
100 Configuration
.warnDeprecatedConfigurationKey(
103 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
105 Configuration
.warnDeprecatedConfigurationKey(
106 'autoReconnectMaxRetries',
108 'Use it in charging station template instead'
111 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
112 return Configuration
.getConfig().autoReconnectMaxRetries
;
116 static getStationTemplateUrls(): StationTemplateUrl
[] {
117 Configuration
.warnDeprecatedConfigurationKey(
118 'stationTemplateURLs',
120 "Use 'stationTemplateUrls' instead"
122 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
123 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
124 'stationTemplateURLs'
125 ] as StationTemplateUrl
[]);
126 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
127 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
129 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
131 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
136 return Configuration
.getConfig().stationTemplateUrls
;
139 static getWorker(): WorkerConfiguration
{
140 Configuration
.warnDeprecatedConfigurationKey(
143 "Use 'worker' section to define the type of worker process model instead"
145 Configuration
.warnDeprecatedConfigurationKey(
148 "Use 'worker' section to define the type of worker process model instead"
150 Configuration
.warnDeprecatedConfigurationKey(
153 "Use 'worker' section to define the worker start delay instead"
155 Configuration
.warnDeprecatedConfigurationKey(
156 'chargingStationsPerWorker',
158 "Use 'worker' section to define the number of element(s) per worker instead"
160 Configuration
.warnDeprecatedConfigurationKey(
163 "Use 'worker' section to define the worker's element start delay instead"
165 Configuration
.warnDeprecatedConfigurationKey(
168 "Use 'worker' section to define the worker pool minimum size instead"
170 Configuration
.warnDeprecatedConfigurationKey(
173 "Use 'worker' section to define the worker pool maximum size instead"
175 Configuration
.warnDeprecatedConfigurationKey(
176 'workerPoolMaxSize;',
178 "Use 'worker' section to define the worker pool maximum size instead"
180 Configuration
.warnDeprecatedConfigurationKey(
181 'workerPoolStrategy;',
183 "Use 'worker' section to define the worker pool strategy instead"
185 let workerConfiguration
: WorkerConfiguration
= {
186 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
187 ? Configuration
.getConfig().workerProcess
188 : WorkerProcessType
.WORKER_SET
,
189 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
190 ? Configuration
.getConfig().workerStartDelay
191 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
192 elementsPerWorker
: Configuration
.objectHasOwnProperty(
193 Configuration
.getConfig(),
194 'chargingStationsPerWorker'
196 ? Configuration
.getConfig().chargingStationsPerWorker
197 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
198 elementStartDelay
: Configuration
.objectHasOwnProperty(
199 Configuration
.getConfig(),
202 ? Configuration
.getConfig().elementStartDelay
203 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
204 poolMinSize
: Configuration
.objectHasOwnProperty(
205 Configuration
.getConfig(),
208 ? Configuration
.getConfig().workerPoolMinSize
209 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
210 poolMaxSize
: Configuration
.objectHasOwnProperty(
211 Configuration
.getConfig(),
214 ? Configuration
.getConfig().workerPoolMaxSize
215 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
216 poolStrategy
: Configuration
.getConfig().workerPoolStrategy
,
218 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
219 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
221 return workerConfiguration
;
224 static getLogConsole(): boolean {
225 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
226 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
227 ? Configuration
.getConfig().logConsole
231 static getLogFormat(): string {
232 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
233 ? Configuration
.getConfig().logFormat
237 static getLogRotate(): boolean {
238 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
239 ? Configuration
.getConfig().logRotate
243 static getLogMaxFiles(): number {
244 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
245 ? Configuration
.getConfig().logMaxFiles
249 static getLogLevel(): string {
250 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
251 ? Configuration
.getConfig().logLevel
.toLowerCase()
255 static getLogFile(): string {
256 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
257 ? Configuration
.getConfig().logFile
261 static getLogErrorFile(): string {
262 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
263 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
264 ? Configuration
.getConfig().logErrorFile
268 static getSupervisionUrls(): string | string[] {
269 Configuration
.warnDeprecatedConfigurationKey(
272 "Use 'supervisionUrls' instead"
274 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
275 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
279 return Configuration
.getConfig().supervisionUrls
;
282 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
283 Configuration
.warnDeprecatedConfigurationKey(
284 'distributeStationToTenantEqually',
286 "Use 'supervisionUrlDistribution' instead"
288 Configuration
.warnDeprecatedConfigurationKey(
289 'distributeStationsToTenantsEqually',
291 "Use 'supervisionUrlDistribution' instead"
293 return Configuration
.objectHasOwnProperty(
294 Configuration
.getConfig(),
295 'supervisionUrlDistribution'
297 ? Configuration
.getConfig().supervisionUrlDistribution
298 : SupervisionUrlDistribution
.ROUND_ROBIN
;
301 private static logPrefix(): string {
302 return new Date().toLocaleString() + ' Simulator configuration |';
305 private static warnDeprecatedConfigurationKey(
307 sectionName
?: string,
312 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
313 !Configuration
.isUndefined(
314 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
318 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
319 logMsgToAppend && '. ' + logMsgToAppend
322 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
324 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
325 logMsgToAppend && '. ' + logMsgToAppend
331 // Read the config file
332 private static getConfig(): ConfigurationData
{
333 if (!Configuration
.configuration
) {
335 Configuration
.configuration
= JSON
.parse(
336 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
337 ) as ConfigurationData
;
339 Configuration
.handleFileException(
340 Configuration
.logPrefix(),
341 FileType
.Configuration
,
342 Configuration
.configurationFile
,
343 error
as NodeJS
.ErrnoException
346 if (!Configuration
.configurationFileWatcher
) {
347 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
350 return Configuration
.configuration
;
353 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
355 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
356 if (filename
&& event
=== 'change') {
357 // Nullify to force configuration file reading
358 Configuration
.configuration
= null;
359 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
360 Configuration
.configurationChangeCallback().catch((error
) => {
361 throw typeof error
=== 'string' ? new Error(error
) : error
;
367 Configuration
.handleFileException(
368 Configuration
.logPrefix(),
369 FileType
.Configuration
,
370 Configuration
.configurationFile
,
371 error
as NodeJS
.ErrnoException
376 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
377 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
378 switch (storageType
) {
379 case StorageType
.JSON_FILE
:
380 return `file://${path.join(
381 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
382 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
384 case StorageType
.SQLITE
:
385 return `file://${path.join(
386 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
390 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
394 private static isObject(item
): boolean {
395 return item
&& typeof item
=== 'object' && Array.isArray(item
) === false;
398 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
399 if (!sources
.length
) {
402 const source
= sources
.shift();
404 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
405 for (const key
in source
) {
406 if (Configuration
.isObject(source
[key
])) {
408 Object.assign(target
, { [key
]: {} });
410 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
411 Configuration
.deepMerge(target
[key
], source
[key
]);
413 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
414 Object.assign(target
, { [key
]: source
[key
] });
418 return Configuration
.deepMerge(target
, ...sources
);
421 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
422 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
425 private static isUndefined(obj
: unknown
): boolean {
426 return typeof obj
=== 'undefined';
429 private static handleFileException(
433 error
: NodeJS
.ErrnoException
,
434 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
436 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
437 if (error
.code
=== 'ENOENT') {
439 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
442 } else if (error
.code
=== 'EEXIST') {
444 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
447 } else if (error
.code
=== 'EACCES') {
449 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
454 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
458 if (params
?.throwError
) {