2  * ============LICENSE_START=======================================================
 
   3  * ONAP : ccsdk features
 
   4  * ================================================================================
 
   5  * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
 
   7  * ================================================================================
 
   8  * Licensed under the Apache License, Version 2.0 (the "License");
 
   9  * you may not use this file except in compliance with the License.
 
  10  * You may obtain a copy of the License at
 
  12  *     http://www.apache.org/licenses/LICENSE-2.0
 
  14  * Unless required by applicable law or agreed to in writing, software
 
  15  * distributed under the License is distributed on an "AS IS" BASIS,
 
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  17  * See the License for the specific language governing permissions and
 
  18  * limitations under the License.
 
  19  * ============LICENSE_END=========================================================
 
  22 package org.onap.ccsdk.features.sdnr.wt.devicemanager.impl.util;
 
  25 import java.lang.reflect.Field;
 
  26 import java.lang.reflect.InvocationTargetException;
 
  27 import java.lang.reflect.Method;
 
  28 import java.lang.reflect.Proxy;
 
  29 import java.time.Instant;
 
  30 import java.util.HashMap;
 
  31 import java.util.List;
 
  32 import org.eclipse.jdt.annotation.NonNull;
 
  33 import org.onap.ccsdk.features.sdnr.wt.devicemanager.service.NotificationProxyParser;
 
  34 import org.opendaylight.yangtools.concepts.Identifier;
 
  35 import org.opendaylight.yangtools.yang.binding.DataObject;
 
  36 import org.opendaylight.yangtools.yang.binding.EventInstantAware;
 
  37 import org.opendaylight.yangtools.yang.binding.Identifiable;
 
  38 import org.opendaylight.yangtools.yang.binding.Notification;
 
  39 import org.opendaylight.yangtools.yang.common.QName;
 
  40 import org.slf4j.Logger;
 
  41 import org.slf4j.LoggerFactory;
 
  43 public class NotificationProxyParserImpl implements NotificationProxyParser {
 
  47      *  Converter of TR069 notifications to VES key, value hashmap.
 
  48      *  Notifications are received as cascade if proxy object.
 
  49      *  References: https://stackoverflow.com/questions/19633534/what-is-com-sun-proxy-proxy
 
  51      *  Attributes are provided by getters starting with "get", "is", "key".
 
  52      *  Proxy received: "com.sun.proxy.$ProxyNN". NN is a number.
 
  56      * Expected output via VES in JSON
 
  59      *         "commonEventHeader": {
 
  60      *            "domain": "notification",
 
  62      *            "eventName": "Notification_LTE_Enterprise_C-RANSC_Cntrl-ACME",
 
  63      *            "eventType": "TR069_RAN_notification",
 
  66      *            "reportingEntityId": "0005B942CDB4",
 
  67      *            "reportingEntityName": "ABCD",
 
  68      *            "sourceId": "0005B942CDB4",
 
  69      *            "sourceName": "ABCD",
 
  70      *            "startEpochMicrosec": 1569579510211,
 
  71      *            "lastEpochMicrosec": 1569579510211,
 
  72      *            "nfcNamingCode": "",
 
  75      *            "timeZoneOffset": "+00:00",
 
  77      *            "vesEventListenerVersion": "7.0.1"
 
  79      *         "notificationFields": {
 
  80      *         "arrayOfNamedHashMap": [
 
  82      *           "name": "VALUECHANGE",
 
  84      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/serial-number": "0005B94238A0",
 
  85      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/software-version": "4.3.00.244",
 
  86      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/hardware-version": "1",
 
  87      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/provisioning-code": "",
 
  88      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer": "ACME",
 
  89      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/product-class": "LTE_Enterprise_C-RANSC_Cntrl",
 
  90      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer-oui": "0005B9",
 
  91      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/index": "1",
 
  92      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/rf-tx-status": "false",
 
  93      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/op-state": "true",
 
  94      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/index": "2",
 
  95      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/rf-tx-status": "false",
 
  96      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/op-state": "true",
 
  97      *               "/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",
 
  98      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-url": "http://10.220.68.2/acscall",
 
  99      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/parameter-key": "none",
 
 100      *               "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-password": "password",
 
 101      *               "/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"
 
 105      *         "changeContact": "",
 
 106      *         "changeIdentifier": "SessionID",
 
 107      *         "changeType": "ValueChange",
 
 110      *         "stateInterface": "",
 
 111      *         "notificationFieldsVersion": "2.0",
 
 117         private static final Logger log = LoggerFactory.getLogger(NotificationProxyParserImpl.class);
 
 118         private Notification notification;
 
 121         public HashMap<String, String> parseNotificationProxy(Notification notification)
 
 122         /*throws ORanNotificationMapperException*/ {
 
 125                 return extractFields(notification);
 
 126             } catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException
 
 127                     | InvocationTargetException e) {
 
 128                 //throw new ORanNotificationMapperException("Mapping/JSON Creation problem", e);
 
 129                 log.info("Exception in performMapping method {}",e);
 
 135         private HashMap<String, String> extractFields(Object o) throws IllegalAccessException, IllegalArgumentException,
 
 136                 InvocationTargetException, NoSuchFieldException, SecurityException {
 
 137             String start = "/notification/" + getExtendedInterfaceName(Notification.class, o) + getXmlNameSpace(o);
 
 138             return recurseExtractData(o, start, 0, new HashMap<String, String>());
 
 141         private HashMap<String, String> recurseExtractData(Object o, String namePath, int level,
 
 142                 HashMap<String, String> result)
 
 143                 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 
 144             log.debug("In recurseExtractData - {} {} {}", namePath, level, log.isTraceEnabled() ? result : result.size());
 
 146                 log.warn("Level to deep protection ended the recusive loop.");
 
 149                     Class<?> classz = o.getClass();
 
 150                     //notification/VALUECHANGE$$$eventInstantAware[@xmlns=urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notificationon] 0 {}
 
 151                     //org.opendaylight.yang.gen.v1.urn.org.onap.ccsdk.features.sdnr.northbound.onecell.notification.rev200622.VALUECHANGE$$$eventInstantAware
 
 152                     //if (Proxy.isProxyClass(classz)) {
 
 153                     handleInterface(classz, o, namePath, level, result);
 
 156                     log.warn("Null not expected here.");
 
 162         private void handleInterface(Class<?> clazz, Object o, String namePath, int level, HashMap<String, String> result)
 
 163                 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 
 164             log.debug("In extract Interface {}", clazz);
 
 166                 log.warn("Loop with null class");
 
 170                 log.warn("Level to deep protection ended the recusive loop.");
 
 172             if (clazz.getName().contentEquals("org.opendaylight.mdsal.binding.dom.codec.impl.AugmentableCodecDataObject")) {
 
 173                 log.trace("Leave AugmentableCodecDataObject");
 
 177             Method[] methods = clazz.getDeclaredMethods();
 
 178             if (methods != null) {
 
 179                 for (Method method : methods) {
 
 180                     String methodName = method.getName();
 
 181                     log.trace("Method {}", methodName);
 
 182                     if (methodName.startsWith("get")) {
 
 183                         if (!methodName.equals("getImplementedInterface")) {
 
 184                             handleGetterValue(method, methodName.substring(3), namePath, o, level, result);
 
 186                     } else if (methodName.startsWith("is")) {
 
 187                         handleGetterValue(method, methodName.substring(2), namePath, o, level, result);
 
 188                     } else if (methodName.equals("key")) {
 
 189                         handleGetterValue(method, methodName, namePath, o, level, result);
 
 193             Class<?> sc = clazz.getSuperclass();  //Sodium
 
 194             log.trace("Superclass is - {}", sc);
 
 195             if (sc != null && !(sc.getName().contains("java.lang.reflect.Proxy")) && !Proxy.isProxyClass(sc)) {
 
 196                 handleInterface(sc, o, namePath, level + 1, result);
 
 200         private void handleGetterValue(Method method, String name, String namePath, Object o, int level,
 
 201                 HashMap<String, String> result)
 
 202                 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 
 203             log.debug("Begin: {}-{}-{}-{}", method.getName(), name, namePath, level);
 
 204             if (!method.isAccessible()) {
 
 205                 method.setAccessible(true);
 
 207             Object value = method.invoke(o);
 
 208             namePath += "/" + convertCamelToKebabCase(name);
 
 209             log.trace("Namepath {}", namePath);
 
 211                 Class<?> type = value.getClass();
 
 212                 log.trace("Class {}", type.getSimpleName());
 
 213                 if (List.class.isAssignableFrom(type)) {
 
 216                     for (Object listObject : (List<?>) value) {
 
 217                         if (listObject != null) {
 
 218                             if (Identifiable.class.isAssignableFrom(listObject.getClass())) {
 
 219                                 keyString = getKeyString((Identifiable<?>) listObject);
 
 221                                 keyString = String.valueOf(idx);
 
 223                             recurseExtractData(listObject, namePath + "[" + keyString + "]", level + 1, result);
 
 225                             log.warn("Null value received {} {} {}", namePath, idx, name);
 
 229                 } else if (DataObject.class.isAssignableFrom(type)) {
 
 230                     recurseExtractData(value, namePath, level + 1, result);
 
 231                 } else if (Proxy.isProxyClass(type)) {
 
 232                     recurseExtractData(value, namePath, level + 1, result);
 
 233                 } else if (Identifier.class.isAssignableFrom(type)) {
 
 236                     result.put(namePath, value.toString());
 
 239                 log.trace("Null value");
 
 243         private String getExtendedInterfaceName(Class<?> assignableClazz, Object o) {
 
 244             Class<?> interfaces[] = o.getClass().getInterfaces();
 
 245             for (Class<?> oneInterface : interfaces) {
 
 246                 log.trace("In getExtendedInterfaceName, oneInterface = {}", oneInterface.getClass().getName());
 
 247                 if (assignableClazz.isAssignableFrom(oneInterface)) {
 
 248                     return oneInterface.getSimpleName().contains("eventInstantAware")?oneInterface.getSimpleName().substring(0, oneInterface.getSimpleName().indexOf("$")):oneInterface.getSimpleName();
 
 251             log.trace("In getExtendedInterfaceName, o.getClass().getName() = {}", o.getClass().getName());
 
 252             return o.getClass().getSimpleName().contains("eventInstantAware")?o.getClass().getSimpleName().substring(0, o.getClass().getSimpleName().indexOf("$")):o.getClass().getSimpleName();
 
 255         private String getXmlNameSpace(Object o)
 
 256                 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
 
 257             Field f = o.getClass().getField("QNAME");
 
 258             Object couldQName = f.get(o);
 
 259             if (couldQName instanceof QName) {
 
 260                 QName qname = (QName) couldQName;
 
 261                 return "[@xmlns=" + qname.getNamespace().toString() + "]";
 
 266         /*private String convertCamelToKebabCase(String camel) {
 
 267             KebabCaseStrategy kbCase = new KebabCaseStrategy();
 
 268             return kbCase.translate(camel);
 
 269             //return camel.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase();
 
 273          * @param input string in Camel Case
 
 274          * @return String in Kebab case
 
 275          * Inspiration from KebabCaseStrategy class of com.fasterxml.jackson.databind with an additional condition to handle numbers as well
 
 276          * Using QNAME would have been a more fool proof solution, however it can lead to performance problems due to usage of Java reflection
 
 278         private String convertCamelToKebabCase(String input)
 
 280             if (input == null) return input; // garbage in, garbage out
 
 281             int length = input.length();
 
 286             StringBuilder result = new StringBuilder(length + (length >> 1));
 
 290             for (int i = 0; i < length; ++i) {
 
 291                 char ch = input.charAt(i);
 
 292                 char lc = Character.toLowerCase(ch);
 
 294                 if (lc == ch) { // lower-case letter means we can get new word
 
 295                     // but need to check for multi-letter upper-case (acronym), where assumption
 
 296                     // is that the last upper-case char is start of a new word
 
 297                     if ((upperCount > 1)  ){
 
 298                         // so insert hyphen before the last character now
 
 299                         result.insert(result.length() - 1, '-');
 
 300                     } else if ((upperCount == 1) && Character.isDigit(ch) && i != length-1) {
 
 305                     // Otherwise starts new word, unless beginning of string
 
 306                     if ((upperCount == 0) && (i > 0)) {
 
 313             return result.toString();
 
 317          * Key format like this: "FapServiceKey{_index=2}"
 
 321         private String getKeyString(Identifiable<?> indentifiableObject) {
 
 322             String keyString = (indentifiableObject.key()).toString();
 
 323             int start = keyString.indexOf("=") + 1;
 
 324             int end = keyString.length() - 1;
 
 325             if (start > 0 && start < end)
 
 326                 return keyString.substring(keyString.indexOf("=") + 1, keyString.length() - 1);
 
 328                 throw new IllegalArgumentException("indentifiable object without key");
 
 331         public Instant getTime(Notification notification) {
 
 334             if (notification instanceof EventInstantAware) { // If notification class extends/implements the EventInstantAware
 
 335                 time = ((EventInstantAware) notification).eventInstant();
 
 336                 log.info("Event time {}", time);
 
 338                 time = Instant.now();
 
 339                 log.info("Defaulting to actual time of processing the notification - {}", time);