26bdf58d4a41e6c52706ed70fcffe13384d4afa4
[nonrtric/plt/ranpm.git] / pmproducer / src / main / java / org / oran / pmproducer / filter / PmReportFilter.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2023 Nordix Foundation
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 package org.oran.pmproducer.filter;
22
23 import java.lang.invoke.MethodHandles;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Map;
29 import java.util.Set;
30
31 import lombok.Getter;
32 import lombok.Setter;
33
34 import org.oran.pmproducer.filter.PmReport.Event;
35 import org.oran.pmproducer.filter.PmReport.MeasDataCollection;
36 import org.oran.pmproducer.filter.PmReport.MeasInfoList;
37 import org.oran.pmproducer.filter.PmReport.MeasResult;
38 import org.oran.pmproducer.filter.PmReport.MeasTypes;
39 import org.oran.pmproducer.filter.PmReport.MeasValuesList;
40 import org.oran.pmproducer.filter.PmReport.Perf3gppFields;
41 import org.oran.pmproducer.tasks.TopicListener.DataFromTopic;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.thymeleaf.util.StringUtils;
45
46 public class PmReportFilter {
47     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
48
49     private static com.google.gson.Gson gson = new com.google.gson.GsonBuilder() //
50             .disableHtmlEscaping() //
51             .excludeFieldsWithoutExposeAnnotation() //
52             .create();
53
54     // excludeFieldsWithoutExposeAnnotation is not needed when parsing and this is a
55     // bit quicker
56     private static com.google.gson.Gson gsonParse = new com.google.gson.GsonBuilder() //
57             .disableHtmlEscaping() //
58             .create();
59
60     @Getter
61     private final FilterData filterData;
62
63     @Getter
64     public static class FilterData {
65
66         public static class MeasTypeSpec {
67             static MeasTypeSpec empty = new MeasTypeSpec();
68
69             static MeasTypeSpec empty() {
70                 return empty;
71             }
72
73             @Getter
74             String measuredObjClass;
75
76             @Getter
77             final Set<String> measTypes = new HashSet<>();
78
79             @Override
80             public boolean equals(Object obj) {
81                 return measuredObjClass.equals(obj);
82             }
83
84             @Override
85             public int hashCode() {
86                 return measuredObjClass.hashCode();
87             }
88         }
89
90         final Set<String> sourceNames = new HashSet<>();
91         final Set<String> measObjInstIds = new HashSet<>();
92         final Collection<MeasTypeSpec> measTypeSpecs = new ArrayList<>();
93         final Set<String> measuredEntityDns = new HashSet<>();
94
95         public void addMeasTypes(String measObjClass, String... measTypes) {
96             MeasTypeSpec spec = this.findMeasTypeSpec(measObjClass);
97             if (spec == null) {
98                 spec = new MeasTypeSpec();
99                 spec.measuredObjClass = measObjClass;
100                 this.measTypeSpecs.add(spec);
101             }
102             for (String measType : measTypes) {
103                 spec.measTypes.add(measType);
104             }
105         }
106
107         public void addMeasTypes(String measObjClass, Collection<String> measTypes) {
108             for (String measType : measTypes) {
109                 addMeasTypes(measObjClass, measType);
110             }
111         }
112
113         @Setter
114         String pmRopStartTime;
115
116         @Setter
117         String pmRopEndTime;
118
119         public void addAll(FilterData other) {
120             addAll(other.sourceNames, sourceNames);
121             addAll(other.measObjInstIds, measObjInstIds);
122             addAll(other.measTypeSpecs);
123             addAll(other.measuredEntityDns, measuredEntityDns);
124         }
125
126         public MeasTypeSpec getMeasTypeSpec(String measuredObjClass) {
127             if (measTypeSpecs.isEmpty()) {
128                 return MeasTypeSpec.empty();
129             }
130             return findMeasTypeSpec(measuredObjClass);
131         }
132
133         private MeasTypeSpec findMeasTypeSpec(String measuredObjClass) {
134             for (MeasTypeSpec t : this.measTypeSpecs) {
135                 if (t.measuredObjClass.equals(measuredObjClass)) {
136                     return t;
137                 }
138             }
139             return null;
140         }
141
142         private void addAll(Collection<MeasTypeSpec> measTypes) {
143             for (MeasTypeSpec s : measTypes) {
144                 addMeasTypes(s.getMeasuredObjClass(), s.getMeasTypes());
145             }
146         }
147
148         private void addAll(Set<String> source, Set<String> dst) {
149             if (source.isEmpty()) {
150                 dst.clear();
151             } else if (dst.isEmpty()) {
152                 // Nothing, this means 'match all'
153             } else {
154                 dst.addAll(source);
155             }
156         }
157     }
158
159     public static PmReport parse(String string) {
160         return gsonParse.fromJson(string, PmReport.class);
161     }
162
163     private static class MeasTypesIndexed extends PmReport.MeasTypes {
164
165         private Map<String, Integer> map = new HashMap<>();
166
167         public int addP(String measTypeName) {
168             Integer p = map.get(measTypeName);
169             if (p != null) {
170                 return p;
171             } else {
172                 sMeasTypesList.add(measTypeName);
173                 this.map.put(measTypeName, sMeasTypesList.size());
174                 return sMeasTypesList.size();
175             }
176         }
177     }
178
179     public PmReportFilter(FilterData filterData) {
180         this.filterData = filterData;
181     }
182
183     public FilteredData filter(DataFromTopic data) {
184         try {
185             PmReport report = getPmReport(data);
186
187             if (report.event == null || report.event.getPerf3gppFields() == null) {
188                 logger.warn("Received PM report with no perf3gppFields, ignored. {}", data);
189                 return FilteredData.empty();
190             }
191
192             PmReport reportFiltered = filter(report, this.filterData);
193             if (reportFiltered == null) {
194                 return FilteredData.empty();
195             }
196             return new FilteredData(data.infoTypeId, data.key, gson.toJson(reportFiltered).getBytes());
197         } catch (Exception e) {
198             logger.warn("Could not parse PM data. {}, reason: {}", data, e.getMessage());
199             return FilteredData.empty();
200         }
201     }
202
203     @SuppressWarnings("java:S2445") // "data" is a method parameter, and should not be used for synchronization.
204     private PmReport getPmReport(DataFromTopic data) {
205         synchronized (data) {
206             if (data.getCachedPmReport() == null) {
207                 data.setCachedPmReport(parse((data.valueAsString())));
208             }
209             return data.getCachedPmReport();
210         }
211     }
212
213     /**
214      * Updates the report based on the filter data.
215      *
216      * @param report
217      * @param filterData
218      * @return true if there is anything left in the report
219      */
220     private PmReport filter(PmReport report, FilterData filterData) {
221         if (!matchSourceNames(report, filterData.sourceNames)) {
222             return null;
223         }
224
225         Collection<MeasInfoList> filteredMeasObjs = createMeasObjInstIds(report, filterData);
226         if (filteredMeasObjs.isEmpty()) {
227             return null;
228         }
229         MeasDataCollection measDataCollection = report.event.getPerf3gppFields().getMeasDataCollection().toBuilder() //
230                 .measInfoList(filteredMeasObjs) //
231                 .build();
232
233         Perf3gppFields perf3gppFields =
234                 report.event.getPerf3gppFields().toBuilder().measDataCollection(measDataCollection) //
235                         .build();
236         Event event = report.event.toBuilder() //
237                 .perf3gppFields(perf3gppFields) //
238                 .build();
239
240         return report.toBuilder() //
241                 .event(event) //
242                 .build();
243     }
244
245     private boolean isContainedInAny(String aString, Collection<String> collection) {
246         for (String s : collection) {
247             if (StringUtils.contains(aString, s) == Boolean.TRUE) {
248                 return true;
249             }
250         }
251         return false;
252     }
253
254     private boolean isMeasResultMatch(MeasResult measResult, MeasTypes measTypes,
255             FilterData.MeasTypeSpec measTypesSpec) {
256         String measType = measTypes.getMeasType(measResult.getP());
257         return measTypesSpec.measTypes.isEmpty() || measTypesSpec.measTypes.contains(measType);
258     }
259
260     private Collection<MeasResult> createMeasResults(Collection<MeasResult> oldMeasResults, MeasTypes measTypes,
261             FilterData.MeasTypeSpec measTypesSpec) {
262         Collection<MeasResult> newMeasResults = new ArrayList<>();
263
264         for (MeasResult measResult : oldMeasResults) {
265             if (isMeasResultMatch(measResult, measTypes, measTypesSpec)) {
266                 newMeasResults.add(measResult.toBuilder().build());
267             }
268         }
269         return newMeasResults;
270     }
271
272     private boolean isMeasInstIdMatch(String measObjInstId, FilterData filter) {
273         return filter.measObjInstIds.isEmpty() || isContainedInAny(measObjInstId, filter.measObjInstIds);
274     }
275
276     private String managedObjectClass(String distinguishedName) {
277         int lastRdn = distinguishedName.lastIndexOf(",");
278         if (lastRdn == -1) {
279             return "";
280         }
281         int lastEqualChar = distinguishedName.indexOf("=", lastRdn);
282         if (lastEqualChar == -1) {
283             return "";
284         }
285         return distinguishedName.substring(lastRdn + 1, lastEqualChar);
286     }
287
288     private FilterData.MeasTypeSpec getMeasTypeSpec(String measObjInstId, FilterData filter) {
289         String measObjClass = managedObjectClass(measObjInstId);
290         return filter.getMeasTypeSpec(measObjClass);
291     }
292
293     private MeasValuesList createMeasValuesList(MeasValuesList oldMeasValues, MeasTypes measTypes, FilterData filter) {
294         FilterData.MeasTypeSpec measTypesSpec = getMeasTypeSpec(oldMeasValues.getMeasObjInstId(), filter);
295         if (measTypesSpec == null) {
296             return MeasValuesList.empty();
297         }
298
299         if (!isMeasInstIdMatch(oldMeasValues.getMeasObjInstId(), filter)) {
300             return MeasValuesList.empty();
301         }
302
303         Collection<MeasResult> newResults = createMeasResults(oldMeasValues.getMeasResults(), measTypes, measTypesSpec);
304         return oldMeasValues.toBuilder() //
305                 .measResults(newResults) //
306                 .build();
307     }
308
309     private MeasTypes createMeasTypes(Collection<MeasValuesList> newMeasValues, MeasTypes oldMMeasTypes) {
310         MeasTypesIndexed newMeasTypes = new MeasTypesIndexed();
311         for (MeasValuesList l : newMeasValues) {
312             for (MeasResult r : l.getMeasResults()) {
313                 String measTypeName = oldMMeasTypes.getMeasType(r.getP());
314                 int newP = newMeasTypes.addP(measTypeName);
315                 r.setP(newP);
316             }
317         }
318         return newMeasTypes;
319     }
320
321     private MeasInfoList createMeasInfoList(MeasInfoList oldMeasInfoList, FilterData filter) {
322
323         Collection<MeasValuesList> measValueLists = new ArrayList<>();
324         for (MeasValuesList oldValues : oldMeasInfoList.getMeasValuesList()) {
325             MeasValuesList newMeasValues = createMeasValuesList(oldValues, oldMeasInfoList.getMeasTypes(), filter);
326             if (!newMeasValues.isEmpty()) {
327                 measValueLists.add(newMeasValues);
328             }
329         }
330
331         MeasTypes newMeasTypes = createMeasTypes(measValueLists, oldMeasInfoList.getMeasTypes());
332
333         return oldMeasInfoList.toBuilder() //
334                 .measTypes(newMeasTypes).measValuesList(measValueLists) //
335                 .build();
336
337     }
338
339     private boolean matchMeasuredEntityDns(PmReport report, FilterData filter) {
340         return filter.measuredEntityDns.isEmpty()
341                 || this.isContainedInAny(report.event.getPerf3gppFields().getMeasDataCollection().getMeasuredEntityDn(),
342                         filter.measuredEntityDns);
343     }
344
345     private Collection<MeasInfoList> createMeasObjInstIds(PmReport report, FilterData filter) {
346         Collection<MeasInfoList> newList = new ArrayList<>();
347         if (!matchMeasuredEntityDns(report, filter)) {
348             return newList;
349         }
350         for (MeasInfoList oldMeasInfoList : report.event.getPerf3gppFields().getMeasDataCollection()
351                 .getMeasInfoList()) {
352             MeasInfoList l = createMeasInfoList(oldMeasInfoList, filter);
353             if (!l.getMeasValuesList().isEmpty()) {
354                 newList.add(l);
355             }
356         }
357         return newList;
358     }
359
360     private boolean matchSourceNames(PmReport report, Collection<String> sourceNames) {
361         return sourceNames.isEmpty() || sourceNames.contains(report.event.getCommonEventHeader().getSourceName());
362     }
363
364 }