Bug 6324 - Notifications stream output is not same as restconf data 72/48772/2
authorJakub Toth <jatoth@cisco.com>
Thu, 20 Oct 2016 08:54:06 +0000 (10:54 +0200)
committerJakub Toth <jatoth@cisco.com>
Tue, 29 Nov 2016 10:06:20 +0000 (10:06 +0000)
  * serialization NormalizedNode to JSON with gson codec from yangtools
  * added test

Change-Id: I15245c48188151a1e7aaaf482997ae0d214491c0
Signed-off-by: Jakub Toth <jatoth@cisco.com>
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/streams/listeners/NotificationListenerTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/notifications/notifi-module.yang [new file with mode: 0644]

index 1d32b39e23bb077c59120d7bcd5a78c09db0fe35..608ddcbca1f8d12e572536b4fba4f2f5b1c4a4a6 100644 (file)
@@ -19,7 +19,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.StringReader;
+import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.io.Writer;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Set;
@@ -40,7 +42,6 @@ import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathFactory;
 import org.json.JSONObject;
-import org.json.XML;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
@@ -53,6 +54,9 @@ import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -75,8 +79,6 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
     private static final TransformerFactory FACTORY = TransformerFactory.newInstance();
 
     private final String streamName;
-    private ListenerRegistration<DOMNotificationListener> registration;
-    private Set<Channel> subscribers = new ConcurrentSet<>();
     private final EventBus eventBus;
     private final EventBusChangeRecorder eventBusChangeRecorder;
 
@@ -86,6 +88,11 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
     private Date stop = null;
     private String filter;
 
+    private SchemaContext schemaContext;
+    private DOMNotification notification;
+    private ListenerRegistration<DOMNotificationListener> registration;
+    private Set<Channel> subscribers = new ConcurrentSet<>();
+
     /**
      * Set path of listener and stream name, register event bus.
      *
@@ -109,10 +116,12 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
 
     @Override
     public void onNotification(final DOMNotification notification) {
+        this.schemaContext = ControllerContext.getInstance().getGlobalSchema();
+        this.notification = notification;
         final Date now = new Date();
         if (this.stop != null) {
             if ((this.start.compareTo(now) < 0) && (this.stop.compareTo(now) > 0)) {
-                checkFilter(notification);
+                checkFilter();
             }
             if (this.stop.compareTo(now) < 0) {
                 try {
@@ -124,21 +133,19 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
         } else if (this.start != null) {
             if (this.start.compareTo(now) < 0) {
                 this.start = null;
-                checkFilter(notification);
+                checkFilter();
             }
         } else {
-            checkFilter(notification);
+            checkFilter();
         }
     }
 
     /**
      * Check if is filter used and then prepare and post data do client
      *
-     * @param notification
-     *            - data of notification
      */
