Map ORAN alarm-notif to VES fault
[ccsdk/features.git] / sdnr / wt / devicemanager / provider / src / main / java / org / onap / ccsdk / features / sdnr / wt / devicemanager / impl / util / NotificationProxyParserImpl.java
diff --git a/sdnr/wt/devicemanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/NotificationProxyParserImpl.java b/sdnr/wt/devicemanager/provider/src/main/java/org/onap/ccsdk/features/sdnr/wt/devicemanager/impl/util/NotificationProxyParserImpl.java
new file mode 100644 (file)
index 0000000..802c05d
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP : ccsdk features
+ * ================================================================================
+ * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
+ * All rights reserved.
+ * ================================================================================
+ * 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.onap.ccsdk.features.sdnr.wt.devicemanager.impl.util;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.onap.ccsdk.features.sdnr.wt.devicemanager.service.NotificationProxyParser;
+import org.opendaylight.yangtools.concepts.Identifier;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.EventInstantAware;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Notification;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NotificationProxyParserImpl implements NotificationProxyParser {
+
+
+    /*
+     *  Converter of TR069 notifications to VES key, value hashmap.
+     *  Notifications are received as cascade if proxy object.
+     *  References: https://stackoverflow.com/questions/19633534/what-is-com-sun-proxy-proxy
+     *
+     *  Attributes are provided by getters starting with "get", "is", "key".
+     *  Proxy received: "com.sun.proxy.$ProxyNN". NN is a number.
+     *
+     *  Example result:
+     *
+     * Expected output via VES in JSON
+     *    {
+     *      "event": {
+     *         "commonEventHeader": {
+     *            "domain": "notification",
+     *            "eventId": "ABCD",
+     *            "eventName": "Notification_LTE_Enterprise_C-RANSC_Cntrl-ACME",
+     *            "eventType": "TR069_RAN_notification",
+     *            "sequence": 0,
+     *            "priority": "High",
+     *            "reportingEntityId": "0005B942CDB4",
+     *            "reportingEntityName": "ABCD",
+     *            "sourceId": "0005B942CDB4",
+     *            "sourceName": "ABCD",
+     *            "startEpochMicrosec": 1569579510211,
+     *            "lastEpochMicrosec": 1569579510211,
+     *            "nfcNamingCode": "",
+     *            "nfNamingCode": "",
+     *            "nfVendorName": "",
+     *            "timeZoneOffset": "+00:00",
+     *            "version": "4.0.1",
+     *            "vesEventListenerVersion": "7.0.1"
+     *         },
+     *         "notificationFields": {
+     *         "arrayOfNamedHashMap": [
+     *          {
+     *           "name": "VALUECHANGE",
+     *           "hashMap": {
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/serial-number": "0005B94238A0",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/software-version": "4.3.00.244",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/hardware-version": "1",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/provisioning-code": "",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer": "ACME",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/product-class": "LTE_Enterprise_C-RANSC_Cntrl",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer-oui": "0005B9",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/index": "1",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/rf-tx-status": "false",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/op-state": "true",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/index": "2",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/rf-tx-status": "false",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/op-state": "true",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/cell-config/lte/ran/rf/phy-cell-id": "201",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-url": "http://10.220.68.2/acscall",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/parameter-key": "none",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-password": "password",
+     *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-username": "0005B9-LTE_Enterprise_C-RANSC_Cntrl-0005B94238A0"
+     *           },
+     *          }
+     *         ],
+     *         "changeContact": "",
+     *         "changeIdentifier": "SessionID",
+     *         "changeType": "ValueChange",
+     *         "newState": "",
+     *         "oldState": "",
+     *         "stateInterface": "",
+     *         "notificationFieldsVersion": "2.0",
+     *         }
+     *      }
+     *    }
+     *
+     */
+        private static final Logger log = LoggerFactory.getLogger(NotificationProxyParserImpl.class);
+        private Notification notification;
+
+        @Override
+        public HashMap<String, String> parseNotificationProxy(Notification notification)
+        /*throws ORanNotificationMapperException*/ {
+
+            try {
+                return extractFields(notification);
+            } catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException
+                    | InvocationTargetException e) {
+                //throw new ORanNotificationMapperException("Mapping/JSON Creation problem", e);
+                log.info("Exception in performMapping method {}",e);
+                return null;
+            }
+
+        }
+
+        private HashMap<String, String> extractFields(Object o) throws IllegalAccessException, IllegalArgumentException,
+                InvocationTargetException, NoSuchFieldException, SecurityException {
+            String start = "/notification/" + getExtendedInterfaceName(Notification.class, o) + getXmlNameSpace(o);
+            return recurseExtractData(o, start, 0, new HashMap<String, String>());
+        }
+
+        private HashMap<String, String> recurseExtractData(Object o, String namePath, int level,
+                HashMap<String, String> result)
+                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+            log.debug("In recurseExtractData - {} {} {}", namePath, level, log.isTraceEnabled() ? result : result.size());
+            if (level > 20) {
+                log.warn("Level to deep protection ended the recusive loop.");
+            } else {
+                if (o != null) {
+                    Class<?> classz = o.getClass();
+                    //notification/VALUECHANGE$$$eventInstantAware[@xmlns=urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notificationon] 0 {}
+                    //org.opendaylight.yang.gen.v1.urn.org.onap.ccsdk.features.sdnr.northbound.onecell.notification.rev200622.VALUECHANGE$$$eventInstantAware
+                    //if (Proxy.isProxyClass(classz)) {
+                    handleInterface(classz, o, namePath, level, result);
+                    //}
+                } else {
+                    log.warn("Null not expected here.");
+                }
+            }
+            return result;
+        }
+
+        private void handleInterface(Class<?> clazz, Object o, String namePath, int level, HashMap<String, String> result)
+                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+            log.debug("In extract Interface {}", clazz);
+            if (clazz == null) {
+                log.warn("Loop with null class");
+                return;
+            }
+            if (level > 20) {
+                log.warn("Level to deep protection ended the recusive loop.");
+            }
+            if (clazz.getName().contentEquals("org.opendaylight.mdsal.binding.dom.codec.impl.AugmentableCodecDataObject")) {
+                log.trace("Leave AugmentableCodecDataObject");
+                return;
+            }
+
+            Method[] methods = clazz.getDeclaredMethods();
+            if (methods != null) {
+                for (Method method : methods) {
+                    String methodName = method.getName();
+                    log.trace("Method {}", methodName);
+                    if (methodName.startsWith("get")) {
+                        if (!methodName.equals("getImplementedInterface")) {
+                            handleGetterValue(method, methodName.substring(3), namePath, o, level, result);
+                        }
+                    } else if (methodName.startsWith("is")) {
+                        handleGetterValue(method, methodName.substring(2), namePath, o, level, result);
+                    } else if (methodName.equals("key")) {
+                        handleGetterValue(method, methodName, namePath, o, level, result);
+                    }
+                }
+            }
+            Class<?> sc = clazz.getSuperclass();  //Sodium
+            log.trace("Superclass is - {}", sc);
+            if (sc != null && !(sc.getName().contains("java.lang.reflect.Proxy")) && !Proxy.isProxyClass(sc)) {
+                handleInterface(sc, o, namePath, level + 1, result);
+            }
+        }
+
+        private void handleGetterValue(Method method, String name, String namePath, Object o, int level,
+                HashMap<String, String> result)
+                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+            log.debug("Begin: {}-{}-{}-{}", method.getName(), name, namePath, level);
+            if (!method.isAccessible()) {
+                method.setAccessible(true);
+            }
+            Object value = method.invoke(o);
+            namePath += "/" + convertCamelToKebabCase(name);
+            log.trace("Namepath {}", namePath);
+            if (value != null) {
+                Class<?> type = value.getClass();
+                log.trace("Class {}", type.getSimpleName());
+                if (List.class.isAssignableFrom(type)) {
+                    int idx = 0;
+                    String keyString;
+                    for (Object listObject : (List<?>) value) {
+                        if (listObject != null) {
+                            if (Identifiable.class.isAssignableFrom(listObject.getClass())) {
+                                keyString = getKeyString((Identifiable<?>) listObject);
+                            } else {
+                                keyString = String.valueOf(idx);
+                            }
+                            recurseExtractData(listObject, namePath + "[" + keyString + "]", level + 1, result);
+                        } else {
+                            log.warn("Null value received {} {} {}", namePath, idx, name);
+                        }
+                        idx++;
+                    }
+                } else if (DataObject.class.isAssignableFrom(type)) {
+                    recurseExtractData(value, namePath, level + 1, result);
+                } else if (Proxy.isProxyClass(type)) {
+                    recurseExtractData(value, namePath, level + 1, result);
+                } else if (Identifier.class.isAssignableFrom(type)) {
+                    //don't put the key
+                } else {
+                    result.put(namePath, value.toString());
+                }
+            } else {
+                log.trace("Null value");
+            }
+        }
+
+        private String getExtendedInterfaceName(Class<?> assignableClazz, Object o) {
+            Class<?> interfaces[] = o.getClass().getInterfaces();
+            for (Class<?> oneInterface : interfaces) {
+                log.trace("In getExtendedInterfaceName, oneInterface = {}", oneInterface.getClass().getName());
+                if (assignableClazz.isAssignableFrom(oneInterface)) {
+                    return oneInterface.getSimpleName().contains("eventInstantAware")?oneInterface.getSimpleName().substring(0, oneInterface.getSimpleName().indexOf("$")):oneInterface.getSimpleName();
+                }
+            }
+            log.trace("In getExtendedInterfaceName, o.getClass().getName() = {}", o.getClass().getName());
+            return o.getClass().getSimpleName().contains("eventInstantAware")?o.getClass().getSimpleName().substring(0, o.getClass().getSimpleName().indexOf("$")):o.getClass().getSimpleName();
+        }
+
+        private String getXmlNameSpace(Object o)
+                throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+            Field f = o.getClass().getField("QNAME");
+            Object couldQName = f.get(o);
+            if (couldQName instanceof QName) {
+                QName qname = (QName) couldQName;
+                return "[@xmlns=" + qname.getNamespace().toString() + "]";
+            }
+            return "";
+        }
+
+        /*private String convertCamelToKebabCase(String camel) {
+            KebabCaseStrategy kbCase = new KebabCaseStrategy();
+            return kbCase.translate(camel);
+            //return camel.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase();
+        } */
+
+        /**
+         * @param input string in Camel Case
+         * @return String in Kebab case
+         * Inspiration from KebabCaseStrategy class of com.fasterxml.jackson.databind with an additional condition to handle numbers as well
+         * Using QNAME would have been a more fool proof solution, however it can lead to performance problems due to usage of Java reflection
+         */
+        private String convertCamelToKebabCase(String input)
+        {
+            if (input == null) return input; // garbage in, garbage out
+            int length = input.length();
+            if (length == 0) {
+                return input;
+            }
+
+            StringBuilder result = new StringBuilder(length + (length >> 1));
+
+            int upperCount = 0;
+
+            for (int i = 0; i < length; ++i) {
+                char ch = input.charAt(i);
+                char lc = Character.toLowerCase(ch);
+
+                if (lc == ch) { // lower-case letter means we can get new word
+                    // but need to check for multi-letter upper-case (acronym), where assumption
+                    // is that the last upper-case char is start of a new word
+                    if ((upperCount > 1)  ){
+                        // so insert hyphen before the last character now
+                        result.insert(result.length() - 1, '-');
+                    } else if ((upperCount == 1) && Character.isDigit(ch) && i != length-1) {
+                        result.append('-');
+                    }
+                    upperCount = 0;
+                } else {
+                    // Otherwise starts new word, unless beginning of string
+                    if ((upperCount == 0) && (i > 0)) {
+                        result.append('-');
+                    }
+                    ++upperCount;
+                }
+                result.append(lc);
+            }
+            return result.toString();
+        }
+
+        /**
+         * Key format like this: "FapServiceKey{_index=2}"
+         *
+         * @return
+         */
+        private String getKeyString(Identifiable<?> indentifiableObject) {
+            String keyString = (indentifiableObject.key()).toString();
+            int start = keyString.indexOf("=") + 1;
+            int end = keyString.length() - 1;
+            if (start > 0 && start < end)
+                return keyString.substring(keyString.indexOf("=") + 1, keyString.length() - 1);
+            else
+                throw new IllegalArgumentException("indentifiable object without key");
+        }
+
+        public Instant getTime(Notification notification) {
+            @NonNull
+            Instant time;
+            if (notification instanceof EventInstantAware) { // If notification class extends/implements the EventInstantAware
+                time = ((EventInstantAware) notification).eventInstant();
+                log.info("Event time {}", time);
+            } else {
+                time = Instant.now();
+                log.info("Defaulting to actual time of processing the notification - {}", time);
+            }
+            return time;
+        }
+}