perf(simulator): use content addressable cache for idtag distribution
[e-mobility-charging-stations-simulator.git] / src / charging-station / AuthorizedTagsCache.ts
CommitLineData
130783a7 1import fs from 'node:fs';
8114d10e 2
aaf2bf9c
JB
3import { type ChargingStation, ChargingStationUtils } from './internal';
4import { FileType, IdTagDistribution } from '../types';
60a74391 5import { FileUtils, Utils, logger } from '../utils';
9d7484a4 6
d6a62ad9
JB
7type TagsCacheValueType = {
8 tags: string[];
9 tagsFileWatcher: fs.FSWatcher | undefined;
10};
11
268a74bb 12export class AuthorizedTagsCache {
9d7484a4 13 private static instance: AuthorizedTagsCache | null = null;
d6a62ad9 14 private readonly tagsCaches: Map<string, TagsCacheValueType>;
aaf2bf9c 15 private readonly tagsCachesAddressableIndexes: Map<string, number>;
9d7484a4
JB
16
17 private constructor() {
d6a62ad9 18 this.tagsCaches = new Map<string, TagsCacheValueType>();
aaf2bf9c 19 this.tagsCachesAddressableIndexes = new Map<string, number>();
9d7484a4
JB
20 }
21
22 public static getInstance(): AuthorizedTagsCache {
1ca780f9 23 if (AuthorizedTagsCache.instance === null) {
9d7484a4
JB
24 AuthorizedTagsCache.instance = new AuthorizedTagsCache();
25 }
26 return AuthorizedTagsCache.instance;
27 }
28
aaf2bf9c
JB
29 public getIdTag(
30 distribution: IdTagDistribution,
31 chargingStation: ChargingStation,
32 connectorId: number
33 ): string {
34 const hashId = chargingStation.stationInfo.hashId;
35 const authorizationFile = ChargingStationUtils.getAuthorizationFile(
36 chargingStation.stationInfo
37 );
38 switch (distribution) {
39 case IdTagDistribution.RANDOM:
40 return this.getRandomIdTag(hashId, authorizationFile);
41 case IdTagDistribution.ROUND_ROBIN:
42 return this.getRoundRobinIdTag(hashId, authorizationFile);
43 case IdTagDistribution.CONNECTOR_AFFINITY:
44 return this.getConnectorAffinityIdTag(chargingStation, connectorId);
45 default:
46 return this.getRoundRobinIdTag(hashId, authorizationFile);
47 }
48 }
49
1895299d 50 public getAuthorizedTags(file: string): string[] | undefined {
0638ddd2 51 if (this.hasTags(file) === false) {
9d7484a4 52 this.setTags(file, this.getAuthorizedTagsFromFile(file));
9d7484a4
JB
53 }
54 return this.getTags(file);
55 }
56
a36bad10
JB
57 public deleteAuthorizedTags(file: string): boolean {
58 return this.deleteTags(file);
59 }
60
aaf2bf9c
JB
61 private getRandomIdTag(hashId: string, file: string): string {
62 const tags = this.getAuthorizedTags(file);
63 this.tagsCachesAddressableIndexes.set(
64 file + hashId,
65 Math.floor(Utils.secureRandom() * tags.length)
66 );
67 return tags[this.tagsCachesAddressableIndexes.get(file + hashId)];
68 }
69
70 private getRoundRobinIdTag(hashId: string, file: string): string {
71 const tags = this.getAuthorizedTags(file);
72 const idTagIndex = this.tagsCachesAddressableIndexes.get(file + hashId) ?? 0;
73 const idTag = tags[idTagIndex];
74 this.tagsCachesAddressableIndexes.set(
75 file + hashId,
76 idTagIndex === tags.length - 1 ? 0 : idTagIndex + 1
77 );
78 return idTag;
79 }
80
81 private getConnectorAffinityIdTag(chargingStation: ChargingStation, connectorId: number): string {
82 const file = ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo);
83 const tags = this.getAuthorizedTags(file);
84 const hashId = chargingStation.stationInfo.hashId;
85 this.tagsCachesAddressableIndexes.set(
86 file + hashId,
87 (chargingStation.index - 1 + (connectorId - 1)) % tags.length
88 );
89 return tags[this.tagsCachesAddressableIndexes.get(file + hashId)];
90 }
91
9d7484a4
JB
92 private hasTags(file: string): boolean {
93 return this.tagsCaches.has(file);
94 }
95
96 private setTags(file: string, tags: string[]) {
d6a62ad9
JB
97 return this.tagsCaches.set(file, {
98 tags,
99 tagsFileWatcher: FileUtils.watchJsonFile(
100 file,
101 FileType.Authorization,
102 this.logPrefix(file),
103 undefined,
104 (event, filename) => {
105 if (Utils.isNotEmptyString(filename) && event === 'change') {
106 try {
107 logger.debug(
108 `${this.logPrefix(file)} ${FileType.Authorization} file have changed, reload`
109 );
110 this.deleteTags(file);
aaf2bf9c 111 this.deleteTagsIndexes(file);
d6a62ad9
JB
112 } catch (error) {
113 FileUtils.handleFileException(
114 file,
115 FileType.Authorization,
116 error as NodeJS.ErrnoException,
117 this.logPrefix(file),
118 {
119 throwError: false,
120 }
121 );
122 }
123 }
124 }
125 ),
126 });
9d7484a4
JB
127 }
128
1895299d 129 private getTags(file: string): string[] | undefined {
d6a62ad9 130 return this.tagsCaches.get(file)?.tags;
9d7484a4
JB
131 }
132
133 private deleteTags(file: string): boolean {
d6a62ad9 134 this.tagsCaches.get(file)?.tagsFileWatcher?.close();
9d7484a4
JB
135 return this.tagsCaches.delete(file);
136 }
137
aaf2bf9c
JB
138 private deleteTagsIndexes(file: string): void {
139 for (const [key] of this.tagsCachesAddressableIndexes) {
140 if (key.startsWith(file)) {
141 this.tagsCachesAddressableIndexes.delete(key);
142 }
143 }
144 }
145
9d7484a4
JB
146 private getAuthorizedTagsFromFile(file: string): string[] {
147 let authorizedTags: string[] = [];
148 if (file) {
149 try {
150 // Load authorization file
151 authorizedTags = JSON.parse(fs.readFileSync(file, 'utf8')) as string[];
152 } catch (error) {
153 FileUtils.handleFileException(
9d7484a4 154 file,
7164966d
JB
155 FileType.Authorization,
156 error as NodeJS.ErrnoException,
157 this.logPrefix(file)
9d7484a4
JB
158 );
159 }
160 } else {
44eb6026 161 logger.info(`${this.logPrefix(file)} No authorization file given`);
9d7484a4
JB
162 }
163 return authorizedTags;
164 }
165
8b7072dc 166 private logPrefix = (file: string): string => {
9d7484a4 167 return Utils.logPrefix(` Authorized tags cache for authorization file '${file}' |`);
8b7072dc 168 };
9d7484a4 169}