2 * ========================LICENSE_START=================================
5 * Copyright (C) 2023 Nordix Foundation
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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===================================
21 package org.oran.pmproducer.filter;
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;
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;
46 public class PmReportFilter {
47 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
49 private static com.google.gson.Gson gson = new com.google.gson.GsonBuilder() //
50 .disableHtmlEscaping() //
51 .excludeFieldsWithoutExposeAnnotation() //
54 // excludeFieldsWithoutExposeAnnotation is not needed when parsing and this is a
56 private static com.google.gson.Gson gsonParse = new com.google.gson.GsonBuilder() //
57 .disableHtmlEscaping() //
61 private final FilterData filterData;
64 public static class FilterData {
66 public static class MeasTypeSpec {
67 static MeasTypeSpec empty = new MeasTypeSpec();
69 static MeasTypeSpec empty() {
74 String measuredObjClass;
77 final Set<String> measTypes = new HashSet<>();
80 public boolean equals(Object obj) {
81 return measuredObjClass.equals(obj);
85 public int hashCode() {
86 return measuredObjClass.hashCode();
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<>();
95 public void addMeasTypes(String measObjClass, String... measTypes) {
96 MeasTypeSpec spec = this.findMeasTypeSpec(measObjClass);
98 spec = new MeasTypeSpec();
99 spec.measuredObjClass = measObjClass;
100 this.measTypeSpecs.add(spec);
102 for (String measType : measTypes) {
103 spec.measTypes.add(measType);
107 public void addMeasTypes(String measObjClass, Collection<String> measTypes) {
108 for (String measType : measTypes) {
109 addMeasTypes(measObjClass, measType);
114 String pmRopStartTime;
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);
126 public MeasTypeSpec getMeasTypeSpec(String measuredObjClass) {
127 if (measTypeSpecs.isEmpty()) {
128 return MeasTypeSpec.empty();
130 return findMeasTypeSpec(measuredObjClass);
133 private MeasTypeSpec findMeasTypeSpec(String measuredObjClass) {
134 for (MeasTypeSpec t : this.measTypeSpecs) {
135 if (t.measuredObjClass.equals(measuredObjClass)) {
142 private void addAll(Collection<MeasTypeSpec> measTypes) {
143 for (MeasTypeSpec s : measTypes) {
144 addMeasTypes(s.getMeasuredObjClass(), s.getMeasTypes());
148 private void addAll(Set<String> source, Set<String> dst) {
149 if (source.isEmpty()) {
151 } else if (dst.isEmpty()) {
152 // Nothing, this means 'match all'
159 public static PmReport parse(String string) {
160 return gsonParse.fromJson(string, PmReport.class);
163 private static class MeasTypesIndexed extends PmReport.MeasTypes {
165 private Map<String, Integer> map = new HashMap<>();
167 public int addP(String measTypeName) {
168 Integer p = map.get(measTypeName);
172 sMeasTypesList.add(measTypeName);
173 this.map.put(measTypeName, sMeasTypesList.size());
174 return sMeasTypesList.size();
179 public PmReportFilter(FilterData filterData) {
180 this.filterData = filterData;
183 public FilteredData filter(DataFromTopic data) {
185 PmReport report = getPmReport(data);
187 if (report.event == null || report.event.getPerf3gppFields() == null) {
188 logger.warn("Received PM report with no perf3gppFields, ignored. {}", data);
189 return FilteredData.empty();
192 PmReport reportFiltered = filter(report, this.filterData);
193 if (reportFiltered == null) {
194 return FilteredData.empty();
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();
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())));
209 return data.getCachedPmReport();
214 * Updates the report based on the filter data.
218 * @return true if there is anything left in the report
220 private PmReport filter(PmReport report, FilterData filterData) {
221 if (!matchSourceNames(report, filterData.sourceNames)) {
225 Collection<MeasInfoList> filteredMeasObjs = createMeasObjInstIds(report, filterData);
226 if (filteredMeasObjs.isEmpty()) {
229 MeasDataCollection measDataCollection = report.event.getPerf3gppFields().getMeasDataCollection().toBuilder() //
230 .measInfoList(filteredMeasObjs) //
233 Perf3gppFields perf3gppFields =
234 report.event.getPerf3gppFields().toBuilder().measDataCollection(measDataCollection) //
236 Event event = report.event.toBuilder() //
237 .perf3gppFields(perf3gppFields) //
240 return report.toBuilder() //
245 private boolean isContainedInAny(String aString, Collection<String> collection) {
246 for (String s : collection) {
247 if (StringUtils.contains(aString, s) == Boolean.TRUE) {
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);
260 private Collection<MeasResult> createMeasResults(Collection<MeasResult> oldMeasResults, MeasTypes measTypes,
261 FilterData.MeasTypeSpec measTypesSpec) {
262 Collection<MeasResult> newMeasResults = new ArrayList<>();
264 for (MeasResult measResult : oldMeasResults) {
265 if (isMeasResultMatch(measResult, measTypes, measTypesSpec)) {
266 newMeasResults.add(measResult.toBuilder().build());
269 return newMeasResults;
272 private boolean isMeasInstIdMatch(String measObjInstId, FilterData filter) {
273 return filter.measObjInstIds.isEmpty() || isContainedInAny(measObjInstId, filter.measObjInstIds);
276 private String managedObjectClass(String distinguishedName) {
277 int lastRdn = distinguishedName.lastIndexOf(",");
281 int lastEqualChar = distinguishedName.indexOf("=", lastRdn);
282 if (lastEqualChar == -1) {
285 return distinguishedName.substring(lastRdn + 1, lastEqualChar);
288 private FilterData.MeasTypeSpec getMeasTypeSpec(String measObjInstId, FilterData filter) {
289 String measObjClass = managedObjectClass(measObjInstId);
290 return filter.getMeasTypeSpec(measObjClass);
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();
299 if (!isMeasInstIdMatch(oldMeasValues.getMeasObjInstId(), filter)) {
300 return MeasValuesList.empty();
303 Collection<MeasResult> newResults = createMeasResults(oldMeasValues.getMeasResults(), measTypes, measTypesSpec);
304 return oldMeasValues.toBuilder() //
305 .measResults(newResults) //
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);
321 private MeasInfoList createMeasInfoList(MeasInfoList oldMeasInfoList, FilterData filter) {
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);
331 MeasTypes newMeasTypes = createMeasTypes(measValueLists, oldMeasInfoList.getMeasTypes());
333 return oldMeasInfoList.toBuilder() //
334 .measTypes(newMeasTypes).measValuesList(measValueLists) //
339 private boolean matchMeasuredEntityDns(PmReport report, FilterData filter) {
340 return filter.measuredEntityDns.isEmpty()
341 || this.isContainedInAny(report.event.getPerf3gppFields().getMeasDataCollection().getMeasuredEntityDn(),
342 filter.measuredEntityDns);
345 private Collection<MeasInfoList> createMeasObjInstIds(PmReport report, FilterData filter) {
346 Collection<MeasInfoList> newList = new ArrayList<>();
347 if (!matchMeasuredEntityDns(report, filter)) {
350 for (MeasInfoList oldMeasInfoList : report.event.getPerf3gppFields().getMeasDataCollection()
351 .getMeasInfoList()) {
352 MeasInfoList l = createMeasInfoList(oldMeasInfoList, filter);
353 if (!l.getMeasValuesList().isEmpty()) {
360 private boolean matchSourceNames(PmReport report, Collection<String> sourceNames) {
361 return sourceNames.isEmpty() || sourceNames.contains(report.event.getCommonEventHeader().getSourceName());