-    private void checkFilter(final DOMNotification notification) {
-        final String xml = prepareXmlFrom(notification);
+    private void checkFilter() {
+        final String xml = prepareXml();
         if (this.filter == null) {
             prepareAndPostData(xml);
         } else {
@@ -177,14 +184,40 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
     private void prepareAndPostData(final String xml) {
         final Event event = new Event(EventType.NOTIFY);
         if (this.outputType.equals("JSON")) {
-            final JSONObject jsonObject = XML.toJSONObject(xml);
-            event.setData(jsonObject.toString());
+            event.setData(prepareJson());
         } else {
             event.setData(xml);
         }
         this.eventBus.post(event);
     }
 
+    /**
+     * Prepare json from notification data
+     *
+     * @return json as {@link String}
+     */
+    private String prepareJson() {
+        final JSONObject json = new JSONObject();
+        json.put("ietf-restconf:notification",
+                new JSONObject(writeBodyToString()).put("event-time", ListenerAdapter.toRFC3339(new Date())));
+        return json.toString();
+    }
+
+    private String writeBodyToString() {
+        final Writer writer = new StringWriter();
+        final NormalizedNodeStreamWriter jsonStream =
+                JSONNormalizedNodeStreamWriter.createExclusiveWriter(JSONCodecFactory.create(this.schemaContext),
+                        this.notification.getType(), null, JsonWriterFactory.createJsonWriter(writer));
+        final NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream);
+        try {
+            nodeWriter.write(this.notification.getBody());
+            nodeWriter.close();
+        } catch (final IOException e) {
+            throw new RestconfDocumentedException("Problem while writing body of notification to JSON. ", e);
+        }
+        return writer.toString();
+    }
+
     /**
      * Checks if exists at least one {@link Channel} subscriber.
      *
@@ -273,8 +306,7 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
         this.eventBus.post(event);
     }
 
-    private String prepareXmlFrom(final DOMNotification notification) {
-        final SchemaContext schemaContext = ControllerContext.getInstance().getGlobalSchema();
+    private String prepareXml() {
         final Document doc = ListenerAdapter.createDocument();
         final Element notificationElement =
                 doc.createElementNS("urn:ietf:params:xml:ns:netconf:notification:1.0",
@@ -284,10 +316,10 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
         final Element eventTimeElement = doc.createElement("eventTime");
         eventTimeElement.setTextContent(ListenerAdapter.toRFC3339(new Date()));
         notificationElement.appendChild(eventTimeElement);
-        final String notificationNamespace = notification.getType().getLastComponent().getNamespace().toString();
+
         final Element notificationEventElement = doc.createElementNS(
-                notificationNamespace, "event");
-        addValuesToNotificationEventElement(doc, notificationEventElement, notification, schemaContext);
+                "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "create-notification-stream");
+        addValuesToNotificationEventElement(doc, notificationEventElement, this.notification, this.schemaContext);
         notificationElement.appendChild(notificationEventElement);
 
         try {
@@ -320,7 +352,9 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
             final DOMResult domResult = writeNormalizedNode(body,
                     YangInstanceIdentifier.create(body.getIdentifier()), schemaContext);
             final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
-            element.appendChild(result);
+            final Element dataElement = doc.createElement("notification");
+            dataElement.appendChild(result);
+            element.appendChild(dataElement);
         } catch (final IOException e) {
             LOG.error("Error in writer ", e);
         } catch (final XMLStreamException e) {
index c3746df5974f754cda8a1d8fd6ffd803354d977e..7c809ad77fe74d49604c1d8c6b9e41c814aa8d42 100644 (file)
@@ -174,7 +174,6 @@ public class Notificator {
      */
     public static boolean existNotificationListenerFor(final String streamName) {
         return notificationListenersByStreamName.containsKey(streamName);
-
     }
 
     /**
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/streams/listeners/NotificationListenerTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/streams/listeners/NotificationListenerTest.java
new file mode 100644 (file)
index 0000000..12d623b
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.sal.streams.listeners;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
+import org.opendaylight.controller.sal.restconf.impl.test.TestUtils;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.util.SingletonSet;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+public class NotificationListenerTest {
+
+    private SchemaContext schmeaCtx;
+
+    @Before
+    public void init() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        ControllerContext.getInstance().setGlobalSchema(TestUtils.loadSchemaContext("/notifications"));
+        this.schmeaCtx = ControllerContext.getInstance().getGlobalSchema();
+    }
+
+    @Test
+    public void notifi_leafTest() throws Exception {
+        final QNameModule moduleQName =
+                QNameModule.create(new URI("notifi:mod"), new SimpleDateFormat("yyyy-MM-dd").parse("2016-11-23"));
+        final SchemaPath schemaPathNotifi = SchemaPath.create(false, QName.create(moduleQName, "notifi-leaf"));
+
+        final DOMNotification notificationData = mock(DOMNotification.class);
+
+        final LeafNode leaf = mockLeaf(QName.create(moduleQName, "lf"));
+        final ContainerNode notifiBody = mockCont(schemaPathNotifi.getLastComponent(), leaf);
+
+        when(notificationData.getType()).thenReturn(schemaPathNotifi);
+        when(notificationData.getBody()).thenReturn(notifiBody);
+
+        final String result = prepareJson(notificationData, schemaPathNotifi);
+
+        assertTrue(result.contains("ietf-restconf:notification"));
+        assertTrue(result.contains("event-time"));
+        assertTrue(result.contains("notifi-module:notifi-leaf"));
+        assertTrue(result.contains("lf" + '"' + ":" + '"' + "value"));
+    }
+
+    @Test
+    public void notifi_cont_leafTest() throws Exception {
+        final QNameModule moduleQName =
+                QNameModule.create(new URI("notifi:mod"), new SimpleDateFormat("yyyy-MM-dd").parse("2016-11-23"));
+
+        final SchemaPath schemaPathNotifi =
+                SchemaPath.create(false, QName.create(moduleQName, "notifi-cont"));
+
+        final DOMNotification notificationData = mock(DOMNotification.class);
+
+        final LeafNode leaf = mockLeaf(QName.create(moduleQName, "lf"));
+        final ContainerNode cont = mockCont(QName.create(moduleQName, "cont"), leaf);
+        final ContainerNode notifiBody = mockCont(schemaPathNotifi.getLastComponent(), cont);
+
+        when(notificationData.getType()).thenReturn(schemaPathNotifi);
+        when(notificationData.getBody()).thenReturn(notifiBody);
+
+        final String result = prepareJson(notificationData, schemaPathNotifi);
+
+        assertTrue(result.contains("ietf-restconf:notification"));
+        assertTrue(result.contains("event-time"));
+        assertTrue(result.contains("notifi-module:notifi-cont"));
+        assertTrue(result.contains("cont"));
+        assertTrue(result.contains("lf" + '"' + ":" + '"' + "value"));
+    }
+
+    @Test
+    public void notifi_list_Test() throws Exception {
+        final QNameModule moduleQName =
+                QNameModule.create(new URI("notifi:mod"), new SimpleDateFormat("yyyy-MM-dd").parse("2016-11-23"));
+
+        final SchemaPath schemaPathNotifi = SchemaPath.create(false, QName.create(moduleQName, "notifi-list"));
+
+        final DOMNotification notificationData = mock(DOMNotification.class);
+
+        final LeafNode leaf = mockLeaf(QName.create(moduleQName, "lf"));
+        final MapEntryNode entry = mockMapEntry(QName.create(moduleQName, "lst"), leaf);
+        final MapNode list = mockList(QName.create(moduleQName, "lst"), entry);
+        final ContainerNode cont = mockCont(QName.create(moduleQName, "cont"), list);
+        final ContainerNode notifiBody = mockCont(schemaPathNotifi.getLastComponent(), cont);
+
+        when(notificationData.getType()).thenReturn(schemaPathNotifi);
+        when(notificationData.getBody()).thenReturn(notifiBody);
+
+        final String result = prepareJson(notificationData, schemaPathNotifi);
+
+        assertTrue(result.contains("ietf-restconf:notification"));
+        assertTrue(result.contains("event-time"));
+        assertTrue(result.contains("notifi-module:notifi-list"));
+        assertTrue(result.contains("lst"));
+        assertTrue(result.contains("lf" + '"' + ":" + '"' + "value"));
+    }
+
+    @Test
+    public void notifi_grpTest() throws Exception {
+        final QNameModule moduleQName =
+                QNameModule.create(new URI("notifi:mod"), new SimpleDateFormat("yyyy-MM-dd").parse("2016-11-23"));
+
+        final SchemaPath schemaPathNotifi = SchemaPath.create(false, QName.create(moduleQName, "notifi-grp"));
+
+        final DOMNotification notificationData = mock(DOMNotification.class);
+
+        final LeafNode leaf = mockLeaf(QName.create(moduleQName, "lf"));
+        final ContainerNode notifiBody = mockCont(schemaPathNotifi.getLastComponent(), leaf);
+
+        when(notificationData.getType()).thenReturn(schemaPathNotifi);
+        when(notificationData.getBody()).thenReturn(notifiBody);
+
+        final String result = prepareJson(notificationData, schemaPathNotifi);
+
+        assertTrue(result.contains("ietf-restconf:notification"));
+        assertTrue(result.contains("event-time"));
+        assertTrue(result.contains("lf" + '"' + ":" + '"' + "value"));
+    }
+
+    @Test
+    public void notifi_augmTest() throws Exception {
+        final QNameModule moduleQName =
+                QNameModule.create(new URI("notifi:mod"), new SimpleDateFormat("yyyy-MM-dd").parse("2016-11-23"));
+
+        final SchemaPath schemaPathNotifi = SchemaPath.create(false, QName.create(moduleQName, "notifi-augm"));
+
+        final DOMNotification notificationData = mock(DOMNotification.class);
+
+        final LeafNode leaf = mockLeaf(QName.create(moduleQName, "lf-augm"));
+        final AugmentationNode augm = mockAugm(leaf);
+        final ContainerNode notifiBody = mockCont(schemaPathNotifi.getLastComponent(), augm);
+
+        when(notificationData.getType()).thenReturn(schemaPathNotifi);
+        when(notificationData.getBody()).thenReturn(notifiBody);
+
+        final String result = prepareJson(notificationData, schemaPathNotifi);
+
+        assertTrue(result.contains("ietf-restconf:notification"));
+        assertTrue(result.contains("event-time"));
+        assertTrue(result.contains("lf-augm" + '"' + ":" + '"' + "value"));
+    }
+
+    private AugmentationNode mockAugm(final LeafNode leaf) {
+        final AugmentationNode augm = mock(AugmentationNode.class);
+        final AugmentationIdentifier augmId = new AugmentationIdentifier(SingletonSet.of(leaf.getNodeType()));
+        when(augm.getIdentifier()).thenReturn(augmId);
+
+        final Collection<DataContainerChild<? extends PathArgument, ?>> childs = new ArrayList<>();
+        childs.add(leaf);
+
+        when(augm.getValue()).thenReturn(childs);
+        return augm;
+    }
+
+    private MapEntryNode mockMapEntry(final QName entryQName, final LeafNode leaf) {
+        final MapEntryNode entry = mock(MapEntryNode.class);
+        final Map<QName, Object> keyValues = new HashMap<>();
+        keyValues.put(leaf.getNodeType(), "value");
+        final NodeIdentifierWithPredicates nodeId = new NodeIdentifierWithPredicates(leaf.getNodeType(), keyValues);
+        when(entry.getIdentifier()).thenReturn(nodeId);
+        when(entry.getChild(any())).thenReturn(Optional.of(leaf));
+
+        final Collection<DataContainerChild<? extends PathArgument, ?>> childs = new ArrayList<>();
+        childs.add(leaf);
+
+        when(entry.getValue()).thenReturn(childs);
+        return entry;
+    }
+
+    private MapNode mockList(final QName listQName, final MapEntryNode... entries) {
+        final MapNode list = mock(MapNode.class);
+        when(list.getIdentifier()).thenReturn(NodeIdentifier.create(listQName));
+        when(list.getNodeType()).thenReturn(listQName);
+        when(list.getValue()).thenReturn(Lists.newArrayList(entries));
+        return list;
+    }
+
+    private ContainerNode mockCont(final QName contQName, final DataContainerChild<? extends PathArgument, ?> child) {
+        final ContainerNode cont = mock(ContainerNode.class);
+        when(cont.getIdentifier()).thenReturn(NodeIdentifier.create(contQName));
+        when(cont.getNodeType()).thenReturn(contQName);
+
+        final Collection<DataContainerChild<? extends PathArgument, ?>> childs = new ArrayList<>();
+        childs.add(child);
+        when(cont.getValue()).thenReturn(childs);
+        return cont;
+    }
+
+    private LeafNode mockLeaf(final QName leafQName) {
+        final LeafNode child = mock(LeafNode.class);
+        when(child.getNodeType()).thenReturn(leafQName);
+        when(child.getIdentifier()).thenReturn(NodeIdentifier.create(leafQName));
+        when(child.getValue()).thenReturn("value");
+        return child;
+    }
+
+    private String prepareJson(final DOMNotification notificationData, final SchemaPath schemaPathNotifi)
+            throws Exception {
+        final List<SchemaPath> paths = new ArrayList<>();
+        paths.add(schemaPathNotifi);
+        final List<NotificationListenerAdapter> listNotifi =
+                Notificator.createNotificationListener(paths, "stream-name", NotificationOutputType.JSON.toString());
+        final NotificationListenerAdapter notifi = listNotifi.get(0);
+
+        final Class<?> vars[] = {};
+        final Method prepareJsonM = notifi.getClass().getDeclaredMethod("prepareJson", vars);
+        prepareJsonM.setAccessible(true);
+
+        final Field notification = notifi.getClass().getDeclaredField("notification");
+        notification.setAccessible(true);
+        notification.set(notifi, notificationData);
+
+        final Field schema = notifi.getClass().getDeclaredField("schemaContext");
+        schema.setAccessible(true);
+        schema.set(notifi, this.schmeaCtx);
+
+        final String result = (String) prepareJsonM.invoke(notifi, null);
+        Preconditions.checkNotNull(result);
+        return result;
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/notifications/notifi-module.yang b/restconf/sal-rest-connector/src/test/resources/notifications/notifi-module.yang
new file mode 100644 (file)
index 0000000..70d45f1
--- /dev/null
@@ -0,0 +1,49 @@
+module notifi-module {
+    namespace "notifi:mod";
+    prefix notm;
+
+    revision "2016-11-23" {
+    }
+
+    notification notifi-leaf {
+        leaf lf {
+            type string;
+        }
+    }
+
+    notification notifi-cont {
+        container cont {
+            leaf lf {
+                type string;
+            }
+        }
+    }
+
+    notification notifi-list {
+        list lst {
+            key lf;
+            leaf lf {
+                type string;
+            }
+        }
+    }
+
+    notification notifi-grp {
+        uses grp;
+    }
+
+    grouping grp {
+        leaf lf {
+            type string;
+        }
+    }
+
+    notification notifi-augm {
+    }
+
+    augment notifi-augm {
+        leaf lf-augm {
+            type string;
+        }
+    }
+}
\ No newline at end of file