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';
10 type ConfigurationData
,
11 type StationTemplateUrl
,
12 type StorageConfiguration
,
13 SupervisionUrlDistribution
,
14 type UIServerConfiguration
,
15 type WorkerConfiguration
,
16 } from
'../types/ConfigurationData';
17 import type { EmptyObject
} from
'../types/EmptyObject';
18 import type { HandleErrorParams
} from
'../types/Error';
19 import { FileType
} from
'../types/FileType';
20 import { StorageType
} from
'../types/Storage';
21 import { ApplicationProtocol
} from
'../types/UIProtocol';
22 import { WorkerProcessType
} from
'../types/Worker';
23 import WorkerConstants from
'../worker/WorkerConstants';
24 import Constants from
'./Constants';
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 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
394 switch (storageType
) {
395 case StorageType
.JSON_FILE
:
396 return `file://${path.join(
397 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
398 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
400 case StorageType
.SQLITE
:
401 return `file://${path.join(
402 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
406 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
410 private static objectHasOwnProperty(object
: unknown
, property
: string): boolean {
411 return Object.prototype
.hasOwnProperty
.call(object
, property
) as boolean;
414 private static isUndefined(obj
: unknown
): boolean {
415 return typeof obj
=== 'undefined';
418 private static handleFileException(
422 error
: NodeJS
.ErrnoException
,
423 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
425 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
426 if (error
.code
=== 'ENOENT') {
428 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
431 } else if (error
.code
=== 'EEXIST') {
433 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
436 } else if (error
.code
=== 'EACCES') {
438 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
443 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
447 if (params
?.throwError
) {