2 import path from
'path';
3 import { fileURLToPath
} from
'url';
5 import chalk from
'chalk';
6 import merge from
'just-merge';
7 import { WorkerChoiceStrategies
} from
'poolifier';
9 import Constants from
'./Constants';
11 type ConfigurationData
,
12 type StationTemplateUrl
,
13 type StorageConfiguration
,
14 SupervisionUrlDistribution
,
15 type UIServerConfiguration
,
16 type WorkerConfiguration
,
17 } from
'../types/ConfigurationData';
18 import type { EmptyObject
} from
'../types/EmptyObject';
19 import type { HandleErrorParams
} from
'../types/Error';
20 import { FileType
} from
'../types/FileType';
21 import { StorageType
} from
'../types/Storage';
22 import { ApplicationProtocol
} from
'../types/UIProtocol';
23 import { WorkerProcessType
} from
'../types/Worker';
24 import WorkerConstants from
'../worker/WorkerConstants';
26 export default class Configuration
{
27 private static configurationFile
= path
.join(
28 path
.resolve(path
.dirname(fileURLToPath(import.meta
.url
)), '../'),
33 private static configurationFileWatcher
: fs
.FSWatcher
;
34 private static configuration
: ConfigurationData
| null = null;
35 private static configurationChangeCallback
: () => Promise
<void>;
37 private constructor() {
38 // This is intentional
41 static setConfigurationChangeCallback(cb
: () => Promise
<void>): void {
42 Configuration
.configurationChangeCallback
= cb
;
45 static getLogStatisticsInterval(): number {
46 Configuration
.warnDeprecatedConfigurationKey(
47 'statisticsDisplayInterval',
49 "Use 'logStatisticsInterval' instead"
52 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logStatisticsInterval')
53 ? Configuration
.getConfig().logStatisticsInterval
54 : Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
;
57 static getUIServer(): UIServerConfiguration
{
58 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiWebSocketServer')) {
60 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
63 let uiServerConfiguration
: UIServerConfiguration
= {
65 type: ApplicationProtocol
.WS
,
67 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
68 port
: Constants
.DEFAULT_UI_SERVER_PORT
,
71 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'uiServer')) {
72 uiServerConfiguration
= merge(uiServerConfiguration
, Configuration
.getConfig().uiServer
);
74 if (Configuration
.isCFEnvironment() === true) {
75 delete uiServerConfiguration
.options
.host
;
76 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
78 return uiServerConfiguration
;
81 static getPerformanceStorage(): StorageConfiguration
{
82 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
83 let storageConfiguration
: StorageConfiguration
= {
85 type: StorageType
.JSON_FILE
,
86 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
88 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
89 storageConfiguration
= {
90 ...storageConfiguration
,
91 ...Configuration
.getConfig().performanceStorage
,
94 return storageConfiguration
;
97 static getAutoReconnectMaxRetries(): number {
98 Configuration
.warnDeprecatedConfigurationKey(
99 'autoReconnectTimeout',
101 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
103 Configuration
.warnDeprecatedConfigurationKey(
106 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
108 Configuration
.warnDeprecatedConfigurationKey(
109 'autoReconnectMaxRetries',
111 'Use it in charging station template instead'
114 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
115 return Configuration
.getConfig().autoReconnectMaxRetries
;
119 static getStationTemplateUrls(): StationTemplateUrl
[] {
120 Configuration
.warnDeprecatedConfigurationKey(
121 'stationTemplateURLs',
123 "Use 'stationTemplateUrls' instead"
125 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
126 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
127 'stationTemplateURLs'
128 ] as StationTemplateUrl
[]);
129 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
130 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
132 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
134 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
139 return Configuration
.getConfig().stationTemplateUrls
;
142 static getWorker(): WorkerConfiguration
{
143 Configuration
.warnDeprecatedConfigurationKey(
146 "Use 'worker' section to define the type of worker process model instead"
148 Configuration
.warnDeprecatedConfigurationKey(
151 "Use 'worker' section to define the type of worker process model instead"
153 Configuration
.warnDeprecatedConfigurationKey(
156 "Use 'worker' section to define the worker start delay instead"
158 Configuration
.warnDeprecatedConfigurationKey(
159 'chargingStationsPerWorker',
161 "Use 'worker' section to define the number of element(s) per worker instead"
163 Configuration
.warnDeprecatedConfigurationKey(
166 "Use 'worker' section to define the worker's element start delay instead"
168 Configuration
.warnDeprecatedConfigurationKey(
171 "Use 'worker' section to define the worker pool minimum size instead"
173 Configuration
.warnDeprecatedConfigurationKey(
176 "Use 'worker' section to define the worker pool maximum size instead"
178 Configuration
.warnDeprecatedConfigurationKey(
179 'workerPoolMaxSize;',
181 "Use 'worker' section to define the worker pool maximum size instead"
183 Configuration
.warnDeprecatedConfigurationKey(
184 'workerPoolStrategy;',
186 "Use 'worker' section to define the worker pool strategy instead"
188 let workerConfiguration
: WorkerConfiguration
= {
189 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
190 ? Configuration
.getConfig().workerProcess
191 : WorkerProcessType
.WORKER_SET
,
192 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
193 ? Configuration
.getConfig().workerStartDelay
194 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
195 elementsPerWorker
: Configuration
.objectHasOwnProperty(
196 Configuration
.getConfig(),
197 'chargingStationsPerWorker'
199 ? Configuration
.getConfig().chargingStationsPerWorker
200 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
201 elementStartDelay
: Configuration
.objectHasOwnProperty(
202 Configuration
.getConfig(),
205 ? Configuration
.getConfig().elementStartDelay
206 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
207 poolMinSize
: Configuration
.objectHasOwnProperty(
208 Configuration
.getConfig(),
211 ? Configuration
.getConfig().workerPoolMinSize
212 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
213 poolMaxSize
: Configuration
.objectHasOwnProperty(
214 Configuration
.getConfig(),
217 ? Configuration
.getConfig().workerPoolMaxSize
218 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
220 Configuration
.getConfig().workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
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 | string | undefined {
249 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
250 Configuration
.getConfig().logMaxFiles
254 static getLogMaxSize(): number | string | undefined {
256 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
257 Configuration
.getConfig().logMaxSize
261 static getLogLevel(): string {
262 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
263 ? Configuration
.getConfig().logLevel
.toLowerCase()
267 static getLogFile(): string {
268 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
269 ? Configuration
.getConfig().logFile
273 static getLogErrorFile(): string {
274 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
275 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
276 ? Configuration
.getConfig().logErrorFile
280 static getSupervisionUrls(): string | string[] {
281 Configuration
.warnDeprecatedConfigurationKey(
284 "Use 'supervisionUrls' instead"
286 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
287 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
291 return Configuration
.getConfig().supervisionUrls
;
294 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
295 Configuration
.warnDeprecatedConfigurationKey(
296 'distributeStationToTenantEqually',
298 "Use 'supervisionUrlDistribution' instead"
300 Configuration
.warnDeprecatedConfigurationKey(
301 'distributeStationsToTenantsEqually',
303 "Use 'supervisionUrlDistribution' instead"
305 return Configuration
.objectHasOwnProperty(
306 Configuration
.getConfig(),
307 'supervisionUrlDistribution'
309 ? Configuration
.getConfig().supervisionUrlDistribution
310 : SupervisionUrlDistribution
.ROUND_ROBIN
;
313 private static logPrefix(): string {
314 return `${new Date().toLocaleString()} Simulator configuration |`;
317 private static warnDeprecatedConfigurationKey(
319 sectionName
?: string,
324 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
325 !Configuration
.isUndefined(
326 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
330 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
331 logMsgToAppend && `. ${logMsgToAppend}
`
334 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
336 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
337 logMsgToAppend && `. ${logMsgToAppend}
`
343 // Read the config file
344 private static getConfig(): ConfigurationData
{
345 if (!Configuration
.configuration
) {
347 Configuration
.configuration
= JSON
.parse(
348 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
349 ) as ConfigurationData
;
351 Configuration
.handleFileException(
352 Configuration
.logPrefix(),
353 FileType
.Configuration
,
354 Configuration
.configurationFile
,
355 error
as NodeJS
.ErrnoException
358 if (!Configuration
.configurationFileWatcher
) {
359 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
362 return Configuration
.configuration
;
365 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
367 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
368 if (filename
&& event
=== 'change') {
369 // Nullify to force configuration file reading
370 Configuration
.configuration
= null;
371 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
372 Configuration
.configurationChangeCallback().catch(error
=> {
373 throw typeof error
=== 'string' ? new Error(error
) : error
;
379 Configuration
.handleFileException(
380 Configuration
.logPrefix(),
381 FileType
.Configuration
,
382 Configuration
.configurationFile
,
383 error
as NodeJS
.ErrnoException
388 private static isCFEnvironment(): boolean {
389 return process
.env
.VCAP_APPLICATION
!== undefined;
392 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
393 switch (storageType
) {
394 case StorageType
.JSON_FILE
:
395 return `file://${path.join(
396 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
397 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
399 case StorageType
.SQLITE
:
400 return `file://${path.join(
401 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
402 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
405 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
409 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
410 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
413 private static isUndefined(obj
: unknown
): boolean {
414 return typeof obj
=== 'undefined';
417 private static handleFileException(
421 error
: NodeJS
.ErrnoException
,
422 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
424 const prefix
= logPrefix
.trim().length
!== 0 ? `${logPrefix} ` : '';
426 switch (error
.code
) {
428 logMsg
= `${fileType} file ${filePath} not found: `;
431 logMsg
= `${fileType} file ${filePath} already exists: `;
434 logMsg
= `${fileType} file ${filePath} access denied: `;
437 logMsg
= `${fileType} file ${filePath} error: `;
439 console
.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error
);
440 if (params
?.throwError
) {