<dependency>
<groupId>org.opendaylight.netconf</groupId>
<artifactId>restconf-common-models</artifactId>
- <version>${project.version}</version>
- </dependency>
+ </dependency>
<dependency>
<groupId>org.opendaylight.netconf</groupId>
<artifactId>restconf-common</artifactId>
- <version>${project.version}</version>
- </dependency>
+ </dependency>
<dependency>
<groupId>org.opendaylight.netconf</groupId>
<artifactId>ietf-yang-library</artifactId>
- <version>${project.version}</version>
- </dependency>
+ </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.0</version>
</dependency>
<dependency>
<artifactId>aaa-shiro-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ </dependency>
+
<!-- Testing Dependencies -->
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<dependency>
<groupId>org.glassfish.jersey.bundles.repackaged</groupId>
<artifactId>jersey-guava</artifactId>
- <version>2.6</version>
+ <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>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skyscreamer</groupId>
+ <artifactId>jsonassert</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
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.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.DOMNotificationListener;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
* @param domDataBroker
* data broker for register data change listener
*/
- @SuppressWarnings("deprecation")
private static void registration(final LogicalDatastoreType ds, final DataChangeScope scope,
final ListenerAdapter listener, final DOMDataBroker domDataBroker) {
if (listener.isListening()) {
return;
}
- final YangInstanceIdentifier path = listener.getPath();
- final ListenerRegistration<DOMDataChangeListener> registration =
- domDataBroker.registerDataChangeListener(ds, path, listener, scope);
+ final DOMDataTreeChangeService changeService = (DOMDataTreeChangeService)domDataBroker.getSupportedExtensions()
+ .get(DOMDataTreeChangeService.class);
+ if (changeService == null) {
+ throw new UnsupportedOperationException("DOMDataBroker does not support the DOMDataTreeChangeService");
+ }
+
+ final DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(ds, listener.getPath());
+ final ListenerRegistration<ListenerAdapter> registration =
+ changeService.registerDataTreeChangeListener(root, listener);
listener.setRegistration(registration);
}
@SuppressWarnings("rawtypes")
private EventBusChangeRecorder eventBusChangeRecorder;
- @SuppressWarnings("rawtypes")
- private ListenerRegistration registration;
+
+ private volatile ListenerRegistration<?> registration;
/**
* Creating {@link EventBus}.
@Override
public final void close() throws Exception {
- this.registration.close();
- this.registration = null;
+ if (this.registration != null) {
+ this.registration.close();
+ this.registration = null;
+ }
deleteDataInDS();
unregister();
* @param registration
* DOMDataChangeListener registration
*/
- @SuppressWarnings("rawtypes")
- public void setRegistration(final ListenerRegistration registration) {
+ public void setRegistration(final ListenerRegistration<?> registration) {
this.registration = registration;
}
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Set;
+import java.util.Optional;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMResult;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
+import org.json.XML;
+import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
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;
* {@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);
private final String streamName;
private final NotificationOutputType outputType;
- private AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change;
-
/**
* Creates new {@link ListenerAdapter} listener specified by path and stream
* name and register for subscribing.
}
@Override
- @SuppressWarnings("checkstyle:hiddenField")
- public void onDataChanged(final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change) {
- this.change = change;
- final String xml = prepareXml();
+ public void onDataTreeChanged(final Collection<DataTreeCandidate> dataTreeCandidates) {
+ final String xml = prepareXml(dataTreeCandidates);
if (checkQueryParams(xml, this)) {
prepareAndPostData(xml);
}
private void prepareAndPostData(final String xml) {
final Event event = new Event(EventType.NOTIFY);
if (this.outputType.equals(NotificationOutputType.JSON)) {
- try {
- final JsonNode node = new XmlMapper().readTree(xml.getBytes(StandardCharsets.UTF_8));
- event.setData(node.toString());
- } catch (final IOException e) {
- LOG.error("Error parsing XML {}", xml, e);
- Throwables.propagate(e);
- }
+ event.setData(XML.toJSONObject(xml).toString());
} else {
event.setData(xml);
}
/**
* Prepare data in printable form and transform it to String.
*
+ * @param dataTreeCandidates the DataTreeCandidates to transform
+ *
* @return Data in printable form.
*/
- private String prepareXml() {
+ private String prepareXml(final Collection<DataTreeCandidate> dataTreeCandidates) {
final SchemaContext schemaContext = schemaHandler.get();
final DataSchemaContextTree dataContextTree = DataSchemaContextTree.from(schemaContext);
final Document doc = createDocument();
final Element dataChangedNotificationEventElement = doc.createElementNS(
"urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "data-changed-notification");
- addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, this.change,
+ addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, dataTreeCandidates,
schemaContext, dataContextTree);
notificationElement.appendChild(dataChangedNotificationEventElement);
return transformDoc(doc);
/**
* Adds values to data changed notification event element.
- *
- * @param doc
- * {@link Document}
- * @param dataChangedNotificationEventElement
- * {@link Element}
- * @param change
- * {@link AsyncDataChangeEvent}
*/
@SuppressWarnings("checkstyle:hiddenField")
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, schemaContext, dataSchemaContextTree);
- }
-
- /**
- * Adds values from data to element.
- *
- * @param doc
- * {@link Document}
- * @param data
- * Set of {@link YangInstanceIdentifier}.
- * @param element
- * {@link Element}
- * @param operation
- * {@link Operation}
- * @param schemaContext
- * schema context
- * @param dataSchemaContextTree
- * data schema context tree
- */
- private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data,
- final Element element, final Operation operation, final SchemaContext schemaContext,
- final DataSchemaContextTree dataSchemaContextTree) {
- if (data == null || data.isEmpty()) {
- return;
- }
- for (final YangInstanceIdentifier yiid : data) {
- if (!dataSchemaContextTree.getChild(yiid).isMixin()) {
- final Node node = createDataChangeEventElement(doc, yiid, operation, schemaContext);
- 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,
+ private void addNodeToDataChangeNotificationEventElement(final Document doc,
+ final Element dataChangedNotificationEventElement, final DataTreeCandidateNode candidateNode,
+ final YangInstanceIdentifier parentYiid, final SchemaContext schemaContext,
final DataSchemaContextTree dataSchemaContextTree) {
- if (data == null || data.isEmpty()) {
+
+ Optional<NormalizedNode<?,?>> optionalNormalizedNode = Optional.empty();
+ 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 (!dataSchemaContextTree.getChild(entry.getKey()).isMixin()
- && (!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 = dataSchemaContextTree.getChild(yiid).isMixin();
+ 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, schemaContext);
+ break;
+ case UNMODIFIED:
+ default:
+ break;
+ }
+ if (node != null) {
+ dataChangedNotificationEventElement.appendChild(node);
}
}
+
+ for (DataTreeCandidateNode childNode : candidateNode.getChildNodes()) {
+ addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, childNode,
+ yiid, schemaContext, dataSchemaContextTree);
+ }
}
/**
}
private Node createCreatedChangedDataChangeEventElement(final Document doc,
- final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry, final Operation operation,
+ final YangInstanceIdentifier eventPath, 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 eventPath = entry.getKey();
addPathAsValueToElement(eventPath, pathElement, schemaContext);
dataChangeEventElement.appendChild(pathElement);
try {
SchemaPath nodePath;
- final NormalizedNode<?, ?> normalized = entry.getValue();
if (normalized instanceof MapEntryNode || normalized instanceof UnkeyedListEntryNode) {
nodePath = dataSchemaContextTree.getChild(eventPath).getDataSchemaNode().getPath();
} else {
import com.google.common.util.concurrent.Futures;
import java.lang.reflect.Field;
import java.net.URI;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
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.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.restconf.common.context.NormalizedNodeContext;
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- final TransactionChainHandler txHandler = Mockito.mock(TransactionChainHandler.class);
- final DOMTransactionChain domTx = Mockito.mock(DOMTransactionChain.class);
+ final TransactionChainHandler txHandler = mock(TransactionChainHandler.class);
+ final DOMTransactionChain domTx = mock(DOMTransactionChain.class);
Mockito.when(this.transactionHandler.get()).thenReturn(domTx);
Mockito.when(txHandler.get()).thenReturn(domTx);
final DOMDataWriteTransaction wTx = Mockito.mock(DOMDataWriteTransaction.class);
Futures.immediateCheckedFuture(null);
Mockito.when(rwTx.submit()).thenReturn(checkedFutureEmpty);
Mockito.when(domTx.newReadWriteTransaction()).thenReturn(rwTx);
- final CheckedFuture<Void, TransactionCommitFailedException> checked = Mockito.mock(CheckedFuture.class);
+ final CheckedFuture<Void, TransactionCommitFailedException> checked = mock(CheckedFuture.class);
Mockito.when(wTx.submit()).thenReturn(checked);
Mockito.when(checked.checkedGet()).thenReturn(null);
this.schemaHandler = new SchemaContextHandler(txHandler);
final DOMDataBroker dataBroker = mock(DOMDataBroker.class);
- final ListenerRegistration<DOMDataChangeListener> listener = mock(ListenerRegistration.class);
+
+ DOMDataTreeChangeService dataTreeChangeService = mock(DOMDataTreeChangeService.class);
+ doReturn(mock(ListenerRegistration.class)).when(dataTreeChangeService)
+ .registerDataTreeChangeListener(any(), any());
+
+ doReturn(Collections.singletonMap(DOMDataTreeChangeService.class, dataTreeChangeService))
+ .when(dataBroker).getSupportedExtensions();
+
doReturn(dataBroker).when(this.dataBrokerHandler).get();
- doReturn(listener).when(dataBroker).registerDataChangeListener(any(), any(), any(), any());
- final MultivaluedMap<String, String> map = Mockito.mock(MultivaluedMap.class);
+
+ final MultivaluedMap<String, String> map = mock(MultivaluedMap.class);
final Set<Entry<String, List<String>>> set = new HashSet<>();
Mockito.when(map.entrySet()).thenReturn(set);
Mockito.when(this.uriInfo.getQueryParameters()).thenReturn(map);
--- /dev/null
+/*
+ * 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.restconf.nb.rfc8040.streams.listeners;
+
+import static java.time.Instant.EPOCH;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+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 java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+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.common.api.data.TransactionChainListener;
+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.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
+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;
+ private TransactionChainHandler transactionChainHandler;
+ private SchemaContextHandler schemaContextHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ dataBroker = getDataBroker();
+ domDataBroker = getDomBroker();
+ SchemaContext sc = YangParserTestUtils.parseYangResource(
+ "/instanceidentifier/yang/instance-identifier-patch-module.yang");
+
+ transactionChainHandler = new TransactionChainHandler(domDataBroker.createTransactionChain(
+ Mockito.mock(TransactionChainListener.class)));
+ schemaContextHandler = new SchemaContextHandler(transactionChainHandler);
+ SchemaContextHandler.setSchemaContext(sc);
+ }
+
+ class ListenerAdapterTester extends ListenerAdapter {
+
+ private volatile String lastNotification;
+ private CountDownLatch notificationLatch = new CountDownLatch(1);
+
+ ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
+ final NotificationOutputTypeGrouping.NotificationOutputType outputType,
+ final boolean leafNodesOnly) {
+ super(path, streamName, outputType);
+ setQueryParams(EPOCH, Optional.empty(), Optional.empty(), leafNodesOnly);
+ }
+
+ @Override
+ protected void post(final Event event) {
+ this.lastNotification = event.getData();
+ notificationLatch.countDown();
+ }
+
+ public void assertGot(final String json) {
+ if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 5, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for notification for: " + json);
+ }
+
+ LOG.info("lastNotification: {}", lastNotification);
+ String withFakeDate = withFakeDate(lastNotification);
+ LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
+
+ JSONAssert.assertEquals(json, withFakeDate, false);
+ this.lastNotification = null;
+ notificationLatch = new CountDownLatch(1);
+ }
+ }
+
+ static String withFakeDate(final 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(final 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);
+ adapter.setCloseVars(transactionChainHandler, schemaContextHandler);
+
+ 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);
+ adapter.setCloseVars(transactionChainHandler, schemaContextHandler);
+
+ 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));
+ }
+}
--- /dev/null
+{
+ "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: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: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: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"
+ }
+}
--- /dev/null
+{
+ "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: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: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: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: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"
+ }
+}
--- /dev/null
+{
+ "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: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: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"
+ }
+}
--- /dev/null
+{
+ "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: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: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: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"
+ }
+}
--- /dev/null
+{
+ "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: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: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"
+ }
+}
--- /dev/null
+{
+ "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: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: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: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"
+ }
+}