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 if (Configuration
.isCFEnvironment() === true) {
76 delete uiServerConfiguration
.options
.host
;
77 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
79 return uiServerConfiguration
;
82 static getPerformanceStorage(): StorageConfiguration
{
83 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
84 let storageConfiguration
: StorageConfiguration
= {
86 type: StorageType
.JSON_FILE
,
87 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
89 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
90 storageConfiguration
= {
91 ...storageConfiguration
,
92 ...Configuration
.getConfig().performanceStorage
,
95 return storageConfiguration
;
98 static getAutoReconnectMaxRetries(): number {
99 Configuration
.warnDeprecatedConfigurationKey(
100 'autoReconnectTimeout',
102 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
104 Configuration
.warnDeprecatedConfigurationKey(
107 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
109 Configuration
.warnDeprecatedConfigurationKey(
110 'autoReconnectMaxRetries',
112 'Use it in charging station template instead'
115 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
116 return Configuration
.getConfig().autoReconnectMaxRetries
;
120 static getStationTemplateUrls(): StationTemplateUrl
[] {
121 Configuration
.warnDeprecatedConfigurationKey(
122 'stationTemplateURLs',
124 "Use 'stationTemplateUrls' instead"
126 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
127 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
128 'stationTemplateURLs'
129 ] as StationTemplateUrl
[]);
130 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
131 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
133 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
135 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
140 return Configuration
.getConfig().stationTemplateUrls
;
143 static getWorker(): WorkerConfiguration
{
144 Configuration
.warnDeprecatedConfigurationKey(
147 "Use 'worker' section to define the type of worker process model instead"
149 Configuration
.warnDeprecatedConfigurationKey(
152 "Use 'worker' section to define the type of worker process model instead"
154 Configuration
.warnDeprecatedConfigurationKey(
157 "Use 'worker' section to define the worker start delay instead"
159 Configuration
.warnDeprecatedConfigurationKey(
160 'chargingStationsPerWorker',
162 "Use 'worker' section to define the number of element(s) per worker instead"
164 Configuration
.warnDeprecatedConfigurationKey(
167 "Use 'worker' section to define the worker's element start delay instead"
169 Configuration
.warnDeprecatedConfigurationKey(
172 "Use 'worker' section to define the worker pool minimum size instead"
174 Configuration
.warnDeprecatedConfigurationKey(
177 "Use 'worker' section to define the worker pool maximum size instead"
179 Configuration
.warnDeprecatedConfigurationKey(
180 'workerPoolMaxSize;',
182 "Use 'worker' section to define the worker pool maximum size instead"
184 Configuration
.warnDeprecatedConfigurationKey(
185 'workerPoolStrategy;',
187 "Use 'worker' section to define the worker pool strategy instead"
189 let workerConfiguration
: WorkerConfiguration
= {
190 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
191 ? Configuration
.getConfig().workerProcess
192 : WorkerProcessType
.WORKER_SET
,
193 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
194 ? Configuration
.getConfig().workerStartDelay
195 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
196 elementsPerWorker
: Configuration
.objectHasOwnProperty(
197 Configuration
.getConfig(),
198 'chargingStationsPerWorker'
200 ? Configuration
.getConfig().chargingStationsPerWorker
201 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
202 elementStartDelay
: Configuration
.objectHasOwnProperty(
203 Configuration
.getConfig(),
206 ? Configuration
.getConfig().elementStartDelay
207 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
208 poolMinSize
: Configuration
.objectHasOwnProperty(
209 Configuration
.getConfig(),
212 ? Configuration
.getConfig().workerPoolMinSize
213 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
214 poolMaxSize
: Configuration
.objectHasOwnProperty(
215 Configuration
.getConfig(),
218 ? Configuration
.getConfig().workerPoolMaxSize
219 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
220 poolStrategy
: Configuration
.getConfig().workerPoolStrategy
,
222 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
223 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
225 return workerConfiguration
;
228 static getLogConsole(): boolean {
229 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
230 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
231 ? Configuration
.getConfig().logConsole
235 static getLogFormat(): string {
236 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
237 ? Configuration
.getConfig().logFormat
241 static getLogRotate(): boolean {
242 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
243 ? Configuration
.getConfig().logRotate
247 static getLogMaxFiles(): number {
248 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
249 ? Configuration
.getConfig().logMaxFiles
253 static getLogLevel(): string {
254 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
255 ? Configuration
.getConfig().logLevel
.toLowerCase()
259 static getLogFile(): string {
260 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
261 ? Configuration
.getConfig().logFile
265 static getLogErrorFile(): string {
266 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
267 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
268 ? Configuration
.getConfig().logErrorFile
272 static getSupervisionUrls(): string | string[] {
273 Configuration
.warnDeprecatedConfigurationKey(
276 "Use 'supervisionUrls' instead"
278 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
279 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
283 return Configuration
.getConfig().supervisionUrls
;
286 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
287 Configuration
.warnDeprecatedConfigurationKey(
288 'distributeStationToTenantEqually',
290 "Use 'supervisionUrlDistribution' instead"
292 Configuration
.warnDeprecatedConfigurationKey(
293 'distributeStationsToTenantsEqually',
295 "Use 'supervisionUrlDistribution' instead"
297 return Configuration
.objectHasOwnProperty(
298 Configuration
.getConfig(),
299 'supervisionUrlDistribution'
301 ? Configuration
.getConfig().supervisionUrlDistribution
302 : SupervisionUrlDistribution
.ROUND_ROBIN
;
305 private static logPrefix(): string {
306 return new Date().toLocaleString() + ' Simulator configuration |';
309 private static warnDeprecatedConfigurationKey(
311 sectionName
?: string,
316 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
317 !Configuration
.isUndefined(
318 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
322 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
323 logMsgToAppend && '. ' + logMsgToAppend
326 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
328 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
329 logMsgToAppend && '. ' + logMsgToAppend
335 // Read the config file
336 private static getConfig(): ConfigurationData
{
337 if (!Configuration
.configuration
) {
339 Configuration
.configuration
= JSON
.parse(
340 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
341 ) as ConfigurationData
;
343 Configuration
.handleFileException(
344 Configuration
.logPrefix(),
345 FileType
.Configuration
,
346 Configuration
.configurationFile
,
347 error
as NodeJS
.ErrnoException
350 if (!Configuration
.configurationFileWatcher
) {
351 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
354 return Configuration
.configuration
;
357 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
359 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
360 if (filename
&& event
=== 'change') {
361 // Nullify to force configuration file reading
362 Configuration
.configuration
= null;
363 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
364 Configuration
.configurationChangeCallback().catch((error
) => {
365 throw typeof error
=== 'string' ? new Error(error
) : error
;
371 Configuration
.handleFileException(
372 Configuration
.logPrefix(),
373 FileType
.Configuration
,
374 Configuration
.configurationFile
,
375 error
as NodeJS
.ErrnoException
380 private static isCFEnvironment(): boolean {
381 return process
.env
.VCAP_APPLICATION
!== undefined;
384 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
385 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
386 switch (storageType
) {
387 case StorageType
.JSON_FILE
:
388 return `file://${path.join(
389 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
390 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
392 case StorageType
.SQLITE
:
393 return `file://${path.join(
394 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
398 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
402 private static isObject(item
): boolean {
403 return item
&& typeof item
=== 'object' && Array.isArray(item
) === false;
406 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
407 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
410 private static isUndefined(obj
: unknown
): boolean {
411 return typeof obj
=== 'undefined';
414 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
415 if (!sources
.length
) {
418 const source
= sources
.shift();
420 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
421 for (const key
in source
) {
422 if (Configuration
.isObject(source
[key
])) {
424 Object.assign(target
, { [key
]: {} });
426 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
427 Configuration
.deepMerge(target
[key
], source
[key
]);
429 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
430 Object.assign(target
, { [key
]: source
[key
] });
434 return Configuration
.deepMerge(target
, ...sources
);
437 private static handleFileException(
441 error
: NodeJS
.ErrnoException
,
442 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
444 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
445 if (error
.code
=== 'ENOENT') {
447 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
450 } else if (error
.code
=== 'EEXIST') {
452 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
455 } else if (error
.code
=== 'EACCES') {
457 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
462 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
466 if (params
?.throwError
) {