2 import path from
'path';
3 import { fileURLToPath
} from
'url';
5 import chalk from
'chalk';
7 import ConfigurationData
, {
10 SupervisionUrlDistribution
,
11 UIServerConfiguration
,
13 } from
'../types/ConfigurationData';
14 import { EmptyObject
} from
'../types/EmptyObject';
15 import { HandleErrorParams
} from
'../types/Error';
16 import { FileType
} from
'../types/FileType';
17 import { StorageType
} from
'../types/Storage';
18 import { WorkerProcessType
} from
'../types/Worker';
19 import WorkerConstants from
'../worker/WorkerConstants';
20 import Constants from
'./Constants';
22 export default class Configuration
{
23 private static configurationFile
= path
.join(
24 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
29 private static configurationFileWatcher
: fs
.FSWatcher
;
30 private static configuration
: ConfigurationData
| null = null;
31 private static configurationChangeCallback
: () => Promise
<void>;
33 private constructor() {
34 // This is intentional
37 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
38 Configuration
.configurationChangeCallback
= cb
;
41 static getLogStatisticsInterval(): number {
42 Configuration
.warnDeprecatedConfigurationKey(
43 'statisticsDisplayInterval',
45 "Use 'logStatisticsInterval' instead"
48 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
49 ? Configuration
.getConfig().logStatisticsInterval
50 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
53 static getUIServer(): UIServerConfiguration
{
54 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
56 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
59 let uiServerConfiguration
: UIServerConfiguration
= {
62 host
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_HOST
,
63 port
: Constants
.DEFAULT_UI_WEBSOCKET_SERVER_PORT
,
66 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
67 uiServerConfiguration
= Configuration
.deepMerge(
68 uiServerConfiguration
,
69 Configuration
.getConfig().uiServer
72 return uiServerConfiguration
;
75 static getPerformanceStorage(): StorageConfiguration
{
76 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
77 let storageConfiguration
: StorageConfiguration
= {
79 type: StorageType
.JSON_FILE
,
80 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
82 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
83 storageConfiguration
= {
84 ...storageConfiguration
,
85 ...Configuration
.getConfig().performanceStorage
,
88 return storageConfiguration
;
91 static getAutoReconnectMaxRetries(): number {
92 Configuration
.warnDeprecatedConfigurationKey(
93 'autoReconnectTimeout',
95 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
97 Configuration
.warnDeprecatedConfigurationKey(
100 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
102 Configuration
.warnDeprecatedConfigurationKey(
103 'autoReconnectMaxRetries',
105 'Use it in charging station template instead'
108 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
109 return Configuration
.getConfig().autoReconnectMaxRetries
;
113 static getStationTemplateUrls(): StationTemplateUrl
[] {
114 Configuration
.warnDeprecatedConfigurationKey(
115 'stationTemplateURLs',
117 "Use 'stationTemplateUrls' instead"
119 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
120 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
121 'stationTemplateURLs'
122 ] as StationTemplateUrl
[]);
123 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
124 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
126 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
128 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
133 return Configuration
.getConfig().stationTemplateUrls
;
136 static getWorker(): WorkerConfiguration
{
137 Configuration
.warnDeprecatedConfigurationKey(
140 "Use 'worker' section to define the type of worker process model instead"
142 Configuration
.warnDeprecatedConfigurationKey(
145 "Use 'worker' section to define the type of worker process model instead"
147 Configuration
.warnDeprecatedConfigurationKey(
150 "Use 'worker' section to define the worker start delay instead"
152 Configuration
.warnDeprecatedConfigurationKey(
153 'chargingStationsPerWorker',
155 "Use 'worker' section to define the number of element(s) per worker instead"
157 Configuration
.warnDeprecatedConfigurationKey(
160 "Use 'worker' section to define the worker's element start delay instead"
162 Configuration
.warnDeprecatedConfigurationKey(
165 "Use 'worker' section to define the worker pool minimum size instead"
167 Configuration
.warnDeprecatedConfigurationKey(
170 "Use 'worker' section to define the worker pool maximum size instead"
172 Configuration
.warnDeprecatedConfigurationKey(
173 'workerPoolMaxSize;',
175 "Use 'worker' section to define the worker pool maximum size instead"
177 Configuration
.warnDeprecatedConfigurationKey(
178 'workerPoolStrategy;',
180 "Use 'worker' section to define the worker pool strategy instead"
182 let workerConfiguration
: WorkerConfiguration
= {
183 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
184 ? Configuration
.getConfig().workerProcess
185 : WorkerProcessType
.WORKER_SET
,
186 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
187 ? Configuration
.getConfig().workerStartDelay
188 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
189 elementsPerWorker
: Configuration
.objectHasOwnProperty(
190 Configuration
.getConfig(),
191 'chargingStationsPerWorker'
193 ? Configuration
.getConfig().chargingStationsPerWorker
194 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
195 elementStartDelay
: Configuration
.objectHasOwnProperty(
196 Configuration
.getConfig(),
199 ? Configuration
.getConfig().elementStartDelay
200 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
201 poolMinSize
: Configuration
.objectHasOwnProperty(
202 Configuration
.getConfig(),
205 ? Configuration
.getConfig().workerPoolMinSize
206 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
207 poolMaxSize
: Configuration
.objectHasOwnProperty(
208 Configuration
.getConfig(),
211 ? Configuration
.getConfig().workerPoolMaxSize
212 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
213 poolStrategy
: Configuration
.getConfig().workerPoolStrategy
,
215 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
216 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
218 return workerConfiguration
;
221 static getLogConsole(): boolean {
222 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
223 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
224 ? Configuration
.getConfig().logConsole
228 static getLogFormat(): string {
229 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
230 ? Configuration
.getConfig().logFormat
234 static getLogRotate(): boolean {
235 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
236 ? Configuration
.getConfig().logRotate
240 static getLogMaxFiles(): number {
241 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles')
242 ? Configuration
.getConfig().logMaxFiles
246 static getLogLevel(): string {
247 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
248 ? Configuration
.getConfig().logLevel
.toLowerCase()
252 static getLogFile(): string {
253 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
254 ? Configuration
.getConfig().logFile
258 static getLogErrorFile(): string {
259 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
260 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
261 ? Configuration
.getConfig().logErrorFile
265 static getSupervisionUrls(): string | string[] {
266 Configuration
.warnDeprecatedConfigurationKey(
269 "Use 'supervisionUrls' instead"
271 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
272 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
276 return Configuration
.getConfig().supervisionUrls
;
279 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
280 Configuration
.warnDeprecatedConfigurationKey(
281 'distributeStationToTenantEqually',
283 "Use 'supervisionUrlDistribution' instead"
285 Configuration
.warnDeprecatedConfigurationKey(
286 'distributeStationsToTenantsEqually',
288 "Use 'supervisionUrlDistribution' instead"
290 return Configuration
.objectHasOwnProperty(
291 Configuration
.getConfig(),
292 'supervisionUrlDistribution'
294 ? Configuration
.getConfig().supervisionUrlDistribution
295 : SupervisionUrlDistribution
.ROUND_ROBIN
;
298 private static logPrefix(): string {
299 return new Date().toLocaleString() + ' Simulator configuration |';
302 private static warnDeprecatedConfigurationKey(
304 sectionName
?: string,
309 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
310 !Configuration
.isUndefined(
311 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
315 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
316 logMsgToAppend && '. ' + logMsgToAppend
319 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
321 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
322 logMsgToAppend && '. ' + logMsgToAppend
328 // Read the config file
329 private static getConfig(): ConfigurationData
{
330 if (!Configuration
.configuration
) {
332 Configuration
.configuration
= JSON
.parse(
333 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
334 ) as ConfigurationData
;
336 Configuration
.handleFileException(
337 Configuration
.logPrefix(),
338 FileType
.Configuration
,
339 Configuration
.configurationFile
,
340 error
as NodeJS
.ErrnoException
343 if (!Configuration
.configurationFileWatcher
) {
344 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
347 return Configuration
.configuration
;
350 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
352 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
353 if (filename
&& event
=== 'change') {
354 // Nullify to force configuration file reading
355 Configuration
.configuration
= null;
356 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
357 Configuration
.configurationChangeCallback().catch((error
) => {
358 throw typeof error
=== 'string' ? new Error(error
) : error
;
364 Configuration
.handleFileException(
365 Configuration
.logPrefix(),
366 FileType
.Configuration
,
367 Configuration
.configurationFile
,
368 error
as NodeJS
.ErrnoException
373 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
374 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
375 switch (storageType
) {
376 case StorageType
.JSON_FILE
:
377 return `file://${path.join(
378 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
379 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
381 case StorageType
.SQLITE
:
382 return `file://${path.join(
383 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
387 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
391 private static isObject(item
): boolean {
392 return item
&& typeof item
=== 'object' && Array.isArray(item
) === false;
395 private static deepMerge(target
: object
, ...sources
: object
[]): object
{
396 if (!sources
.length
) {
399 const source
= sources
.shift();
401 if (Configuration
.isObject(target
) && Configuration
.isObject(source
)) {
402 for (const key
in source
) {
403 if (Configuration
.isObject(source
[key
])) {
405 Object.assign(target
, { [key
]: {} });
407 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
408 Configuration
.deepMerge(target
[key
], source
[key
]);
410 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
411 Object.assign(target
, { [key
]: source
[key
] });
415 return Configuration
.deepMerge(target
, ...sources
);
418 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
419 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
422 private static isUndefined(obj
: unknown
): boolean {
423 return typeof obj
=== 'undefined';
426 private static handleFileException(
430 error
: NodeJS
.ErrnoException
,
431 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
433 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
434 if (error
.code
=== 'ENOENT') {
436 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
439 } else if (error
.code
=== 'EEXIST') {
441 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
444 } else if (error
.code
=== 'EACCES') {
446 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
451 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
455 if (params
?.throwError
) {