1 import ConfigurationData
, {
4 SupervisionUrlDistribution
,
7 } from
'../types/ConfigurationData';
9 import Constants from
'./Constants';
10 import { EmptyObject
} from
'../types/EmptyObject';
11 import { FileType
} from
'../types/FileType';
12 import { HandleErrorParams
} from
'../types/Error';
13 import { StorageType
} from
'../types/Storage';
14 import WorkerConstants from
'../worker/WorkerConstants';
15 import { WorkerProcessType
} from
'../types/Worker';
16 import chalk from
'chalk';
17 import { fileURLToPath
} from
'url';
19 import path from
'path';
21 export default class Configuration
{
22 private static configurationFile
= path
.join(
23 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
28 private static configurationFileWatcher
: fs
.FSWatcher
;
29 private static configuration
: ConfigurationData
| null = null;
30 private static configurationChangeCallback
: () => Promise
<void>;
32 private constructor() {
33 // This is intentional
36 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
37 Configuration
.configurationChangeCallback
= cb
;
40 static getLogStatisticsInterval(): number {
41 Configuration
.warnDeprecatedConfigurationKey(
42 'statisticsDisplayInterval',
44 "Use 'logStatisticsInterval' instead"
47 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
48 ? Configuration
.getConfig().logStatisticsInterval
49 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
52 static getUIServer(): UIServerConfiguration
{
53 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
55 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
58 let uiServerConfiguration
: UIServerConfiguration
= {
61 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
62 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
65 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
66 uiServerConfiguration
= Configuration
.deepMerge(
67 uiServerConfiguration
,
68 Configuration
.getConfig().uiServer
71 return uiServerConfiguration
;
74 static getPerformanceStorage(): StorageConfiguration
{
75 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
76 let storageConfiguration
: StorageConfiguration
= {
78 type: StorageType
.JSON_FILE
,
79 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
81 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
82 storageConfiguration
= {
83 ...storageConfiguration
,
84 ...Configuration
.getConfig().performanceStorage
,
87 return storageConfiguration
;
90 static getAutoReconnectMaxRetries(): number {
91 Configuration
.warnDeprecatedConfigurationKey(
92 'autoReconnectTimeout',
94 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
96 Configuration
.warnDeprecatedConfigurationKey(
99 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
101 Configuration
.warnDeprecatedConfigurationKey(
102 'autoReconnectMaxRetries',
104 'Use it in charging station template instead'
107 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
108 return Configuration
.getConfig().autoReconnectMaxRetries
;
112 static getStationTemplateUrls(): StationTemplateUrl
[] {
113 Configuration
.warnDeprecatedConfigurationKey(
114 'stationTemplateURLs',
116 "Use 'stationTemplateUrls' instead"
118 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
119 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
120 'stationTemplateURLs'
121 ] as StationTemplateUrl
[]);
122 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
123 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
125 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
127 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
132 return Configuration
.getConfig().stationTemplateUrls
;
135 static getWorker(): WorkerConfiguration
{
136 Configuration
.warnDeprecatedConfigurationKey(
139 "Use 'worker' section to define the type of worker process model instead"
141 Configuration
.warnDeprecatedConfigurationKey(
144 "Use 'worker' section to define the type of worker process model instead"
146 Configuration
.warnDeprecatedConfigurationKey(
149 "Use 'worker' section to define the worker start delay instead"
151 Configuration
.warnDeprecatedConfigurationKey(
152 'chargingStationsPerWorker',
154 "Use 'worker' section to define the number of element(s) per worker instead"
156 Configuration
.warnDeprecatedConfigurationKey(
159 "Use 'worker' section to define the worker's element start delay instead"
161 Configuration
.warnDeprecatedConfigurationKey(
164 "Use 'worker' section to define the worker pool minimum size instead"
166 Configuration
.warnDeprecatedConfigurationKey(
169 "Use 'worker' section to define the worker pool maximum size instead"
171 Configuration
.warnDeprecatedConfigurationKey(
172 'workerPoolMaxSize;',
174 "Use 'worker' section to define the worker pool maximum size instead"
176 Configuration
.warnDeprecatedConfigurationKey(
177 'workerPoolStrategy;',
179 "Use 'worker' section to define the worker pool strategy instead"
181 let workerConfiguration
: WorkerConfiguration
= {
182 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
183 ? Configuration
.getConfig().workerProcess
184 : WorkerProcessType
.WORKER_SET
,
185 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
186 ? Configuration
.getConfig().workerStartDelay
187 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
188 elementsPerWorker
: Configuration
.objectHasOwnProperty(
189 Configuration
.getConfig(),
190 'chargingStationsPerWorker'
192 ? Configuration
.getConfig().chargingStationsPerWorker
193 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
194 elementStartDelay
: Configuration
.objectHasOwnProperty(
195 Configuration
.getConfig(),
198 ? Configuration
.getConfig().elementStartDelay
199 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
200 poolMinSize
: Configuration
.objectHasOwnProperty(
201 Configuration
.getConfig(),
204 ? Configuration
.getConfig().workerPoolMinSize
205 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
206 poolMaxSize
: Configuration
.objectHasOwnProperty(
207 Configuration
.getConfig(),
210 ? Configuration
.getConfig().workerPoolMaxSize
211 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
212 poolStrategy
: Configuration
.getConfig().workerPoolStrategy
,
214 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
215 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
217 return workerConfiguration
;
220 static getLogConsole(): boolean {
221 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
222 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
223 ? Configuration
.getConfig().logConsole
227 static getLogFormat(): string {
228 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
229 ? Configuration
.getConfig().logFormat
233 static getLogRotate(): boolean {
234 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
235 ? Configuration
.getConfig().logRotate
239 static getLogMaxFiles(): number {
240 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
241 ? Configuration
.getConfig().logMaxFiles
245 static getLogLevel(): string {
246 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
247 ? Configuration
.getConfig().logLevel
.toLowerCase()
251 static getLogFile(): string {
252 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
253 ? Configuration
.getConfig().logFile
257 static getLogErrorFile(): string {
258 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
259 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
260 ? Configuration
.getConfig().logErrorFile
264 static getSupervisionUrls(): string | string[] {
265 Configuration
.warnDeprecatedConfigurationKey(
268 "Use 'supervisionUrls' instead"
270 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
271 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
275 return Configuration
.getConfig().supervisionUrls
;
278 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
279 Configuration
.warnDeprecatedConfigurationKey(
280 'distributeStationToTenantEqually',
282 "Use 'supervisionUrlDistribution' instead"
284 Configuration
.warnDeprecatedConfigurationKey(
285 'distributeStationsToTenantsEqually',
287 "Use 'supervisionUrlDistribution' instead"
289 return Configuration
.objectHasOwnProperty(
290 Configuration
.getConfig(),
291 'supervisionUrlDistribution'
293 ? Configuration
.getConfig().supervisionUrlDistribution
294 : SupervisionUrlDistribution
.ROUND_ROBIN
;
297 private static logPrefix(): string {
298 return new Date().toLocaleString() + ' Simulator configuration |';
301 private static warnDeprecatedConfigurationKey(
303 sectionName
?: string,
308 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
309 !Configuration
.isUndefined(
310 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
314 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
315 logMsgToAppend && '. ' + logMsgToAppend
318 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
320 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
321 logMsgToAppend && '. ' + logMsgToAppend
327 // Read the config file
328 private static getConfig(): ConfigurationData
{
329 if (!Configuration
.configuration
) {
331 Configuration
.configuration
= JSON
.parse(
332 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
333 ) as ConfigurationData
;
335 Configuration
.handleFileException(
336 Configuration
.logPrefix(),
337 FileType
.Configuration
,
338 Configuration
.configurationFile
,
339 error
as NodeJS
.ErrnoException
342 if (!Configuration
.configurationFileWatcher
) {
343 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
346 return Configuration
.configuration
;
349 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
351 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
352 if (filename
&& event
=== 'change') {
353 // Nullify to force configuration file reading
354 Configuration
.configuration
= null;
355 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
356 Configuration
.configurationChangeCallback().catch((error
) => {
357 throw typeof error
=== 'string' ? new Error(error
) : error
;
363 Configuration
.handleFileException(
364 Configuration
.logPrefix(),
365 FileType
.Configuration
,
366 Configuration
.configurationFile
,
367 error
as NodeJS
.ErrnoException
372 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
373 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
374 switch (storageType
) {
375 case StorageType
.JSON_FILE
:
376 return `file://${path.join(
377 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
378 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
380 case StorageType
.SQLITE
:
381 return `file://${path.join(
382 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
386 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
390 private static isObject(item
): boolean {
391 return item
&& typeof item
=== 'object' && !Array.isArray(item
);
394 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
395 if (!sources
.length
) {
398 const source
= sources
.shift();
400 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
401 for (const key
in source
) {
402 if (Configuration
.isObject(source
[key
])) {
404 Object.assign(target
, { [key
]: {} });
406 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
407 Configuration
.deepMerge(target
[key
], source
[key
]);
409 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
410 Object.assign(target
, { [key
]: source
[key
] });
414 return Configuration
.deepMerge(target
, ...sources
);
417 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
418 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
421 private static isUndefined(obj
: unknown
): boolean {
422 return typeof obj
=== 'undefined';
425 private static handleFileException(
429 error
: NodeJS
.ErrnoException
,
430 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
432 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
433 if (error
.code
=== 'ENOENT') {
435 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
438 } else if (error
.code
=== 'EEXIST') {
440 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
443 } else if (error
.code
=== 'EACCES') {
445 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
450 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
454 if (params
?.throwError
) {