Merge changes from topic 'ListenerAdapterTreeMigration'
authorTomas Cere <tcere@cisco.com>
Mon, 16 Oct 2017 08:39:28 +0000 (08:39 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 16 Oct 2017 08:39:28 +0000 (08:39 +0000)
* changes:
  Transition ListenerAdapter to ClusteredDOMDataTreeListener
  Add unit tests for ListenerAdapter

12 files changed:
restconf/restconf-nb-bierman02/pom.xml
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java
restconf/restconf-nb-bierman02/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java
restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplNotificationSubscribingTest.java
restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-create.json [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-del.json [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-create.json [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-del.json [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-update.json [new file with mode: 0644]
restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-update.json [new file with mode: 0644]

index 168bcc2c424cef37b06bd8e7d0a37ce9cac792c4..8a2b28ee2d82fde597151e8cf95338f50feedcaa 100644 (file)
       <groupId>org.opendaylight.aaa</groupId>
       <artifactId>aaa-shiro-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>testutils</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-broker-impl</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.skyscreamer</groupId>
+      <artifactId>jsonassert</artifactId>
+      <version>1.5.0</version>
+    </dependency>
   </dependencies>
 
   <build>
index e1178633136e8e90babd43e8d1ccb68946f6525e..3b460fdfa1f521427a62089352111a4a466498a9 100644 (file)
@@ -30,10 +30,11 @@ import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
@@ -529,9 +530,15 @@ public class BrokerFacade {
         }
 
         final YangInstanceIdentifier path = listener.getPath();
-        final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
-                datastore, path, listener, scope);
-
+        DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)
+                                    this.domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
+        if (changeService == null) {
+            throw new UnsupportedOperationException("DOMDataBroker does not support the DOMDataTreeChangeService"
+                                                        + this.domDataBroker);
+        }
+        DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(datastore, path);
+        ListenerRegistration<ListenerAdapter> registration =
+                                    changeService.registerDataTreeChangeListener(root, listener);
         listener.setRegistration(registration);
     }
 
index e6e27ccb701f41ef93d94cb098a2b635ec1e2dfe..d88f4e5b61612b3c440db213f9303ce4d58a3ceb 100644 (file)
@@ -7,16 +7,16 @@
  */
 package org.opendaylight.netconf.sal.streams.listeners;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
+import javax.annotation.Nonnull;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.transform.dom.DOMResult;
 import org.json.XML;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -28,6 +28,8 @@ 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.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -42,7 +44,7 @@ import org.w3c.dom.Node;
  * {@link ListenerAdapter} is responsible to track events, which occurred by
  * changing data in data source.
  */
-public class ListenerAdapter extends AbstractCommonSubscriber implements DOMDataChangeListener {
+public class ListenerAdapter extends AbstractCommonSubscriber implements ClusteredDOMDataTreeChangeListener {
 
     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class);
 
@@ -50,7 +52,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
     private final String streamName;
     private final NotificationOutputType outputType;
 
-    private AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change;
+    private Collection<DataTreeCandidate> dataTreeCandidates;
 
     /**
      * Creates new {@link ListenerAdapter} listener specified by path and stream
@@ -74,8 +76,8 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
     }
 
     @Override
-    public void onDataChanged(final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change) {
-        this.change = change;
+    public void onDataTreeChanged(@Nonnull Collection<DataTreeCandidate> dataTreeCandidates) {
+        this.dataTreeCandidates = dataTreeCandidates;
         final String xml = prepareXml();
         if (checkQueryParams(xml, this)) {
             prepareAndPostData(xml);
@@ -139,8 +141,8 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
         final Element dataChangedNotificationEventElement = doc.createElementNS(
                 "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "data-changed-notification");
 
-        addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, this.change,
-                schemaContext, dataContextTree);
+        addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement,
+                                                            this.dataTreeCandidates, schemaContext, dataContextTree);
         notificationElement.appendChild(dataChangedNotificationEventElement);
         return transformDoc(doc);
     }
@@ -152,64 +154,84 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
      *            {@link Document}
      * @param dataChangedNotificationEventElement
      *            {@link Element}
-     * @param change
-     *            {@link AsyncDataChangeEvent}
+     * @param dataTreeCandidates
+     *            {@link DataTreeCandidate}
      */
     private void addValuesToDataChangedNotificationEventElement(final Document doc,
             final Element dataChangedNotificationEventElement,
-            final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change,
+            final Collection<DataTreeCandidate> dataTreeCandidates,
             final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
 
-        addCreatedChangedValuesFromDataToElement(doc, change.getCreatedData().entrySet(),
-                dataChangedNotificationEventElement, Operation.CREATED, schemaContext, dataSchemaContextTree);
-
-        addCreatedChangedValuesFromDataToElement(doc, change.getUpdatedData().entrySet(),
-                dataChangedNotificationEventElement, Operation.UPDATED, schemaContext, dataSchemaContextTree);
-
-        addValuesFromDataToElement(doc, change.getRemovedPaths(), dataChangedNotificationEventElement,
-                Operation.DELETED);
-    }
-
-    /**
-     * Adds values from data to element.
-     *
-     * @param doc
-     *            {@link Document}
-     * @param data
-     *            Set of {@link YangInstanceIdentifier}.
-     * @param element
-     *            {@link Element}
-     * @param operation
-     *            {@link Operation}
-     */
-    private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data,
-            final Element element, final Operation operation) {
-        if ((data == null) || data.isEmpty()) {
-            return;
-        }
-        for (final YangInstanceIdentifier path : data) {
-            if (!ControllerContext.getInstance().isNodeMixin(path)) {
-                final Node node = createDataChangeEventElement(doc, path, operation);
-                element.appendChild(node);
+        for (DataTreeCandidate dataTreeCandidate : dataTreeCandidates) {
+            DataTreeCandidateNode candidateNode = dataTreeCandidate.getRootNode();
+            if (candidateNode == null) {
+                continue;
             }
+            YangInstanceIdentifier yiid = dataTreeCandidate.getRootPath();
+            addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, candidateNode,
+                    yiid.getParent(), schemaContext, dataSchemaContextTree);
         }
     }
 
-    private void addCreatedChangedValuesFromDataToElement(final Document doc,
-            final Set<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> data, final Element element,
-            final Operation operation, final SchemaContext schemaContext,
-            final DataSchemaContextTree dataSchemaContextTree) {
-        if ((data == null) || data.isEmpty()) {
+    private void addNodeToDataChangeNotificationEventElement(final Document doc,
+                             final Element dataChangedNotificationEventElement, DataTreeCandidateNode candidateNode,
+                             YangInstanceIdentifier parentYiid, SchemaContext schemaContext,
+                             DataSchemaContextTree dataSchemaContextTree) {
+
+        Optional<NormalizedNode<?,?>> optionalNormalizedNode = Optional.absent();
+        switch (candidateNode.getModificationType()) {
+            case APPEARED:
+            case SUBTREE_MODIFIED:
+            case WRITE:
+                optionalNormalizedNode = candidateNode.getDataAfter();
+                break;
+            case DELETE:
+            case DISAPPEARED:
+                optionalNormalizedNode = candidateNode.getDataBefore();
+                break;
+            case UNMODIFIED:
+            default:
+                break;
+        }
+
+        if (!optionalNormalizedNode.isPresent()) {
+            LOG.error("No node present in notification for {}", candidateNode);
             return;
         }
-        for (final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry : data) {
-            if (!ControllerContext.getInstance().isNodeMixin(entry.getKey())
-                    && (!getLeafNodesOnly() || entry.getValue() instanceof LeafNode)) {
-                final Node node = createCreatedChangedDataChangeEventElement(doc, entry, operation, schemaContext,
-                        dataSchemaContextTree);
-                element.appendChild(node);
+
+        NormalizedNode<?,?> normalizedNode = optionalNormalizedNode.get();
+        YangInstanceIdentifier yiid = YangInstanceIdentifier.builder(parentYiid)
+                                                            .append(normalizedNode.getIdentifier()).build();
+
+        boolean isNodeMixin = ControllerContext.getInstance().isNodeMixin(yiid);
+        boolean isSkippedNonLeaf = getLeafNodesOnly() && !(normalizedNode instanceof LeafNode);
+        if (!isNodeMixin && !isSkippedNonLeaf) {
+            Node node = null;
+            switch (candidateNode.getModificationType()) {
+                case APPEARED:
+                case SUBTREE_MODIFIED:
+                case WRITE:
+                    Operation op = candidateNode.getDataBefore().isPresent() ? Operation.UPDATED : Operation.CREATED;
+                    node = createCreatedChangedDataChangeEventElement(doc, yiid, normalizedNode, op,
+                            schemaContext, dataSchemaContextTree);
+                    break;
+                case DELETE:
+                case DISAPPEARED:
+                    node = createDataChangeEventElement(doc, yiid, Operation.DELETED);
+                    break;
+                case UNMODIFIED:
+                default:
+                    break;
+            }
+            if (node != null) {
+                dataChangedNotificationEventElement.appendChild(node);
             }
         }
+
+        for (DataTreeCandidateNode childNode : candidateNode.getChildNodes()) {
+            addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, childNode,
+                                                                        yiid, schemaContext, dataSchemaContextTree);
+        }
     }
 
     /**
@@ -238,11 +260,10 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
     }
 
     private Node createCreatedChangedDataChangeEventElement(final Document doc,
-            final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry, final Operation operation,
+            YangInstanceIdentifier path, NormalizedNode normalized, final Operation operation,
             final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
         final Element dataChangeEventElement = doc.createElement("data-change-event");
         final Element pathElement = doc.createElement("path");
-        final YangInstanceIdentifier path = entry.getKey();
         addPathAsValueToElement(path, pathElement);
         dataChangeEventElement.appendChild(pathElement);
 
@@ -252,7 +273,6 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements DOMData
 
         try {
             SchemaPath nodePath;
-            final NormalizedNode<?, ?> normalized = entry.getValue();
             if ((normalized instanceof MapEntryNode) || (normalized instanceof UnkeyedListEntryNode)) {
                 nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath();
             } else {
index 50f2195302fceafb30996a4f4be94d36c47e7c37..70e1c340a63819b3aebd5b960825d9ca944700d0 100644 (file)
@@ -28,20 +28,24 @@ import com.google.common.base.Optional;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
+
+import java.util.HashMap;
 import java.util.concurrent.Future;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
@@ -113,6 +117,9 @@ public class BrokerFacadeTest {
         when(this.domDataBroker.newReadOnlyTransaction()).thenReturn(this.readTransaction);
         when(this.domDataBroker.newWriteOnlyTransaction()).thenReturn(this.writeTransaction);
         when(this.domDataBroker.newReadWriteTransaction()).thenReturn(this.rwTransaction);
+        HashMap extensions = new HashMap();
+        extensions.put(DOMDataTreeChangeService.class, Mockito.mock(DOMDataTreeChangeService.class));
+        when(this.domDataBroker.getSupportedExtensions()).thenReturn(extensions);
 
         ControllerContext.getInstance()
                 .setSchemas(TestUtils.loadSchemaContext("/full-versions/test-module", "/modules"));
@@ -327,22 +334,23 @@ public class BrokerFacadeTest {
                 NotificationOutputType.XML);
 
         @SuppressWarnings("unchecked")
-        final ListenerRegistration<DOMDataChangeListener> mockRegistration = mock(ListenerRegistration.class);
+        final ListenerRegistration<ListenerAdapter> mockRegistration = mock(ListenerRegistration.class);
 
-        when(this.domDataBroker.registerDataChangeListener(any(LogicalDatastoreType.class), eq(this.instanceID),
-                eq(listener), eq(DataChangeScope.BASE))).thenReturn(mockRegistration);
+        DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)
+                this.domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
+        DOMDataTreeIdentifier loc = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, this.instanceID);
+        when(changeService.registerDataTreeChangeListener(eq(loc), eq(listener))).thenReturn(mockRegistration);
 
         this.brokerFacade.registerToListenDataChanges(
                 LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
 
-        verify(this.domDataBroker).registerDataChangeListener(
-                LogicalDatastoreType.CONFIGURATION, this.instanceID, listener, DataChangeScope.BASE);
+        verify(changeService).registerDataTreeChangeListener(loc, listener);
 
         assertEquals("isListening", true, listener.isListening());
 
         this.brokerFacade.registerToListenDataChanges(
                 LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE, listener);
-        verifyNoMoreInteractions(this.domDataBroker);
+        verifyNoMoreInteractions(changeService);
     }
 
     /**
index e34f9dbda1afe09e2aaf105625b9e28a604a23b4..bbac0c5cf675dbea0ba317762349dfbe5a0fc1f0 100644 (file)
@@ -23,7 +23,6 @@ import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
@@ -37,7 +36,7 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 public class RestconfImplNotificationSubscribingTest {
@@ -209,11 +208,10 @@ public class RestconfImplNotificationSubscribingTest {
 
         subscribe(list);
 
-        final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change =
-                Mockito.mock(AsyncDataChangeEvent.class);
+        ArrayList<DataTreeCandidate> candidates = new ArrayList<DataTreeCandidate>(0);
         Instant startOrig = listener.getStart();
         Assert.assertNotNull(startOrig);
-        listener.onDataChanged(change);
+        listener.onDataTreeChanged(candidates);
 
         startOrig = listener.getStart();
         Assert.assertNull(startOrig);
diff --git a/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java b/restconf/restconf-nb-bierman02/src/test/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapterTest.java
new file mode 100644 (file)
index 0000000..41bc131
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.netconf.sal.streams.listeners;
+
+import static java.time.Instant.EPOCH;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.binding.test.AbstractConcurrentDataBrokerTest;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
+import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
+import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
+import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
+    private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
+
+    private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
+    private static final String JSON_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.json";
+    private static final String JSON_NOTIF_LEAVES_DEL =  "/listener-adapter-test/notif-leaves-del.json";
+    private static final String JSON_NOTIF_CREATE = "/listener-adapter-test/notif-create.json";
+    private static final String JSON_NOTIF_UPDATE = "/listener-adapter-test/notif-update.json";
+    private static final String JSON_NOTIF_DEL = "/listener-adapter-test/notif-del.json";
+
+    private static YangInstanceIdentifier PATCH_CONT_YIID =
+            YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(PatchCont.QNAME));
+
+    private DataBroker dataBroker;
+    private DOMDataBroker domDataBroker;
+
+    @Before
+    public void setUp() throws Exception {
+        dataBroker = getDataBroker();
+        domDataBroker = getDomBroker();
+        SchemaContext sc = YangParserTestUtils.parseYangSource(
+                "/instanceidentifier/yang/instance-identifier-patch-module.yang");
+        ControllerContext.getInstance().setGlobalSchema(sc);
+    }
+
+    class ListenerAdapterTester extends ListenerAdapter {
+
+        private String lastNotification = null;
+
+        ListenerAdapterTester(YangInstanceIdentifier path, String streamName,
+                              NotificationOutputTypeGrouping.NotificationOutputType outputType, boolean leafNodesOnly) {
+            super(path, streamName, outputType);
+            setQueryParams(EPOCH, Optional.empty(), Optional.empty(), leafNodesOnly);
+        }
+
+        @Override
+        protected void post(final Event event) {
+            this.lastNotification = event.getData();
+        }
+
+        public void assertGot(String json) throws Exception {
+            long start = System.currentTimeMillis();
+            while (true) {
+                if (lastNotification != null) {
+                    break;
+                }
+                if (System.currentTimeMillis() - start > 1000) {
+                    throw new Exception("TIMED OUT waiting for notification with " + json);
+                }
+                Thread.currentThread().sleep(200);
+            }
+            LOG.debug("Comparing {} {}", json, lastNotification);
+            JSONAssert.assertEquals(json, withFakeDate(lastNotification), false);
+            this.lastNotification = null;
+        }
+    }
+
+    static String withFakeDate(String in) {
+        JSONObject doc = new JSONObject(in);
+        JSONObject notification = doc.getJSONObject("notification");
+        if (notification == null) {
+            return in;
+        }
+        notification.put("eventTime", "someDate");
+        return doc.toString();
+    }
+
+    private String getNotifJson(String path) throws IOException, URISyntaxException {
+        URL url = getClass().getResource(path);
+        byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
+        return withFakeDate(new String(bytes, StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void testJsonNotifsLeaves() throws Exception {
+        ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
+                                        NotificationOutputTypeGrouping.NotificationOutputType.JSON, true);
+        DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)
+                domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
+        DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
+        changeService.registerDataTreeChangeListener(root, adapter);
+
+        WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
+        InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
+                .child(MyList1.class, new MyList1Key("Althea"));
+        writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build(), true);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
+
+        writeTransaction = dataBroker.newWriteOnlyTransaction();
+        builder = new MyList1Builder().setKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
+        writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build(), true);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
+
+        writeTransaction = dataBroker.newWriteOnlyTransaction();
+        writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
+    }
+
+    @Test
+    public void testJsonNotifs() throws Exception {
+        ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
+                NotificationOutputTypeGrouping.NotificationOutputType.JSON, false);
+        DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)
+                domDataBroker.getSupportedExtensions().get(DOMDataTreeChangeService.class);
+        DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
+        changeService.registerDataTreeChangeListener(root, adapter);
+
+        WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
+        InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
+                .child(MyList1.class, new MyList1Key("Althea"));
+        writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build(), true);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
+
+        writeTransaction = dataBroker.newWriteOnlyTransaction();
+        builder = new MyList1Builder().setKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
+        writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build(), true);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
+
+        writeTransaction = dataBroker.newWriteOnlyTransaction();
+        writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
+        writeTransaction.submit();
+        adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-create.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-create.json
new file mode 100644 (file)
index 0000000..bc1cf52
--- /dev/null
@@ -0,0 +1,55 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "data": {
+                        "my-leaf11": {
+                            "content": "Jed",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf11"
+                },
+                {
+                    "data": {
+                        "name": {
+                            "content": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                },
+                {
+                    "data": {
+                        "patch-cont": {
+                            "my-list1": {
+                                "my-leaf11": "Jed",
+                                "name": "Althea"
+                            },
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont"
+                },
+                {
+                    "data": {
+                        "my-list1": {
+                            "my-leaf11": "Jed",
+                            "name": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-17T13:32:03.586+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-del.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-del.json
new file mode 100644 (file)
index 0000000..19afc1d
--- /dev/null
@@ -0,0 +1,36 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "data": {
+                        "patch-cont": {
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "updated",
+                    "path": "/instance-identifier-patch-module:patch-cont"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf12"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf11"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-17T14:18:53.404+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-create.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-create.json
new file mode 100644 (file)
index 0000000..52be56a
--- /dev/null
@@ -0,0 +1,31 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "data": {
+                        "my-leaf11": {
+                            "content": "Jed",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf11"
+                },
+                {
+                    "data": {
+                        "name": {
+                            "content": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-17T11:23:10.323+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-del.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-del.json
new file mode 100644 (file)
index 0000000..5d9e9f1
--- /dev/null
@@ -0,0 +1,23 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf11"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                },
+                {
+                    "operation": "deleted",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf12"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-18T15:30:16.099+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-update.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-leaves-update.json
new file mode 100644 (file)
index 0000000..ffef660
--- /dev/null
@@ -0,0 +1,31 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "data": {
+                        "my-leaf12": {
+                            "content": "Bertha",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf12"
+                },
+                {
+                    "data": {
+                        "name": {
+                            "content": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "updated",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-18T14:20:54.82+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}
diff --git a/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-update.json b/restconf/restconf-nb-bierman02/src/test/resources/listener-adapter-test/notif-update.json
new file mode 100644 (file)
index 0000000..b2957ea
--- /dev/null
@@ -0,0 +1,57 @@
+{
+    "notification": {
+        "data-changed-notification": {
+            "data-change-event": [
+                {
+                    "data": {
+                        "patch-cont": {
+                            "my-list1": {
+                                "my-leaf11": "Jed",
+                                "my-leaf12": "Bertha",
+                                "name": "Althea"
+                            },
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "updated",
+                    "path": "/instance-identifier-patch-module:patch-cont"
+                },
+                {
+                    "data": {
+                        "my-list1": {
+                            "my-leaf11": "Jed",
+                            "my-leaf12": "Bertha",
+                            "name": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "updated",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']"
+                },
+                {
+                    "data": {
+                        "my-leaf12": {
+                            "content": "Bertha",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "created",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:my-leaf12"
+                },
+                {
+                    "data": {
+                        "name": {
+                            "content": "Althea",
+                            "xmlns": "instance:identifier:patch:module"
+                        }
+                    },
+                    "operation": "updated",
+                    "path": "/instance-identifier-patch-module:patch-cont/instance-identifier-patch-module:my-list1[instance-identifier-patch-module:name='Althea']/instance-identifier-patch-module:name"
+                }
+            ],
+            "xmlns": "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
+        },
+        "eventTime": "2017-09-18T15:52:25.213+03:00",
+        "xmlns": "urn:ietf:params:xml:ns:netconf:notification:1.0"
+    }
+}