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.oran.impl;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Proxy;
28 import java.time.Instant;
29 import java.util.HashMap;
30 import java.util.List;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.opendaylight.yangtools.concepts.Identifier;
33 import org.opendaylight.yangtools.yang.binding.DataObject;
34 import org.opendaylight.yangtools.yang.binding.EventInstantAware;
35 import org.opendaylight.yangtools.yang.binding.Identifiable;
36 import org.opendaylight.yangtools.yang.binding.Notification;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 public class ORanNotificationMapper {
44 * Converter of TR069 notifications to VES key, value hashmap.
45 * Notifications are received as cascade if proxy object.
46 * References: https://stackoverflow.com/questions/19633534/what-is-com-sun-proxy-proxy
48 * Attributes are provided by getters starting with "get", "is", "key".
49 * Proxy received: "com.sun.proxy.$ProxyNN". NN is a number.
53 * Expected output via VES in JSON
56 * "commonEventHeader": {
57 * "domain": "notification",
59 * "eventName": "Notification_LTE_Enterprise_C-RANSC_Cntrl-ACME",
60 * "eventType": "TR069_RAN_notification",
63 * "reportingEntityId": "0005B942CDB4",
64 * "reportingEntityName": "ABCD",
65 * "sourceId": "0005B942CDB4",
66 * "sourceName": "ABCD",
67 * "startEpochMicrosec": 1569579510211,
68 * "lastEpochMicrosec": 1569579510211,
69 * "nfcNamingCode": "",
72 * "timeZoneOffset": "+00:00",
74 * "vesEventListenerVersion": "7.0.1"
76 * "notificationFields": {
77 * "arrayOfNamedHashMap": [
79 * "name": "VALUECHANGE",
81 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/serial-number": "0005B94238A0",
82 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/software-version": "4.3.00.244",
83 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/hardware-version": "1",
84 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/provisioning-code": "",
85 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer": "ACME",
86 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/product-class": "LTE_Enterprise_C-RANSC_Cntrl",
87 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/device-info/manufacturer-oui": "0005B9",
88 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/index": "1",
89 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/rf-tx-status": "false",
90 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[1]/fap-control/lte/op-state": "true",
91 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/index": "2",
92 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/rf-tx-status": "false",
93 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/services/fap-service[2]/fap-control/lte/op-state": "true",
94 * "/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",
95 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-url": "http://10.220.68.2/acscall",
96 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/parameter-key": "none",
97 * "/notification/VALUECHANGE[@xmlns=\"urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notification\"]/device/management-server/connection-request-password": "password",
98 * "/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 * "changeContact": "",
103 * "changeIdentifier": "SessionID",
104 * "changeType": "ValueChange",
107 * "stateInterface": "",
108 * "notificationFieldsVersion": "2.0",
114 private static final Logger log = LoggerFactory.getLogger(ORanNotificationMapper.class);
115 private Notification notification;
117 public HashMap<String, String> performMapping(Notification notification)
118 /*throws ORanNotificationMapperException*/ {
121 return extractFields(notification);
122 } catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException
123 | InvocationTargetException e) {
124 //throw new ORanNotificationMapperException("Mapping/JSON Creation problem", e);
125 log.info("Exception in performMapping method {}",e);
131 private HashMap<String, String> extractFields(Object o) throws IllegalAccessException, IllegalArgumentException,
132 InvocationTargetException, NoSuchFieldException, SecurityException {
133 String start = "/notification/" + getExtendedInterfaceName(Notification.class, o) + getXmlNameSpace(o);
134 return recurseExtractData(o, start, 0, new HashMap<String, String>());
137 private HashMap<String, String> recurseExtractData(Object o, String namePath, int level,
138 HashMap<String, String> result)
139 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
140 log.debug("In recurseExtractData - {} {} {}", namePath, level, log.isTraceEnabled() ? result : result.size());
142 log.warn("Level to deep protection ended the recusive loop.");
145 Class<?> classz = o.getClass();
146 //notification/VALUECHANGE$$$eventInstantAware[@xmlns=urn:org:onap:ccsdk:features:sdnr:northbound:onecell-notificationon] 0 {}
147 //org.opendaylight.yang.gen.v1.urn.org.onap.ccsdk.features.sdnr.northbound.onecell.notification.rev200622.VALUECHANGE$$$eventInstantAware
148 //if (Proxy.isProxyClass(classz)) {
149 handleInterface(classz, o, namePath, level, result);
152 log.warn("Null not expected here.");
158 private void handleInterface(Class<?> clazz, Object o, String namePath, int level, HashMap<String, String> result)
159 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
160 log.debug("In extract Interface {}", clazz);
162 log.warn("Loop with null class");
166 log.warn("Level to deep protection ended the recusive loop.");
168 if (clazz.getName().contentEquals("org.opendaylight.mdsal.binding.dom.codec.impl.AugmentableCodecDataObject")) {
169 log.trace("Leave AugmentableCodecDataObject");
173 Method[] methods = clazz.getDeclaredMethods();
174 if (methods != null) {
175 for (Method method : methods) {
176 String methodName = method.getName();
177 log.trace("Method {}", methodName);
178 if (methodName.startsWith("get")) {
179 if (!methodName.equals("getImplementedInterface")) {
180 handleGetterValue(method, methodName.substring(3), namePath, o, level, result);
182 } else if (methodName.startsWith("is")) {
183 handleGetterValue(method, methodName.substring(2), namePath, o, level, result);
184 } else if (methodName.equals("key")) {
185 handleGetterValue(method, methodName, namePath, o, level, result);
189 Class<?> sc = clazz.getSuperclass(); //Sodium
190 log.trace("Superclass is - {}", sc);
191 if (sc != null && !(sc.getName().contains("java.lang.reflect.Proxy")) && !Proxy.isProxyClass(sc)) {
192 handleInterface(sc, o, namePath, level + 1, result);
196 private void handleGetterValue(Method method, String name, String namePath, Object o, int level,
197 HashMap<String, String> result)
198 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
199 log.debug("Begin: {}-{}-{}-{}", method.getName(), name, namePath, level);
200 if (!method.isAccessible()) {
201 method.setAccessible(true);
203 Object value = method.invoke(o);
204 namePath += "/" + convertCamelToKebabCase(name);
205 log.trace("Namepath {}", namePath);
207 Class<?> type = value.getClass();
208 log.trace("Class {}", type.getSimpleName());
209 if (List.class.isAssignableFrom(type)) {
212 for (Object listObject : (List<?>) value) {
213 if (listObject != null) {
214 if (Identifiable.class.isAssignableFrom(listObject.getClass())) {
215 keyString = getKeyString((Identifiable<?>) listObject);
217 keyString = String.valueOf(idx);
219 recurseExtractData(listObject, namePath + "[" + keyString + "]", level + 1, result);
221 log.warn("Null value received {} {} {}", namePath, idx, name);
225 } else if (DataObject.class.isAssignableFrom(type)) {
226 recurseExtractData(value, namePath, level + 1, result);
227 } else if (Proxy.isProxyClass(type)) {
228 recurseExtractData(value, namePath, level + 1, result);
229 } else if (Identifier.class.isAssignableFrom(type)) {
232 result.put(namePath, value.toString());
235 log.trace("Null value");
239 private String getExtendedInterfaceName(Class<?> assignableClazz, Object o) {
240 Class<?> interfaces[] = o.getClass().getInterfaces();
241 for (Class<?> oneInterface : interfaces) {
242 log.trace("In getExtendedInterfaceName, oneInterface = {}", oneInterface.getClass().getName());
243 if (assignableClazz.isAssignableFrom(oneInterface)) {
244 return oneInterface.getSimpleName().contains("eventInstantAware")?oneInterface.getSimpleName().substring(0, oneInterface.getSimpleName().indexOf("$")):oneInterface.getSimpleName();
247 log.trace("In getExtendedInterfaceName, o.getClass().getName() = {}", o.getClass().getName());
248 return o.getClass().getSimpleName().contains("eventInstantAware")?o.getClass().getSimpleName().substring(0, o.getClass().getSimpleName().indexOf("$")):o.getClass().getSimpleName();
251 private String getXmlNameSpace(Object o)
252 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
253 Field f = o.getClass().getField("QNAME");
254 Object couldQName = f.get(o);
255 if (couldQName instanceof QName) {
256 QName qname = (QName) couldQName;
257 return "[@xmlns=" + qname.getNamespace().toString() + "]";
262 /*private String convertCamelToKebabCase(String camel) {
263 KebabCaseStrategy kbCase = new KebabCaseStrategy();
264 return kbCase.translate(camel);
265 //return camel.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase();
269 * @param input string in Camel Case
270 * @return String in Kebab case
271 * Inspiration from KebabCaseStrategy class of com.fasterxml.jackson.databind with an additional condition to handle numbers as well
272 * Using QNAME would have been a more fool proof solution, however it can lead to performance problems due to usage of Java reflection
274 public String convertCamelToKebabCase(String input)
276 if (input == null) return input; // garbage in, garbage out
277 int length = input.length();
282 StringBuilder result = new StringBuilder(length + (length >> 1));
286 for (int i = 0; i < length; ++i) {
287 char ch = input.charAt(i);
288 char lc = Character.toLowerCase(ch);
290 if (lc == ch) { // lower-case letter means we can get new word
291 // but need to check for multi-letter upper-case (acronym), where assumption
292 // is that the last upper-case char is start of a new word
293 if ((upperCount > 1) ){
294 // so insert hyphen before the last character now
295 result.insert(result.length() - 1, '-');
296 } else if ((upperCount == 1) && Character.isDigit(ch) && i != length-1) {
301 // Otherwise starts new word, unless beginning of string
302 if ((upperCount == 0) && (i > 0)) {
309 return result.toString();
313 * Key format like this: "FapServiceKey{_index=2}"
317 private String getKeyString(Identifiable<?> indentifiableObject) {
318 String keyString = (indentifiableObject.key()).toString();
319 int start = keyString.indexOf("=") + 1;
320 int end = keyString.length() - 1;
321 if (start > 0 && start < end)
322 return keyString.substring(keyString.indexOf("=") + 1, keyString.length() - 1);
324 throw new IllegalArgumentException("indentifiable object without key");
327 public Instant getTime(Notification notification2) {
330 if (notification instanceof EventInstantAware) { // If notification class extends/implements the EventInstantAware
331 time = ((EventInstantAware) notification).eventInstant();
332 log.info("Event time {}", time);
334 time = Instant.now();
335 log.info("Defaulting to actual time of processing the notification - {}", time);