Adding source name to kafka headers
[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(reportFiltered.event.getCommonEventHeader().getSourceName(), data.infoTypeId,
197                     data.key, gson.toJson(reportFiltered).getBytes());
198         } catch (Exception e) {
199             logger.warn("Could not parse PM data. {}, reason: {}", data, e.getMessage());
200             return FilteredData.empty();
201         }
202     }
203
204     @SuppressWarnings("java:S2445") // "data" is a method parameter, and should not be used for synchronization.
205     private PmReport getPmReport(DataFromTopic data) {
206         synchronized (data) {
207             if (data.getCachedPmReport() == null) {
208                 data.setCachedPmReport(parse((data.valueAsString())));
209             }
210             return data.getCachedPmReport();
211         }
212     }
213
214     /**
215      * Updates the report based on the filter data.
216      *
217      * @param report
218      * @param filterData
219      * @return true if there is anything left in the report
220      */
221     private PmReport filter(PmReport report, FilterData filterData) {
222         if (!matchSourceNames(report, filterData.sourceNames)) {
223             return null;
224         }
225
226         Collection<MeasInfoList> filteredMeasObjs = createMeasObjInstIds(report, filterData);
227         if (filteredMeasObjs.isEmpty()) {
228             return null;
229         }
230         MeasDataCollection measDataCollection = report.event.getPerf3gppFields().getMeasDataCollection().toBuilder() //
231                 .measInfoList(filteredMeasObjs) //
232                 .build();
233
234         Perf3gppFields perf3gppFields =
235                 report.event.getPerf3gppFields().toBuilder().measDataCollection(measDataCollection) //
236                         .build();
237         Event event = report.event.toBuilder() //
238                 .perf3gppFields(perf3gppFields) //
239                 .build();
240
241         return report.toBuilder() //
242                 .event(event) //
243                 .build();
244     }
245
246     private boolean isContainedInAny(String aString, Collection<String> collection) {
247         for (String s : collection) {
248             if (StringUtils.contains(aString, s) == Boolean.TRUE) {
249                 return true;
250             }
251         }
252         return false;
253     }
254
255     private boolean isMeasResultMatch(MeasResult measResult, MeasTypes measTypes,
256             FilterData.MeasTypeSpec measTypesSpec) {
257         String measType = measTypes.getMeasType(measResult.getP());
258         return measTypesSpec.measTypes.isEmpty() || measTypesSpec.measTypes.contains(measType);
259     }
260
261     private Collection<MeasResult> createMeasResults(Collection<MeasResult> oldMeasResults, MeasTypes measTypes,
262             FilterData.MeasTypeSpec measTypesSpec) {
263         Collection<MeasResult> newMeasResults = new ArrayList<>();
264
265         for (MeasResult measResult : oldMeasResults) {
266             if (isMeasResultMatch(measResult, measTypes, measTypesSpec)) {
267                 newMeasResults.add(measResult.toBuilder().build());
268             }
269         }
270         return newMeasResults;
271     }
272
273     private boolean isMeasInstIdMatch(String measObjInstId, FilterData filter) {
274         return filter.measObjInstIds.isEmpty() || isContainedInAny(measObjInstId, filter.measObjInstIds);
275     }
276
277     private String managedObjectClass(String distinguishedName) {
278         int lastRdn = distinguishedName.lastIndexOf(",");
279         if (lastRdn == -1) {
280             return "";
281         }
282         int lastEqualChar = distinguishedName.indexOf("=", lastRdn);
283         if (lastEqualChar == -1) {
284             return "";
285         }
286         return distinguishedName.substring(lastRdn + 1, lastEqualChar);
287     }
288
289     private FilterData.MeasTypeSpec getMeasTypeSpec(String measObjInstId, FilterData filter) {
290         String measObjClass = managedObjectClass(measObjInstId);
291         return filter.getMeasTypeSpec(measObjClass);
292     }
293
294     private MeasValuesList createMeasValuesList(MeasValuesList oldMeasValues, MeasTypes measTypes, FilterData filter) {
295         FilterData.MeasTypeSpec measTypesSpec = getMeasTypeSpec(oldMeasValues.getMeasObjInstId(), filter);
296         if (measTypesSpec == null) {
297             return MeasValuesList.empty();
298         }
299
300         if (!isMeasInstIdMatch(oldMeasValues.getMeasObjInstId(), filter)) {
301             return MeasValuesList.empty();
302         }
303
304         Collection<MeasResult> newResults = createMeasResults(oldMeasValues.getMeasResults(), measTypes, measTypesSpec);
305         return oldMeasValues.toBuilder() //
306                 .measResults(newResults) //
307                 .build();
308     }
309
310     private MeasTypes createMeasTypes(Collection<MeasValuesList> newMeasValues, MeasTypes oldMMeasTypes) {
311         MeasTypesIndexed newMeasTypes = new MeasTypesIndexed();
312         for (MeasValuesList l : newMeasValues) {
313             for (MeasResult r : l.getMeasResults()) {
314                 String measTypeName = oldMMeasTypes.getMeasType(r.getP());
315                 int newP = newMeasTypes.addP(measTypeName);
316                 r.setP(newP);
317             }
318         }
319         return newMeasTypes;
320     }
321
322     private MeasInfoList createMeasInfoList(MeasInfoList oldMeasInfoList, FilterData filter) {
323
324         Collection<MeasValuesList> measValueLists = new ArrayList<>();
325         for (MeasValuesList oldValues : oldMeasInfoList.getMeasValuesList()) {
326             MeasValuesList newMeasValues = createMeasValuesList(oldValues, oldMeasInfoList.getMeasTypes(), filter);
327             if (!newMeasValues.isEmpty()) {
328                 measValueLists.add(newMeasValues);
329             }
330         }
331
332         MeasTypes newMeasTypes = createMeasTypes(measValueLists, oldMeasInfoList.getMeasTypes());
333
334         return oldMeasInfoList.toBuilder() //
335                 .measTypes(newMeasTypes).measValuesList(measValueLists) //
336                 .build();
337
338     }
339
340     private boolean matchMeasuredEntityDns(PmReport report, FilterData filter) {
341         return filter.measuredEntityDns.isEmpty()
342                 || this.isContainedInAny(report.event.getPerf3gppFields().getMeasDataCollection().getMeasuredEntityDn(),
343                         filter.measuredEntityDns);
344     }
345
346     private Collection<MeasInfoList> createMeasObjInstIds(PmReport report, FilterData filter) {
347         Collection<MeasInfoList> newList = new ArrayList<>();
348         if (!matchMeasuredEntityDns(report, filter)) {
349             return newList;
350         }
351         for (MeasInfoList oldMeasInfoList : report.event.getPerf3gppFields().getMeasDataCollection()
352                 .getMeasInfoList()) {
353             MeasInfoList l = createMeasInfoList(oldMeasInfoList, filter);
354             if (!l.getMeasValuesList().isEmpty()) {
355                 newList.add(l);
356             }
357         }
358         return newList;
359     }
360
361     private boolean matchSourceNames(PmReport report, Collection<String> sourceNames) {
362         return sourceNames.isEmpty() || sourceNames.contains(report.event.getCommonEventHeader().getSourceName());
363     }
364
365 }