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
| undefined;
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 | undefined {
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 | undefined {
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
[] | undefined {
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 unknown
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 | undefined {
229 Configuration
.warnDeprecatedConfigurationKey(
232 "Use 'logConsole' instead"
234 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
235 ? Configuration
.getConfig()?.logConsole
239 static getLogFormat(): string | undefined {
240 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
241 ? Configuration
.getConfig()?.logFormat
245 static getLogRotate(): boolean | undefined {
246 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
247 ? Configuration
.getConfig()?.logRotate
251 static getLogMaxFiles(): number | string | false | undefined {
253 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
254 Configuration
.getConfig()?.logMaxFiles
258 static getLogMaxSize(): number | string | false | undefined {
260 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
261 Configuration
.getConfig()?.logMaxSize
265 static getLogLevel(): string | undefined {
266 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
267 ? Configuration
.getConfig()?.logLevel
?.toLowerCase()
271 static getLogFile(): string | undefined {
272 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
273 ? Configuration
.getConfig()?.logFile
277 static getLogErrorFile(): string | undefined {
278 Configuration
.warnDeprecatedConfigurationKey(
281 "Use 'logErrorFile' instead"
283 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
284 ? Configuration
.getConfig()?.logErrorFile
288 static getSupervisionUrls(): string | string[] | undefined {
289 Configuration
.warnDeprecatedConfigurationKey(
292 "Use 'supervisionUrls' instead"
294 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
295 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()['supervisionURLs'] as
299 return Configuration
.getConfig()?.supervisionUrls
;
302 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
| undefined {
303 Configuration
.warnDeprecatedConfigurationKey(
304 'distributeStationToTenantEqually',
306 "Use 'supervisionUrlDistribution' instead"
308 Configuration
.warnDeprecatedConfigurationKey(
309 'distributeStationsToTenantsEqually',
311 "Use 'supervisionUrlDistribution' instead"
313 return Configuration
.objectHasOwnProperty(
314 Configuration
.getConfig(),
315 'supervisionUrlDistribution'
317 ? Configuration
.getConfig()?.supervisionUrlDistribution
318 : SupervisionUrlDistribution
.ROUND_ROBIN
;
321 private static logPrefix(): string {
322 return `${new Date().toLocaleString()} Simulator configuration |`;
325 private static warnDeprecatedConfigurationKey(
327 sectionName
?: string,
332 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
333 !Configuration
.isUndefined(
334 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
338 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
339 logMsgToAppend && `. ${logMsgToAppend}
`
342 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
344 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
345 logMsgToAppend && `. ${logMsgToAppend}
`
351 // Read the config file
352 private static getConfig(): ConfigurationData
| null {
353 if (!Configuration
.configuration
) {
355 Configuration
.configuration
= JSON
.parse(
356 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
357 ) as ConfigurationData
;
359 Configuration
.handleFileException(
360 Configuration
.logPrefix(),
361 FileType
.Configuration
,
362 Configuration
.configurationFile
,
363 error
as NodeJS
.ErrnoException
366 if (!Configuration
.configurationFileWatcher
) {
367 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
370 return Configuration
.configuration
;
373 private static getConfigurationFileWatcher(): fs
.FSWatcher
| undefined {
375 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
376 if (filename
&& event
=== 'change') {
377 // Nullify to force configuration file reading
378 Configuration
.configuration
= null;
379 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
380 Configuration
.configurationChangeCallback().catch((error
) => {
381 throw typeof error
=== 'string' ? new Error(error
) : error
;
387 Configuration
.handleFileException(
388 Configuration
.logPrefix(),
389 FileType
.Configuration
,
390 Configuration
.configurationFile
,
391 error
as NodeJS
.ErrnoException
396 private static isCFEnvironment(): boolean {
397 return process
.env
.VCAP_APPLICATION
!== undefined;
400 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
401 switch (storageType
) {
402 case StorageType
.JSON_FILE
:
403 return `file://${path.join(
404 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
405 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
407 case StorageType
.SQLITE
:
408 return `file://${path.join(
409 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
410 `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}
.db
`
413 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
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 obj
=== undefined;
425 private static handleFileException(
429 error
: NodeJS
.ErrnoException
,
430 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
432 const prefix
= logPrefix
.trim().length
!== 0 ? `${logPrefix} ` : '';
434 switch (error
.code
) {
436 logMsg
= `${fileType} file ${filePath} not found: `;
439 logMsg
= `${fileType} file ${filePath} already exists: `;
442 logMsg
= `${fileType} file ${filePath} access denied: `;
445 logMsg
= `${fileType} file ${filePath} error: `;
447 console
.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error
);
448 if (params
?.throwError
) {