e8f12f955992703509b6d5c267d75136caee0dc7
[ccsdk/features.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
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
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.devicemanager.impl.util;
23
24
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;
42
43 public class NotificationProxyParserImpl implements NotificationProxyParser {
44
45
46     /*
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
50      *
51      *  Attributes are provided by getters starting with "get", "is", "key".
52      *  Proxy received: "com.sun.proxy.$ProxyNN". NN is a number.
53      *
54      *  Example result:
55      *
56      * Expected output via VES in JSON
57      *    {
58      *      "event": {
59      *         "commonEventHeader": {
60      *            "domain": "notification",
61      *            "eventId": "ABCD",
62      *            "eventName": "Notification_LTE_Enterprise_C-RANSC_Cntrl-ACME",
63      *            "eventType": "TR069_RAN_notification",
64      *            "sequence": 0,
65      *            "priority": "High",
66      *            "reportingEntityId": "0005B942CDB4",
67      *            "reportingEntityName": "ABCD",
68      *            "sourceId": "0005B942CDB4",
69      *            "sourceName": "ABCD",
70      *            "startEpochMicrosec": 1569579510211,
71      *            "lastEpochMicrosec": 1569579510211,
72      *            "nfcNamingCode": "",
73      *            "nfNamingCode": "",
74      *            "nfVendorName": "",
75      *            "timeZoneOffset": "+00:00",
76      *            "version": "4.0.1",
77      *            "vesEventListenerVersion": "7.0.1"
78      *         },
79      *         "notificationFields": {
80      *         "arrayOfNamedHashMap": [
81      *          {
82      *           "name": "VALUECHANGE",
83      *           "hashMap": {
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"
102      *           },
103      *          }
104      *         ],
105      *         "changeContact": "",
106      *         "changeIdentifier": "SessionID",
107      *         "changeType": "ValueChange",
108      *         "newState": "",
109      *         "oldState": "",
110      *         "stateInterface": "",
111      *         "notificationFieldsVersion": "2.0",
112      *         }
113      *      }
114      *    }
115      *
116      */
117         private static final Logger log = LoggerFactory.getLogger(NotificationProxyParserImpl.class);
118         private Notification notification;
119
120         @Override
121         public HashMap<String, String> parseNotificationProxy(Notification notification)
122         /*throws ORanNotificationMapperException*/ {
123
124             try {
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);
130                 return null;
131             }
132
133         }
134
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>());
139         }
140
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());
145             if (level > 20) {
146                 log.warn("Level to deep protection ended the recusive loop.");
147             } else {
148                 if (o != null) {
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);
154                     //}
155                 } else {
156                     log.warn("Null not expected here.");
157                 }
158             }
159             return result;
160         }
161
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);
165             if (clazz == null) {
166                 log.warn("Loop with null class");
167                 return;
168             }
169             if (level > 20) {
170                 log.warn("Level to deep protection ended the recusive loop.");
171             }
172             if (clazz.getName().contentEquals("org.opendaylight.mdsal.binding.dom.codec.impl.AugmentableCodecDataObject")) {
173                 log.trace("Leave AugmentableCodecDataObject");
174                 return;
175             }
176
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);
185                         }
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);
190                     }
191                 }
192             }
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);
197             }
198         }
199
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);
206             }
207             Object value = method.invoke(o);
208             namePath += "/" + convertCamelToKebabCase(name);
209             log.trace("Namepath {}", namePath);
210             if (value != null) {
211                 Class<?> type = value.getClass();
212                 log.trace("Class {}", type.getSimpleName());
213                 if (List.class.isAssignableFrom(type)) {
214                     int idx = 0;
215                     String keyString;
216                     for (Object listObject : (List<?>) value) {
217                         if (listObject != null) {
218                             if (Identifiable.class.isAssignableFrom(listObject.getClass())) {
219                                 keyString = getKeyString((Identifiable<?>) listObject);
220                             } else {
221                                 keyString = String.valueOf(idx);
222                             }
223                             recurseExtractData(listObject, namePath + "[" + keyString + "]", level + 1, result);
224                         } else {
225                             log.warn("Null value received {} {} {}", namePath, idx, name);
226                         }
227                         idx++;
228                     }
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)) {
234                     //don't put the key
235                 } else {
236                     result.put(namePath, value.toString());
237                 }
238             } else {
239                 log.trace("Null value");
240             }
241         }
242
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();
249                 }
250             }
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();
253         }
254
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() + "]";
262             }
263             return "";
264         }
265
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();
270         } */
271
272         /**
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
277          */
278         private String convertCamelToKebabCase(String input)
279         {
280             if (input == null) return input; // garbage in, garbage out
281             int length = input.length();
282             if (length == 0) {
283                 return input;
284             }
285
286             StringBuilder result = new StringBuilder(length + (length >> 1));
287
288             int upperCount = 0;
289
290             for (int i = 0; i < length; ++i) {
291                 char ch = input.charAt(i);
292                 char lc = Character.toLowerCase(ch);
293
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) {
301                         result.append('-');
302                     }
303                     upperCount = 0;
304                 } else {
305                     // Otherwise starts new word, unless beginning of string
306                     if ((upperCount == 0) && (i > 0)) {
307                         result.append('-');
308                     }
309                     ++upperCount;
310                 }
311                 result.append(lc);
312             }
313             return result.toString();
314         }
315
316         /**
317          * Key format like this: "FapServiceKey{_index=2}"
318          *
319          * @return
320          */
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);
327             else
328                 throw new IllegalArgumentException("indentifiable object without key");
329         }
330
331         public Instant getTime(Notification notification) {
332             @NonNull
333             Instant time;
334             if (notification instanceof EventInstantAware) { // If notification class extends/implements the EventInstantAware
335                 time = ((EventInstantAware) notification).eventInstant();
336                 log.debug("Event time {}", time);
337             } else {
338                 time = Instant.now();
339                 log.debug("Defaulting to actual time of processing the notification - {}", time);
340             }
341             return time;
342         }
343 }