Creating PM-producer
[nonrtric/plt/ranpm.git] / pmproducer / src / main / java / org / oran / pmproducer / filter / PmReportFilter.java
diff --git a/pmproducer/src/main/java/org/oran/pmproducer/filter/PmReportFilter.java b/pmproducer/src/main/java/org/oran/pmproducer/filter/PmReportFilter.java
new file mode 100644 (file)
index 0000000..26bdf58
--- /dev/null
@@ -0,0 +1,364 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2023 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oran.pmproducer.filter;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import org.oran.pmproducer.filter.PmReport.Event;
+import org.oran.pmproducer.filter.PmReport.MeasDataCollection;
+import org.oran.pmproducer.filter.PmReport.MeasInfoList;
+import org.oran.pmproducer.filter.PmReport.MeasResult;
+import org.oran.pmproducer.filter.PmReport.MeasTypes;
+import org.oran.pmproducer.filter.PmReport.MeasValuesList;
+import org.oran.pmproducer.filter.PmReport.Perf3gppFields;
+import org.oran.pmproducer.tasks.TopicListener.DataFromTopic;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.util.StringUtils;
+
+public class PmReportFilter {
+    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    private static com.google.gson.Gson gson = new com.google.gson.GsonBuilder() //
+            .disableHtmlEscaping() //
+            .excludeFieldsWithoutExposeAnnotation() //
+            .create();
+
+    // excludeFieldsWithoutExposeAnnotation is not needed when parsing and this is a
+    // bit quicker
+    private static com.google.gson.Gson gsonParse = new com.google.gson.GsonBuilder() //
+            .disableHtmlEscaping() //
+            .create();
+
+    @Getter
+    private final FilterData filterData;
+
+    @Getter
+    public static class FilterData {
+
+        public static class MeasTypeSpec {
+            static MeasTypeSpec empty = new MeasTypeSpec();
+
+            static MeasTypeSpec empty() {
+                return empty;
+            }
+
+            @Getter
+            String measuredObjClass;
+
+            @Getter
+            final Set<String> measTypes = new HashSet<>();
+
+            @Override
+            public boolean equals(Object obj) {
+                return measuredObjClass.equals(obj);
+            }
+
+            @Override
+            public int hashCode() {
+                return measuredObjClass.hashCode();
+            }
+        }
+
+        final Set<String> sourceNames = new HashSet<>();
+        final Set<String> measObjInstIds = new HashSet<>();
+        final Collection<MeasTypeSpec> measTypeSpecs = new ArrayList<>();
+        final Set<String> measuredEntityDns = new HashSet<>();
+
+        public void addMeasTypes(String measObjClass, String... measTypes) {
+            MeasTypeSpec spec = this.findMeasTypeSpec(measObjClass);
+            if (spec == null) {
+                spec = new MeasTypeSpec();
+                spec.measuredObjClass = measObjClass;
+                this.measTypeSpecs.add(spec);
+            }
+            for (String measType : measTypes) {
+                spec.measTypes.add(measType);
+            }
+        }
+
+        public void addMeasTypes(String measObjClass, Collection<String> measTypes) {
+            for (String measType : measTypes) {
+                addMeasTypes(measObjClass, measType);
+            }
+        }
+
+        @Setter
+        String pmRopStartTime;
+
+        @Setter
+        String pmRopEndTime;
+
+        public void addAll(FilterData other) {
+            addAll(other.sourceNames, sourceNames);
+            addAll(other.measObjInstIds, measObjInstIds);
+            addAll(other.measTypeSpecs);
+            addAll(other.measuredEntityDns, measuredEntityDns);
+        }
+
+        public MeasTypeSpec getMeasTypeSpec(String measuredObjClass) {
+            if (measTypeSpecs.isEmpty()) {
+                return MeasTypeSpec.empty();
+            }
+            return findMeasTypeSpec(measuredObjClass);
+        }
+
+        private MeasTypeSpec findMeasTypeSpec(String measuredObjClass) {
+            for (MeasTypeSpec t : this.measTypeSpecs) {
+                if (t.measuredObjClass.equals(measuredObjClass)) {
+                    return t;
+                }
+            }
+            return null;
+        }
+
+        private void addAll(Collection<MeasTypeSpec> measTypes) {
+            for (MeasTypeSpec s : measTypes) {
+                addMeasTypes(s.getMeasuredObjClass(), s.getMeasTypes());
+            }
+        }
+
+        private void addAll(Set<String> source, Set<String> dst) {
+            if (source.isEmpty()) {
+                dst.clear();
+            } else if (dst.isEmpty()) {
+                // Nothing, this means 'match all'
+            } else {
+                dst.addAll(source);
+            }
+        }
+    }
+
+    public static PmReport parse(String string) {
+        return gsonParse.fromJson(string, PmReport.class);
+    }
+
+    private static class MeasTypesIndexed extends PmReport.MeasTypes {
+
+        private Map<String, Integer> map = new HashMap<>();
+
+        public int addP(String measTypeName) {
+            Integer p = map.get(measTypeName);
+            if (p != null) {
+                return p;
+            } else {
+                sMeasTypesList.add(measTypeName);
+                this.map.put(measTypeName, sMeasTypesList.size());
+                return sMeasTypesList.size();
+            }
+        }
+    }
+
+    public PmReportFilter(FilterData filterData) {
+        this.filterData = filterData;
+    }
+
+    public FilteredData filter(DataFromTopic data) {
+        try {
+            PmReport report = getPmReport(data);
+
+            if (report.event == null || report.event.getPerf3gppFields() == null) {
+                logger.warn("Received PM report with no perf3gppFields, ignored. {}", data);
+                return FilteredData.empty();
+            }
+
+            PmReport reportFiltered = filter(report, this.filterData);
+            if (reportFiltered == null) {
+                return FilteredData.empty();
+            }
+            return new FilteredData(data.infoTypeId, data.key, gson.toJson(reportFiltered).getBytes());
+        } catch (Exception e) {
+            logger.warn("Could not parse PM data. {}, reason: {}", data, e.getMessage());
+            return FilteredData.empty();
+        }
+    }
+
+    @SuppressWarnings("java:S2445") // "data" is a method parameter, and should not be used for synchronization.
+    private PmReport getPmReport(DataFromTopic data) {
+        synchronized (data) {
+            if (data.getCachedPmReport() == null) {
+                data.setCachedPmReport(parse((data.valueAsString())));
+            }
+            return data.getCachedPmReport();
+        }
+    }
+
+    /**
+     * Updates the report based on the filter data.
+     *
+     * @param report
+     * @param filterData
+     * @return true if there is anything left in the report
+     */
+    private PmReport filter(PmReport report, FilterData filterData) {
+        if (!matchSourceNames(report, filterData.sourceNames)) {
+            return null;
+        }
+
+        Collection<MeasInfoList> filteredMeasObjs = createMeasObjInstIds(report, filterData);
+        if (filteredMeasObjs.isEmpty()) {
+            return null;
+        }
+        MeasDataCollection measDataCollection = report.event.getPerf3gppFields().getMeasDataCollection().toBuilder() //
+                .measInfoList(filteredMeasObjs) //
+                .build();
+
+        Perf3gppFields perf3gppFields =
+                report.event.getPerf3gppFields().toBuilder().measDataCollection(measDataCollection) //
+                        .build();
+        Event event = report.event.toBuilder() //
+                .perf3gppFields(perf3gppFields) //
+                .build();
+
+        return report.toBuilder() //
+                .event(event) //
+                .build();
+    }
+
+    private boolean isContainedInAny(String aString, Collection<String> collection) {
+        for (String s : collection) {
+            if (StringUtils.contains(aString, s) == Boolean.TRUE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isMeasResultMatch(MeasResult measResult, MeasTypes measTypes,
+            FilterData.MeasTypeSpec measTypesSpec) {
+        String measType = measTypes.getMeasType(measResult.getP());
+        return measTypesSpec.measTypes.isEmpty() || measTypesSpec.measTypes.contains(measType);
+    }
+
+    private Collection<MeasResult> createMeasResults(Collection<MeasResult> oldMeasResults, MeasTypes measTypes,
+            FilterData.MeasTypeSpec measTypesSpec) {
+        Collection<MeasResult> newMeasResults = new ArrayList<>();
+
+        for (MeasResult measResult : oldMeasResults) {
+            if (isMeasResultMatch(measResult, measTypes, measTypesSpec)) {
+                newMeasResults.add(measResult.toBuilder().build());
+            }
+        }
+        return newMeasResults;
+    }
+
+    private boolean isMeasInstIdMatch(String measObjInstId, FilterData filter) {
+        return filter.measObjInstIds.isEmpty() || isContainedInAny(measObjInstId, filter.measObjInstIds);
+    }
+
+    private String managedObjectClass(String distinguishedName) {
+        int lastRdn = distinguishedName.lastIndexOf(",");
+        if (lastRdn == -1) {
+            return "";
+        }
+        int lastEqualChar = distinguishedName.indexOf("=", lastRdn);
+        if (lastEqualChar == -1) {
+            return "";
+        }
+        return distinguishedName.substring(lastRdn + 1, lastEqualChar);
+    }
+
+    private FilterData.MeasTypeSpec getMeasTypeSpec(String measObjInstId, FilterData filter) {
+        String measObjClass = managedObjectClass(measObjInstId);
+        return filter.getMeasTypeSpec(measObjClass);
+    }
+
+    private MeasValuesList createMeasValuesList(MeasValuesList oldMeasValues, MeasTypes measTypes, FilterData filter) {
+        FilterData.MeasTypeSpec measTypesSpec = getMeasTypeSpec(oldMeasValues.getMeasObjInstId(), filter);
+        if (measTypesSpec == null) {
+            return MeasValuesList.empty();
+        }
+
+        if (!isMeasInstIdMatch(oldMeasValues.getMeasObjInstId(), filter)) {
+            return MeasValuesList.empty();
+        }
+
+        Collection<MeasResult> newResults = createMeasResults(oldMeasValues.getMeasResults(), measTypes, measTypesSpec);
+        return oldMeasValues.toBuilder() //
+                .measResults(newResults) //
+                .build();
+    }
+
+    private MeasTypes createMeasTypes(Collection<MeasValuesList> newMeasValues, MeasTypes oldMMeasTypes) {
+        MeasTypesIndexed newMeasTypes = new MeasTypesIndexed();
+        for (MeasValuesList l : newMeasValues) {
+            for (MeasResult r : l.getMeasResults()) {
+                String measTypeName = oldMMeasTypes.getMeasType(r.getP());
+                int newP = newMeasTypes.addP(measTypeName);
+                r.setP(newP);
+            }
+        }
+        return newMeasTypes;
+    }
+
+    private MeasInfoList createMeasInfoList(MeasInfoList oldMeasInfoList, FilterData filter) {
+
+        Collection<MeasValuesList> measValueLists = new ArrayList<>();
+        for (MeasValuesList oldValues : oldMeasInfoList.getMeasValuesList()) {
+            MeasValuesList newMeasValues = createMeasValuesList(oldValues, oldMeasInfoList.getMeasTypes(), filter);
+            if (!newMeasValues.isEmpty()) {
+                measValueLists.add(newMeasValues);
+            }
+        }
+
+        MeasTypes newMeasTypes = createMeasTypes(measValueLists, oldMeasInfoList.getMeasTypes());
+
+        return oldMeasInfoList.toBuilder() //
+                .measTypes(newMeasTypes).measValuesList(measValueLists) //
+                .build();
+
+    }
+
+    private boolean matchMeasuredEntityDns(PmReport report, FilterData filter) {
+        return filter.measuredEntityDns.isEmpty()
+                || this.isContainedInAny(report.event.getPerf3gppFields().getMeasDataCollection().getMeasuredEntityDn(),
+                        filter.measuredEntityDns);
+    }
+
+    private Collection<MeasInfoList> createMeasObjInstIds(PmReport report, FilterData filter) {
+        Collection<MeasInfoList> newList = new ArrayList<>();
+        if (!matchMeasuredEntityDns(report, filter)) {
+            return newList;
+        }
+        for (MeasInfoList oldMeasInfoList : report.event.getPerf3gppFields().getMeasDataCollection()
+                .getMeasInfoList()) {
+            MeasInfoList l = createMeasInfoList(oldMeasInfoList, filter);
+            if (!l.getMeasValuesList().isEmpty()) {
+                newList.add(l);
+            }
+        }
+        return newList;
+    }
+
+    private boolean matchSourceNames(PmReport report, Collection<String> sourceNames) {
+        return sourceNames.isEmpty() || sourceNames.contains(report.event.getCommonEventHeader().getSourceName());
+    }
+
+}