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.debug("Event time {}", time);
338 time = Instant.now();
339 log.debug("Defaulting to actual time of processing the notification - {}", time);