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
= Configuration
.merge(
73 uiServerConfiguration
,
74 Configuration
.getConfig().uiServer
77 if (Configuration
.isCFEnvironment() === true) {
78 delete uiServerConfiguration
.options
.host
;
79 uiServerConfiguration
.options
.port
= parseInt(process
.env
.PORT
);
81 return uiServerConfiguration
;
84 static getPerformanceStorage(): StorageConfiguration
{
85 Configuration
.warnDeprecatedConfigurationKey('URI', 'performanceStorage', "Use 'uri' instead");
86 let storageConfiguration
: StorageConfiguration
= {
88 type: StorageType
.JSON_FILE
,
89 uri
: this.getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
),
91 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'performanceStorage')) {
92 storageConfiguration
= {
93 ...storageConfiguration
,
94 ...Configuration
.getConfig().performanceStorage
,
97 return storageConfiguration
;
100 static getAutoReconnectMaxRetries(): number {
101 Configuration
.warnDeprecatedConfigurationKey(
102 'autoReconnectTimeout',
104 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
106 Configuration
.warnDeprecatedConfigurationKey(
109 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
111 Configuration
.warnDeprecatedConfigurationKey(
112 'autoReconnectMaxRetries',
114 'Use it in charging station template instead'
117 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'autoReconnectMaxRetries')) {
118 return Configuration
.getConfig().autoReconnectMaxRetries
;
122 static getStationTemplateUrls(): StationTemplateUrl
[] {
123 Configuration
.warnDeprecatedConfigurationKey(
124 'stationTemplateURLs',
126 "Use 'stationTemplateUrls' instead"
128 !Configuration
.isUndefined(Configuration
.getConfig()['stationTemplateURLs']) &&
129 (Configuration
.getConfig().stationTemplateUrls
= Configuration
.getConfig()[
130 'stationTemplateURLs'
131 ] as StationTemplateUrl
[]);
132 Configuration
.getConfig().stationTemplateUrls
.forEach((stationUrl
: StationTemplateUrl
) => {
133 if (!Configuration
.isUndefined(stationUrl
['numberOfStation'])) {
135 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
137 }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
142 return Configuration
.getConfig().stationTemplateUrls
;
145 static getWorker(): WorkerConfiguration
{
146 Configuration
.warnDeprecatedConfigurationKey(
149 "Use 'worker' section to define the type of worker process model instead"
151 Configuration
.warnDeprecatedConfigurationKey(
154 "Use 'worker' section to define the type of worker process model instead"
156 Configuration
.warnDeprecatedConfigurationKey(
159 "Use 'worker' section to define the worker start delay instead"
161 Configuration
.warnDeprecatedConfigurationKey(
162 'chargingStationsPerWorker',
164 "Use 'worker' section to define the number of element(s) per worker instead"
166 Configuration
.warnDeprecatedConfigurationKey(
169 "Use 'worker' section to define the worker's element start delay instead"
171 Configuration
.warnDeprecatedConfigurationKey(
174 "Use 'worker' section to define the worker pool minimum size instead"
176 Configuration
.warnDeprecatedConfigurationKey(
179 "Use 'worker' section to define the worker pool maximum size instead"
181 Configuration
.warnDeprecatedConfigurationKey(
182 'workerPoolMaxSize;',
184 "Use 'worker' section to define the worker pool maximum size instead"
186 Configuration
.warnDeprecatedConfigurationKey(
187 'workerPoolStrategy;',
189 "Use 'worker' section to define the worker pool strategy instead"
191 let workerConfiguration
: WorkerConfiguration
= {
192 processType
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerProcess')
193 ? Configuration
.getConfig().workerProcess
194 : WorkerProcessType
.WORKER_SET
,
195 startDelay
: Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'workerStartDelay')
196 ? Configuration
.getConfig().workerStartDelay
197 : WorkerConstants
.DEFAULT_WORKER_START_DELAY
,
198 elementsPerWorker
: Configuration
.objectHasOwnProperty(
199 Configuration
.getConfig(),
200 'chargingStationsPerWorker'
202 ? Configuration
.getConfig().chargingStationsPerWorker
203 : WorkerConstants
.DEFAULT_ELEMENTS_PER_WORKER
,
204 elementStartDelay
: Configuration
.objectHasOwnProperty(
205 Configuration
.getConfig(),
208 ? Configuration
.getConfig().elementStartDelay
209 : WorkerConstants
.DEFAULT_ELEMENT_START_DELAY
,
210 poolMinSize
: Configuration
.objectHasOwnProperty(
211 Configuration
.getConfig(),
214 ? Configuration
.getConfig().workerPoolMinSize
215 : WorkerConstants
.DEFAULT_POOL_MIN_SIZE
,
216 poolMaxSize
: Configuration
.objectHasOwnProperty(
217 Configuration
.getConfig(),
220 ? Configuration
.getConfig().workerPoolMaxSize
221 : WorkerConstants
.DEFAULT_POOL_MAX_SIZE
,
223 Configuration
.getConfig().workerPoolStrategy
?? WorkerChoiceStrategies
.ROUND_ROBIN
,
225 if (Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'worker')) {
226 workerConfiguration
= { ...workerConfiguration
, ...Configuration
.getConfig().worker
};
228 return workerConfiguration
;
231 static getLogConsole(): boolean {
232 Configuration
.warnDeprecatedConfigurationKey('consoleLog', null, "Use 'logConsole' instead");
233 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logConsole')
234 ? Configuration
.getConfig().logConsole
238 static getLogFormat(): string {
239 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFormat')
240 ? Configuration
.getConfig().logFormat
244 static getLogRotate(): boolean {
245 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logRotate')
246 ? Configuration
.getConfig().logRotate
250 static getLogMaxFiles(): number | string | undefined {
252 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
253 Configuration
.getConfig().logMaxFiles
257 static getLogMaxSize(): number | string | undefined {
259 Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logMaxFiles') &&
260 Configuration
.getConfig().logMaxSize
264 static getLogLevel(): string {
265 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logLevel')
266 ? Configuration
.getConfig().logLevel
.toLowerCase()
270 static getLogFile(): string {
271 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logFile')
272 ? Configuration
.getConfig().logFile
276 static getLogErrorFile(): string {
277 Configuration
.warnDeprecatedConfigurationKey('errorFile', null, "Use 'logErrorFile' instead");
278 return Configuration
.objectHasOwnProperty(Configuration
.getConfig(), 'logErrorFile')
279 ? Configuration
.getConfig().logErrorFile
283 static getSupervisionUrls(): string | string[] {
284 Configuration
.warnDeprecatedConfigurationKey(
287 "Use 'supervisionUrls' instead"
289 !Configuration
.isUndefined(Configuration
.getConfig()['supervisionURLs']) &&
290 (Configuration
.getConfig().supervisionUrls
= Configuration
.getConfig()[
294 return Configuration
.getConfig().supervisionUrls
;
297 static getSupervisionUrlDistribution(): SupervisionUrlDistribution
{
298 Configuration
.warnDeprecatedConfigurationKey(
299 'distributeStationToTenantEqually',
301 "Use 'supervisionUrlDistribution' instead"
303 Configuration
.warnDeprecatedConfigurationKey(
304 'distributeStationsToTenantsEqually',
306 "Use 'supervisionUrlDistribution' instead"
308 return Configuration
.objectHasOwnProperty(
309 Configuration
.getConfig(),
310 'supervisionUrlDistribution'
312 ? Configuration
.getConfig().supervisionUrlDistribution
313 : SupervisionUrlDistribution
.ROUND_ROBIN
;
316 private static logPrefix(): string {
317 return new Date().toLocaleString() + ' Simulator configuration |';
320 private static warnDeprecatedConfigurationKey(
322 sectionName
?: string,
327 !Configuration
.isUndefined(Configuration
.getConfig()[sectionName
]) &&
328 !Configuration
.isUndefined(
329 (Configuration
.getConfig()[sectionName
] as Record
<string, unknown
>)[key
]
333 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
334 logMsgToAppend && '. ' + logMsgToAppend
337 } else if (!Configuration
.isUndefined(Configuration
.getConfig()[key
])) {
339 chalk
`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
340 logMsgToAppend && '. ' + logMsgToAppend
346 // Read the config file
347 private static getConfig(): ConfigurationData
{
348 if (!Configuration
.configuration
) {
350 Configuration
.configuration
= JSON
.parse(
351 fs
.readFileSync(Configuration
.configurationFile
, 'utf8')
352 ) as ConfigurationData
;
354 Configuration
.handleFileException(
355 Configuration
.logPrefix(),
356 FileType
.Configuration
,
357 Configuration
.configurationFile
,
358 error
as NodeJS
.ErrnoException
361 if (!Configuration
.configurationFileWatcher
) {
362 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher();
365 return Configuration
.configuration
;
368 private static getConfigurationFileWatcher(): fs
.FSWatcher
{
370 return fs
.watch(Configuration
.configurationFile
, (event
, filename
): void => {
371 if (filename
&& event
=== 'change') {
372 // Nullify to force configuration file reading
373 Configuration
.configuration
= null;
374 if (!Configuration
.isUndefined(Configuration
.configurationChangeCallback
)) {
375 Configuration
.configurationChangeCallback().catch((error
) => {
376 throw typeof error
=== 'string' ? new Error(error
) : error
;
382 Configuration
.handleFileException(
383 Configuration
.logPrefix(),
384 FileType
.Configuration
,
385 Configuration
.configurationFile
,
386 error
as NodeJS
.ErrnoException
391 private static isCFEnvironment(): boolean {
392 return process
.env
.VCAP_APPLICATION
!== undefined;
395 private static getDefaultPerformanceStorageUri(storageType
: StorageType
) {
396 const SQLiteFileName
= `${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`;
397 switch (storageType
) {
398 case StorageType
.JSON_FILE
:
399 return `file://${path.join(
400 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
401 Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME
403 case StorageType
.SQLITE
:
404 return `file://${path.join(
405 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'),
409 throw new Error(`Performance storage URI is mandatory with storage type '${storageType}'`);
413 // private static isObject(item: unknown): boolean {
414 // return item && typeof item === 'object' && Array.isArray(item) === false;
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 merge(target
: object
, ...sources
: object
[]): object
{
426 return merge(target
, ...sources
);
429 private static handleFileException(
433 error
: NodeJS
.ErrnoException
,
434 params
: HandleErrorParams
<EmptyObject
> = { throwError
: true }
436 const prefix
= logPrefix
.length
!== 0 ? logPrefix
+ ' ' : '';
437 if (error
.code
=== 'ENOENT') {
439 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' not found: '),
442 } else if (error
.code
=== 'EEXIST') {
444 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' already exists: '),
447 } else if (error
.code
=== 'EACCES') {
449 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' access denied: '),
454 chalk
.green(prefix
) + chalk
.red(fileType
+ ' file ' + filePath
+ ' error: '),
458 if (params
?.throwError
) {