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
.ROUND_ROBIN
,
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 | string | undefined {
251 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
252 Configuration
.getConfig().logMaxFiles
256 static getLogMaxSize(): number | string | undefined {
258 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
259 Configuration
.getConfig().logMaxSize
263 static getLogLevel(): string {
264 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
265 ? Configuration
.getConfig().logLevel
.toLowerCase()
269 static getLogFile(): string {
270 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
271 ? Configuration
.getConfig().logFile
275 static getLogErrorFile(): string {
276 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
277 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
278 ? Configuration
.getConfig().logErrorFile
282 static getSupervisionUrls(): string | string[] {
283 Configuration
.warnDeprecatedConfigurationKey(
286 "Use 'supervisionUrls' instead"
288 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
289 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
293 return Configuration
.getConfig().supervisionUrls
;
296 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
297 Configuration
.warnDeprecatedConfigurationKey(
298 'distributeStationToTenantEqually',
300 "Use 'supervisionUrlDistribution' instead"
302 Configuration
.warnDeprecatedConfigurationKey(
303 'distributeStationsToTenantsEqually',
305 "Use 'supervisionUrlDistribution' instead"
307 return Configuration
.objectHasOwnProperty(
308 Configuration
.getConfig(),
309 'supervisionUrlDistribution'
311 ? Configuration
.getConfig().supervisionUrlDistribution
312 : SupervisionUrlDistribution
.ROUND_ROBIN
;
315 private static logPrefix(): string {
316 return new Date().toLocaleString() + ' Simulator configuration |';
319 private static warnDeprecatedConfigurationKey(
321 sectionName
?: string,
326 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
327 !Configuration
.isUndefined(
328 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
332 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
333 logMsgToAppend && '. ' + logMsgToAppend
336 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
338 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
339 logMsgToAppend && '. ' + logMsgToAppend
345 // Read the config file
346 private static getConfig(): ConfigurationData
{
347 if (!Configuration
.configuration
) {
349 Configuration
.configuration
= JSON
.parse(
350 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
351 ) as ConfigurationData
;
353 Configuration
.handleFileException(
354 Configuration
.logPrefix(),
355 FileType
.Configuration
,
356 Configuration
.configurationFile
,
357 error
as NodeJS
.ErrnoException
360 if (!Configuration
.configurationFileWatcher
) {
361 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
364 return Configuration
.configuration
;
367 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
369 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
370 if (filename
&& event
=== 'change') {
371 // Nullify to force configuration file reading
372 Configuration
.configuration
= null;
373 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
374 Configuration
.configurationChangeCallback().catch((error
) => {
375 throw typeof error
=== 'string' ? new Error(error
) : error
;
381 Configuration
.handleFileException(
382 Configuration
.logPrefix(),
383 FileType
.Configuration
,
384 Configuration
.configurationFile
,
385 error
as NodeJS
.ErrnoException
390 private static isCFEnvironment(): boolean {
391 return process
.env
.VCAP_APPLICATION
!== undefined;
394 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
395 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
396 switch (storageType
) {
397 case StorageType
.JSON_FILE
:
398 return `file://${path.join(
399 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
400 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
402 case StorageType
.SQLITE
:
403 return `file://${path.join(
404 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
408 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
412 private static isObject(item
: unknown
): boolean {
413 return item
&& typeof item
=== 'object' && Array.isArray(item
) === false;
416 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
417 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
420 private static isUndefined(obj
: unknown
): boolean {
421 return typeof obj
=== 'undefined';
424 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
425 if (!sources
.length
) {
428 const source
= sources
.shift();
430 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
431 for (const key
in source
) {
432 if (Configuration
.isObject(source
[key
])) {
434 Object.assign(target
, { [key
]: {} });
436 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
437 Configuration
.deepMerge(target
[key
], source
[key
]);
439 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
440 Object.assign(target
, { [key
]: source
[key
] });
444 return Configuration
.deepMerge(target
, ...sources
);
447 private static handleFileException(
451 error
: NodeJS
.ErrnoException
,
452 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
454 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
455 if (error
.code
=== 'ENOENT') {
457 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
460 } else if (error
.code
=== 'EEXIST') {
462 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
465 } else if (error
.code
=== 'EACCES') {
467 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
472 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
476 if (params
?.throwError
) {