2 import path from
'path';
3 import { fileURLToPath
} from
'url';
5 import chalk from
'chalk';
6 import { WorkerChoiceStrategies
} from
'poolifier';
9 type ConfigurationData
,
10 type StationTemplateUrl
,
11 type StorageConfiguration
,
12 SupervisionUrlDistribution
,
13 type UIServerConfiguration
,
14 type WorkerConfiguration
,
15 } from
'../types/ConfigurationData';
16 import type { EmptyObject
} from
'../types/EmptyObject';
17 import type { HandleErrorParams
} from
'../types/Error';
18 import { FileType
} from
'../types/FileType';
19 import { StorageType
} from
'../types/Storage';
20 import { ApplicationProtocol
} from
'../types/UIProtocol';
21 import { WorkerProcessType
} from
'../types/Worker';
22 import WorkerConstants from
'../worker/WorkerConstants';
23 import Constants from
'./Constants';
25 export default class Configuration
{
26 private static configurationFile
= path
.join(
27 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
32 private static configurationFileWatcher
: fs
.FSWatcher
;
33 private static configuration
: ConfigurationData
| null = null;
34 private static configurationChangeCallback
: () => Promise
<void>;
36 private constructor() {
37 // This is intentional
40 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
41 Configuration
.configurationChangeCallback
= cb
;
44 static getLogStatisticsInterval(): number {
45 Configuration
.warnDeprecatedConfigurationKey(
46 'statisticsDisplayInterval',
48 "Use 'logStatisticsInterval' instead"
51 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
52 ? Configuration
.getConfig().logStatisticsInterval
53 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
56 static getUIServer(): UIServerConfiguration
{
57 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
59 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
62 let uiServerConfiguration
: UIServerConfiguration
= {
64 type: ApplicationProtocol
.WS
,
66 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
67 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
70 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
71 uiServerConfiguration
= Configuration
.deepMerge(
72 uiServerConfiguration
,
73 Configuration
.getConfig().uiServer
76 if (Configuration
.isCFEnvironment() === true) {
77 delete uiServerConfiguration
.options
.host
;
78 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
80 return uiServerConfiguration
;
83 static getPerformanceStorage(): StorageConfiguration
{
84 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
85 let storageConfiguration
: StorageConfiguration
= {
87 type: StorageType
.JSON_FILE
,
88 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
90 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
91 storageConfiguration
= {
92 ...storageConfiguration
,
93 ...Configuration
.getConfig().performanceStorage
,
96 return storageConfiguration
;
99 static getAutoReconnectMaxRetries(): number {
100 Configuration
.warnDeprecatedConfigurationKey(
101 'autoReconnectTimeout',
103 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
105 Configuration
.warnDeprecatedConfigurationKey(
108 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
110 Configuration
.warnDeprecatedConfigurationKey(
111 'autoReconnectMaxRetries',
113 'Use it in charging station template instead'
116 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
117 return Configuration
.getConfig().autoReconnectMaxRetries
;
121 static getStationTemplateUrls(): StationTemplateUrl
[] {
122 Configuration
.warnDeprecatedConfigurationKey(
123 'stationTemplateURLs',
125 "Use 'stationTemplateUrls' instead"
127 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
128 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
129 'stationTemplateURLs'
130 ] as StationTemplateUrl
[]);
131 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
132 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
134 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
136 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
141 return Configuration
.getConfig().stationTemplateUrls
;
144 static getWorker(): WorkerConfiguration
{
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 type of worker process model instead"
155 Configuration
.warnDeprecatedConfigurationKey(
158 "Use 'worker' section to define the worker start delay instead"
160 Configuration
.warnDeprecatedConfigurationKey(
161 'chargingStationsPerWorker',
163 "Use 'worker' section to define the number of element(s) per worker instead"
165 Configuration
.warnDeprecatedConfigurationKey(
168 "Use 'worker' section to define the worker's element start delay instead"
170 Configuration
.warnDeprecatedConfigurationKey(
173 "Use 'worker' section to define the worker pool minimum size instead"
175 Configuration
.warnDeprecatedConfigurationKey(
178 "Use 'worker' section to define the worker pool maximum size instead"
180 Configuration
.warnDeprecatedConfigurationKey(
181 'workerPoolMaxSize;',
183 "Use 'worker' section to define the worker pool maximum size instead"
185 Configuration
.warnDeprecatedConfigurationKey(
186 'workerPoolStrategy;',
188 "Use 'worker' section to define the worker pool strategy instead"
190 let workerConfiguration
: WorkerConfiguration
= {
191 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
192 ? Configuration
.getConfig().workerProcess
193 : WorkerProcessType
.WORKER_SET
,
194 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
195 ? Configuration
.getConfig().workerStartDelay
196 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
197 elementsPerWorker
: Configuration
.objectHasOwnProperty(
198 Configuration
.getConfig(),
199 'chargingStationsPerWorker'
201 ? Configuration
.getConfig().chargingStationsPerWorker
202 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
203 elementStartDelay
: Configuration
.objectHasOwnProperty(
204 Configuration
.getConfig(),
207 ? Configuration
.getConfig().elementStartDelay
208 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
209 poolMinSize
: Configuration
.objectHasOwnProperty(
210 Configuration
.getConfig(),
213 ? Configuration
.getConfig().workerPoolMinSize
214 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
215 poolMaxSize
: Configuration
.objectHasOwnProperty(
216 Configuration
.getConfig(),
219 ? Configuration
.getConfig().workerPoolMaxSize
220 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
222 Configuration
.getConfig().workerPoolStrategy
?? WorkerChoiceStrategies
.FAIR_SHARE
,
224 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
225 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
227 return workerConfiguration
;
230 static getLogConsole(): boolean {
231 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
232 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
233 ? Configuration
.getConfig().logConsole
237 static getLogFormat(): string {
238 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
239 ? Configuration
.getConfig().logFormat
243 static getLogRotate(): boolean {
244 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
245 ? Configuration
.getConfig().logRotate
249 static getLogMaxFiles(): number {
250 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
251 ? Configuration
.getConfig().logMaxFiles
255 static getLogLevel(): string {
256 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
257 ? Configuration
.getConfig().logLevel
.toLowerCase()
261 static getLogFile(): string {
262 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
263 ? Configuration
.getConfig().logFile
267 static getLogErrorFile(): string {
268 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
269 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
270 ? Configuration
.getConfig().logErrorFile
274 static getSupervisionUrls(): string | string[] {
275 Configuration
.warnDeprecatedConfigurationKey(
278 "Use 'supervisionUrls' instead"
280 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
281 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
285 return Configuration
.getConfig().supervisionUrls
;
288 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
289 Configuration
.warnDeprecatedConfigurationKey(
290 'distributeStationToTenantEqually',
292 "Use 'supervisionUrlDistribution' instead"
294 Configuration
.warnDeprecatedConfigurationKey(
295 'distributeStationsToTenantsEqually',
297 "Use 'supervisionUrlDistribution' instead"
299 return Configuration
.objectHasOwnProperty(
300 Configuration
.getConfig(),
301 'supervisionUrlDistribution'
303 ? Configuration
.getConfig().supervisionUrlDistribution
304 : SupervisionUrlDistribution
.ROUND_ROBIN
;
307 private static logPrefix(): string {
308 return new Date().toLocaleString() + ' Simulator configuration |';
311 private static warnDeprecatedConfigurationKey(
313 sectionName
?: string,
318 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
319 !Configuration
.isUndefined(
320 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
324 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
325 logMsgToAppend && '. ' + logMsgToAppend
328 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
330 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
331 logMsgToAppend && '. ' + logMsgToAppend
337 // Read the config file
338 private static getConfig(): ConfigurationData
{
339 if (!Configuration
.configuration
) {
341 Configuration
.configuration
= JSON
.parse(
342 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
343 ) as ConfigurationData
;
345 Configuration
.handleFileException(
346 Configuration
.logPrefix(),
347 FileType
.Configuration
,
348 Configuration
.configurationFile
,
349 error
as NodeJS
.ErrnoException
352 if (!Configuration
.configurationFileWatcher
) {
353 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
356 return Configuration
.configuration
;
359 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
361 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
362 if (filename
&& event
=== 'change') {
363 // Nullify to force configuration file reading
364 Configuration
.configuration
= null;
365 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
366 Configuration
.configurationChangeCallback().catch((error
) => {
367 throw typeof error
=== 'string' ? new Error(error
) : error
;
373 Configuration
.handleFileException(
374 Configuration
.logPrefix(),
375 FileType
.Configuration
,
376 Configuration
.configurationFile
,
377 error
as NodeJS
.ErrnoException
382 private static isCFEnvironment(): boolean {
383 return process
.env
.VCAP_APPLICATION
!== undefined;
386 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
387 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
388 switch (storageType
) {
389 case StorageType
.JSON_FILE
:
390 return `file://${path.join(
391 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
392 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
394 case StorageType
.SQLITE
:
395 return `file://${path.join(
396 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
400 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
404 private static isObject(item
): boolean {
405 return item
&& typeof item
=== 'object' && Array.isArray(item
) === false;
408 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
409 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
412 private static isUndefined(obj
: unknown
): boolean {
413 return typeof obj
=== 'undefined';
416 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
417 if (!sources
.length
) {
420 const source
= sources
.shift();
422 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
423 for (const key
in source
) {
424 if (Configuration
.isObject(source
[key
])) {
426 Object.assign(target
, { [key
]: {} });
428 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
429 Configuration
.deepMerge(target
[key
], source
[key
]);
431 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
432 Object.assign(target
, { [key
]: source
[key
] });
436 return Configuration
.deepMerge(target
, ...sources
);
439 private static handleFileException(
443 error
: NodeJS
.ErrnoException
,
444 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
446 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
447 if (error
.code
=== 'ENOENT') {
449 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
452 } else if (error
.code
=== 'EEXIST') {
454 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
457 } else if (error
.code
=== 'EACCES') {
459 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
464 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
468 if (params
?.throwError
) {