Use NormalizedNodeStreamWriter to emit XML data
[netconf.git] / restconf / restconf-nb / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / ListenerAdapterTest.java
1 /*
2  * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
9
10 import static org.hamcrest.CoreMatchers.allOf;
11 import static org.hamcrest.CoreMatchers.containsString;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.fail;
14
15 import com.google.common.util.concurrent.Uninterruptibles;
16 import java.io.IOException;
17 import java.net.URISyntaxException;
18 import java.nio.file.Files;
19 import java.nio.file.Paths;
20 import java.util.concurrent.CountDownLatch;
21 import java.util.concurrent.TimeUnit;
22 import org.json.JSONException;
23 import org.json.JSONObject;
24 import org.junit.AfterClass;
25 import org.junit.Before;
26 import org.junit.BeforeClass;
27 import org.junit.Test;
28 import org.opendaylight.mdsal.binding.api.DataBroker;
29 import org.opendaylight.mdsal.binding.api.WriteTransaction;
30 import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
35 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
36 import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
37 import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
38 import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
39 import org.opendaylight.restconf.api.query.StartTimeParam;
40 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
41 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
42 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
43 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.PatchCont1Builder;
44 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice1.PatchCase1Builder;
45 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.PatchCase11Builder;
46 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.patch.case11.patch.sub.choice11.PatchSubCase11Builder;
47 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice2.patch.case11.patch.sub.choice11.patch.sub.case11.patch.sub.sub.choice11.PatchSubSubCase11Builder;
48 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
49 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchContBuilder;
50 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
51 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
52 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
53 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
54 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
55 import org.opendaylight.yangtools.yang.binding.util.BindingMap;
56 import org.opendaylight.yangtools.yang.common.QName;
57 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
58 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
59 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
60 import org.skyscreamer.jsonassert.JSONAssert;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63 import org.xmlunit.assertj.XmlAssert;
64
65 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
66     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
67
68     private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
69     private static final String JSON_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.json";
70     private static final String JSON_NOTIF_LEAVES_DELETE = "/listener-adapter-test/notif-leaves-delete.json";
71     private static final String JSON_NOTIF_CHANGED_LEAVES_CREATE =
72             "/listener-adapter-test/notif-changed-leaves-create.json";
73     private static final String JSON_NOTIF_CHANGED_LEAVES_UPDATE =
74             "/listener-adapter-test/notif-changed-leaves-update.json";
75     private static final String JSON_NOTIF_CHANGED_LEAVES_DELETE =
76             "/listener-adapter-test/notif-changed-leaves-delete.json";
77     private static final String JSON_NOTIF_CHILD_NODES_ONLY_CREATE =
78             "/listener-adapter-test/notif-child-nodes-only-create.json";
79     private static final String JSON_NOTIF_CHILD_NODES_ONLY_UPDATE1 =
80         "/listener-adapter-test/notif-child-nodes-only-update1.json";
81     private static final String JSON_NOTIF_CHILD_NODES_ONLY_UPDATE2 =
82         "/listener-adapter-test/notif-child-nodes-only-update2.json";
83     private static final String JSON_NOTIF_CHILD_NODES_ONLY_DELETE =
84             "/listener-adapter-test/notif-child-nodes-only-delete.json";
85
86     private static final String XML_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.xml";
87     private static final String XML_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.xml";
88     private static final String XML_NOTIF_LEAVES_DELETE =  "/listener-adapter-test/notif-leaves-delete.xml";
89     private static final String XML_NOTIF_CHANGED_LEAVES_CREATE =
90             "/listener-adapter-test/notif-changed-leaves-create.xml";
91     private static final String XML_NOTIF_CHANGED_LEAVES_UPDATE =
92             "/listener-adapter-test/notif-changed-leaves-update.xml";
93     private static final String XML_NOTIF_CHANGED_LEAVES_DELETE =
94             "/listener-adapter-test/notif-changed-leaves-delete.xml";
95     private static final String XML_NOTIF_CHILD_NODES_ONLY_CREATE =
96         "/listener-adapter-test/notif-child-nodes-only-create.xml";
97     private static final String XML_NOTIF_CHILD_NODES_ONLY_UPDATE1 =
98         "/listener-adapter-test/notif-child-nodes-only-update1.xml";
99     private static final String XML_NOTIF_CHILD_NODES_ONLY_UPDATE2 =
100         "/listener-adapter-test/notif-child-nodes-only-update2.xml";
101     private static final String XML_NOTIF_CHILD_NODES_ONLY_DELETE =
102         "/listener-adapter-test/notif-child-nodes-only-delete.xml";
103
104     private static final String JSON_NOTIF_CONT_CREATE = "/listener-adapter-test/notif-cont-create.json";
105     private static final String JSON_NOTIF_CONT_UPDATE = "/listener-adapter-test/notif-cont-update.json";
106     private static final String JSON_NOTIF_CONT_DELETE = "/listener-adapter-test/notif-cont-delete.json";
107     private static final String JSON_NOTIF_LIST_CREATE = "/listener-adapter-test/notif-list-create.json";
108     private static final String JSON_NOTIF_LIST_UPDATE = "/listener-adapter-test/notif-list-update.json";
109     private static final String JSON_NOTIF_LIST_DELETE = "/listener-adapter-test/notif-list-delete.json";
110     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_CREATE =
111             "/listener-adapter-test/notif-without-data-cont-create.json";
112     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_UPDATE =
113             "/listener-adapter-test/notif-without-data-cont-update.json";
114     private static final String JSON_NOTIF_WITHOUT_DATA_CONT_DELETE =
115             "/listener-adapter-test/notif-without-data-cont-delete.json";
116     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_CREATE =
117             "/listener-adapter-test/notif-without-data-list-create.json";
118     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_UPDATE =
119             "/listener-adapter-test/notif-without-data-list-update.json";
120     private static final String JSON_NOTIF_WITHOUT_DATA_LIST_DELETE =
121             "/listener-adapter-test/notif-without-data-list-delete.json";
122
123     private static final String XML_NOTIF_CONT_CREATE = "/listener-adapter-test/notif-cont-create.xml";
124     private static final String XML_NOTIF_CONT_UPDATE = "/listener-adapter-test/notif-cont-update.xml";
125     private static final String XML_NOTIF_CONT_DELETE = "/listener-adapter-test/notif-cont-delete.xml";
126     private static final String XML_NOTIF_LIST_CREATE = "/listener-adapter-test/notif-list-create.xml";
127     private static final String XML_NOTIF_LIST_UPDATE = "/listener-adapter-test/notif-list-update.xml";
128     private static final String XML_NOTIF_LIST_DELETE = "/listener-adapter-test/notif-list-delete.xml";
129     private static final String XML_NOTIF_WITHOUT_DATA_CONT_CREATE =
130             "/listener-adapter-test/notif-without-data-cont-create.xml";
131     private static final String XML_NOTIF_WITHOUT_DATA_CONT_UPDATE =
132             "/listener-adapter-test/notif-without-data-cont-update.xml";
133     private static final String XML_NOTIF_WITHOUT_DATA_CONT_DELETE =
134             "/listener-adapter-test/notif-without-data-cont-delete.xml";
135     private static final String XML_NOTIF_WITHOUT_DATA_LIST_CREATE =
136             "/listener-adapter-test/notif-without-data-list-create.xml";
137     private static final String XML_NOTIF_WITHOUT_DATA_LIST_UPDATE =
138             "/listener-adapter-test/notif-without-data-list-update.xml";
139     private static final String XML_NOTIF_WITHOUT_DATA_LIST_DELETE =
140             "/listener-adapter-test/notif-without-data-list-delete.xml";
141
142     private static final YangInstanceIdentifier PATCH_CONT_YIID = YangInstanceIdentifier.of(PatchCont.QNAME);
143
144     private static final YangInstanceIdentifier MY_LIST1_YIID = YangInstanceIdentifier.builder()
145             .node(PatchCont.QNAME)
146             .node(MyList1.QNAME)
147             .nodeWithKey(MyList1.QNAME, QName.create(PatchCont.QNAME.getModule(), "name"), "Althea")
148             .build();
149
150     private static EffectiveModelContext SCHEMA_CONTEXT;
151
152     private DataBroker dataBroker;
153     private DOMDataBroker domDataBroker;
154     private DatabindProvider databindProvider;
155
156     @BeforeClass
157     public static void beforeClass() {
158         SCHEMA_CONTEXT = YangParserTestUtils.parseYangResourceDirectory("/instanceidentifier/yang");
159     }
160
161     @AfterClass
162     public static void afterClass() {
163         SCHEMA_CONTEXT = null;
164     }
165
166     @Before
167     public void setUp() throws Exception {
168         dataBroker = getDataBroker();
169         domDataBroker = getDomBroker();
170         databindProvider = () -> DatabindContext.ofModel(SCHEMA_CONTEXT);
171     }
172
173     class ListenerAdapterTester extends ListenerAdapter {
174
175         private volatile String lastNotification;
176         private CountDownLatch notificationLatch = new CountDownLatch(1);
177
178         ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
179                 final NotificationOutputType outputType, final boolean leafNodesOnly,
180                 final boolean skipNotificationData, final boolean changedLeafNodesOnly, final boolean childNodesOnly) {
181             super(path, streamName, outputType);
182             setQueryParams(NotificationQueryParams.of(StartTimeParam.forUriValue("1970-01-01T00:00:00Z"), null, null,
183                 leafNodesOnly ? LeafNodesOnlyParam.of(true) : null,
184                 skipNotificationData ? SkipNotificationDataParam.of(true) : null,
185                 changedLeafNodesOnly ? ChangedLeafNodesOnlyParam.of(true) : null,
186                 childNodesOnly ? ChildNodesOnlyParam.of(true) : null));
187         }
188
189         @Override
190         protected void post(final String data) {
191             lastNotification = data;
192             notificationLatch.countDown();
193         }
194
195         public void assertGot(final String json) throws JSONException {
196             // FIXME: use awaitility
197             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
198                 fail("Timed out waiting for notification for: " + json);
199             }
200
201             LOG.info("lastNotification: {}", lastNotification);
202             final String withFakeDate = withFakeDate(lastNotification);
203             LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
204
205             JSONAssert.assertEquals(json, withFakeDate, false);
206             lastNotification = null;
207             notificationLatch = new CountDownLatch(1);
208         }
209
210         public void assertXmlSimilar(final String xml) {
211             awaitUntilNotification(xml);
212
213             LOG.info("lastNotification: {}", lastNotification);
214             final String withFakeDate = withFakeXmlDate(lastNotification);
215             LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
216
217             XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
218             lastNotification = null;
219             notificationLatch = new CountDownLatch(1);
220         }
221
222         public String awaitUntilNotification(final String xml) {
223             // FIXME: use awaitility
224             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
225                 fail("Timed out waiting for notification for: " + xml);
226             }
227             return lastNotification;
228         }
229
230         public void resetLatch() {
231             notificationLatch = new CountDownLatch(1);
232         }
233     }
234
235     @Test
236     public void testJsonNotifsLeaves() throws Exception {
237         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
238             true, false, false, false);
239         adapter.setCloseVars(domDataBroker, databindProvider);
240
241         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
242                 .getInstance(DOMDataTreeChangeService.class);
243         final DOMDataTreeIdentifier root =
244                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
245         changeService.registerDataTreeChangeListener(root, adapter);
246
247         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
248         final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
249         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
250             .addAugmentation(new PatchCont1Builder()
251                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
252                 .setLeaf1("AugmentLeaf")
253                 .build())
254             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
255             .build());
256         writeTransaction.commit();
257         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
258
259         writeTransaction = dataBroker.newWriteOnlyTransaction();
260         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
261             .addAugmentation(new PatchCont1Builder()
262                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
263                 .setLeaf1("AugmentLeaf")
264                 .build())
265             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
266             .build());
267         writeTransaction.commit();
268         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
269
270         writeTransaction = dataBroker.newWriteOnlyTransaction();
271         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
272         writeTransaction.commit();
273         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DELETE));
274     }
275
276     @Test
277     public void testJsonNotifsChangedLeaves() throws Exception {
278         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
279                 false, false, true, false);
280         adapter.setCloseVars(domDataBroker, databindProvider);
281
282         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
283                 .getInstance(DOMDataTreeChangeService.class);
284         final DOMDataTreeIdentifier root =
285                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
286         changeService.registerDataTreeChangeListener(root, adapter);
287
288         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
289         final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
290         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
291             .addAugmentation(new PatchCont1Builder()
292                 .setPatchChoice2(new PatchCase11Builder()
293                     .setPatchSubChoice11(new PatchSubCase11Builder()
294                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceLeaf").build())
295                         .build())
296                     .build())
297                 .setLeaf1("AugmentLeaf")
298                 .build())
299             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
300             .build());
301         writeTransaction.commit();
302         adapter.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_CREATE));
303
304         writeTransaction = dataBroker.newWriteOnlyTransaction();
305         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
306             .addAugmentation(new PatchCont1Builder()
307                 .setPatchChoice2(new PatchCase11Builder()
308                     .setPatchSubChoice11(new PatchSubCase11Builder()
309                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceUpdate").build())
310                         .build())
311                     .build())
312                 .setLeaf1("AugmentLeaf")
313                 .build())
314             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
315             .build());
316         writeTransaction.commit();
317         adapter.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_UPDATE));
318
319         writeTransaction = dataBroker.newWriteOnlyTransaction();
320         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
321         writeTransaction.commit();
322         adapter.assertGot(getNotifJson(JSON_NOTIF_CHANGED_LEAVES_DELETE));
323     }
324
325     @Test
326     public void testJsonChildNodesOnly() throws Exception {
327         final var adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
328             NotificationOutputType.JSON, false, false, false, true);
329         adapter.setCloseVars(domDataBroker, databindProvider);
330
331         final var changeService = domDataBroker.getExtensions()
332             .getInstance(DOMDataTreeChangeService.class);
333         final var root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
334         changeService.registerDataTreeChangeListener(root, adapter);
335
336         final var iid = InstanceIdentifier.create(PatchCont.class).child(MyList1.class, new MyList1Key("Althea"));
337         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
338         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
339             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
340         writeTransaction.commit();
341         adapter.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_CREATE));
342
343         writeTransaction = dataBroker.newWriteOnlyTransaction();
344         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
345             new MyList1Builder().setMyLeaf11("Bertha").setName("Althea").build());
346         writeTransaction.commit();
347         adapter.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_UPDATE1));
348
349         writeTransaction = dataBroker.newWriteOnlyTransaction();
350         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid,
351             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
352         writeTransaction.commit();
353         adapter.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_UPDATE2));
354
355         writeTransaction = dataBroker.newWriteOnlyTransaction();
356         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
357         writeTransaction.commit();
358         adapter.assertGot(getNotifJson(JSON_NOTIF_CHILD_NODES_ONLY_DELETE));
359     }
360
361     @Test
362     public void testXmlLeavesOnly() throws Exception {
363         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML,
364             true, false, false, false);
365         adapter.setCloseVars(domDataBroker, databindProvider);
366
367         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
368                 .getInstance(DOMDataTreeChangeService.class);
369         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
370         changeService.registerDataTreeChangeListener(root, adapter);
371         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
372         final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
373         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
374             .addAugmentation(new PatchCont1Builder()
375                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
376                 .setLeaf1("AugmentLeaf")
377                 .build())
378             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
379             .build());
380         writeTransaction.commit();
381         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
382
383         writeTransaction = dataBroker.newWriteOnlyTransaction();
384         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
385             .addAugmentation(new PatchCont1Builder()
386                 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
387                 .setLeaf1("AugmentLeaf")
388                 .build())
389             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
390             .build());
391         writeTransaction.commit();
392         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
393
394         writeTransaction = dataBroker.newWriteOnlyTransaction();
395         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
396         writeTransaction.commit();
397
398         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
399         final String notification = adapter.awaitUntilNotification("");
400         assertThat(notification, allOf(
401             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
402                 + "/a:my-list1[a:name='Althea']/a:my-leaf11</path>"),
403             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
404                 + "/a:my-list1[a:name='Althea']/a:my-leaf12</path>"),
405             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
406                 + "/a:my-list1[a:name='Althea']/a:name</path>"),
407             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
408                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:leaf1</path>"),
409             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
410                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:case-leaf1</path>")));
411     }
412
413     @Test
414     public void testXmlChangedLeavesOnly() throws Exception {
415         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML,
416                 false, false, true, false);
417         adapter.setCloseVars(domDataBroker, databindProvider);
418
419         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
420                 .getInstance(DOMDataTreeChangeService.class);
421         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
422         changeService.registerDataTreeChangeListener(root, adapter);
423         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
424         final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
425         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
426             .addAugmentation(new PatchCont1Builder()
427                 .setPatchChoice2(new PatchCase11Builder()
428                     .setPatchSubChoice11(new PatchSubCase11Builder()
429                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceLeaf").build())
430                         .build())
431                     .build())
432                 .setLeaf1("AugmentLeaf")
433                 .build())
434             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build()))
435             .build());
436         writeTransaction.commit();
437         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHANGED_LEAVES_CREATE));
438
439         writeTransaction = dataBroker.newWriteOnlyTransaction();
440         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, new PatchContBuilder()
441             .addAugmentation(new PatchCont1Builder()
442                 .setPatchChoice2(new PatchCase11Builder()
443                     .setPatchSubChoice11(new PatchSubCase11Builder()
444                         .setPatchSubSubChoice11(new PatchSubSubCase11Builder().setCaseLeaf11("ChoiceUpdate").build())
445                         .build())
446                     .build())
447                 .setLeaf1("AugmentLeaf")
448                 .build())
449             .setMyList1(BindingMap.of(new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build()))
450             .build());
451         writeTransaction.commit();
452         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHANGED_LEAVES_UPDATE));
453
454         writeTransaction = dataBroker.newWriteOnlyTransaction();
455         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
456         writeTransaction.commit();
457
458         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
459         final String notification = adapter.awaitUntilNotification("");
460         assertThat(notification, allOf(
461             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
462                 + "/a:my-list1[a:name='Althea']/a:my-leaf11</path>"),
463             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
464                 + "/a:my-list1[a:name='Althea']/a:my-leaf12</path>"),
465             containsString("<path xmlns:a=\"instance:identifier:patch:module\">/a:patch-cont"
466                 + "/a:my-list1[a:name='Althea']/a:name</path>"),
467             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
468                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:leaf1</path>"),
469             containsString("<path xmlns:a=\"instance:identifier:patch:module\" "
470                 + "xmlns:b=\"augment:instance:identifier:patch:module\">/a:patch-cont/b:case-leaf11</path>")));
471     }
472
473     @Test
474     public void testXmlChildNodesOnly() throws Exception {
475         final var adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
476             NotificationOutputType.XML, false, false, false, true);
477         adapter.setCloseVars(domDataBroker, databindProvider);
478
479         final var changeService = domDataBroker.getExtensions()
480             .getInstance(DOMDataTreeChangeService.class);
481         final var root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
482         changeService.registerDataTreeChangeListener(root, adapter);
483
484         final var iid = InstanceIdentifier.create(PatchCont.class).child(MyList1.class, new MyList1Key("Althea"));
485         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
486         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
487             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
488         writeTransaction.commit();
489         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_CREATE));
490
491         writeTransaction = dataBroker.newWriteOnlyTransaction();
492         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid,
493             new MyList1Builder().setMyLeaf11("Bertha").setName("Althea").build());
494         writeTransaction.commit();
495         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_UPDATE1));
496
497         writeTransaction = dataBroker.newWriteOnlyTransaction();
498         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid,
499             new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build());
500         writeTransaction.commit();
501         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_UPDATE2));
502
503         writeTransaction = dataBroker.newWriteOnlyTransaction();
504         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
505         writeTransaction.commit();
506         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CHILD_NODES_ONLY_DELETE));
507     }
508
509     @Test
510     public void testJsonContNotifications() throws Exception {
511         jsonNotifications(PATCH_CONT_YIID, false, JSON_NOTIF_CONT_CREATE,
512                 JSON_NOTIF_CONT_UPDATE, JSON_NOTIF_CONT_DELETE);
513     }
514
515     @Test
516     public void testJsonListNotifications() throws Exception {
517         jsonNotifications(MY_LIST1_YIID, false, JSON_NOTIF_LIST_CREATE,
518                 JSON_NOTIF_LIST_UPDATE, JSON_NOTIF_LIST_DELETE);
519     }
520
521     @Test
522     public void testJsonContNotificationsWithoutData() throws Exception {
523         jsonNotifications(PATCH_CONT_YIID, true, JSON_NOTIF_WITHOUT_DATA_CONT_CREATE,
524                 JSON_NOTIF_WITHOUT_DATA_CONT_UPDATE, JSON_NOTIF_WITHOUT_DATA_CONT_DELETE);
525     }
526
527     @Test
528     public void testJsonListNotificationsWithoutData() throws Exception {
529         jsonNotifications(MY_LIST1_YIID, true, JSON_NOTIF_WITHOUT_DATA_LIST_CREATE,
530                 JSON_NOTIF_WITHOUT_DATA_LIST_UPDATE, JSON_NOTIF_WITHOUT_DATA_LIST_DELETE);
531     }
532
533     @Test
534     public void testXmlContNotifications() throws Exception {
535         xmlNotifications(PATCH_CONT_YIID, false, XML_NOTIF_CONT_CREATE,
536                 XML_NOTIF_CONT_UPDATE, XML_NOTIF_CONT_DELETE);
537     }
538
539     @Test
540     public void testXmlListNotifications() throws Exception {
541         xmlNotifications(MY_LIST1_YIID, false, XML_NOTIF_LIST_CREATE,
542                 XML_NOTIF_LIST_UPDATE, XML_NOTIF_LIST_DELETE);
543     }
544
545     @Test
546     public void testXmlContNotificationsWithoutData() throws Exception {
547         xmlNotifications(PATCH_CONT_YIID, true, XML_NOTIF_WITHOUT_DATA_CONT_CREATE,
548                 XML_NOTIF_WITHOUT_DATA_CONT_UPDATE, XML_NOTIF_WITHOUT_DATA_CONT_DELETE);
549     }
550
551     @Test
552     public void testXmlListNotificationsWithoutData() throws Exception {
553         xmlNotifications(MY_LIST1_YIID, true, XML_NOTIF_WITHOUT_DATA_LIST_CREATE,
554                 XML_NOTIF_WITHOUT_DATA_LIST_UPDATE, XML_NOTIF_WITHOUT_DATA_LIST_DELETE);
555     }
556
557     static String withFakeDate(final String in) throws JSONException {
558         final JSONObject doc = new JSONObject(in);
559         final JSONObject notification = doc.getJSONObject("ietf-restconf:notification");
560         if (notification == null) {
561             return in;
562         }
563         notification.put("event-time", "someDate");
564         return doc.toString();
565     }
566
567     static String withFakeXmlDate(final String in) {
568         return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
569     }
570
571     private String getNotifJson(final String path) throws IOException, URISyntaxException, JSONException {
572         return withFakeDate(Files.readString(Paths.get(getClass().getResource(path).toURI())));
573     }
574
575     private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
576         return withFakeXmlDate(Files.readString(Paths.get(getClass().getResource(path).toURI())));
577     }
578
579     private void jsonNotifications(final YangInstanceIdentifier pathYiid, final boolean skipData,
580             final String jsonNotifCreate, final String jsonNotifUpdate, final String jsonNotifDelete) throws Exception {
581         final var adapter = new ListenerAdapterTester(pathYiid, "Casey",
582                 NotificationOutputType.JSON, false, skipData, false, false);
583         adapter.setCloseVars(domDataBroker, databindProvider);
584
585         final var changeService = domDataBroker.getExtensions()
586                 .getInstance(DOMDataTreeChangeService.class);
587         final var root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, pathYiid);
588         changeService.registerDataTreeChangeListener(root, adapter);
589
590         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
591         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
592         final var iid = InstanceIdentifier.create(PatchCont.class)
593                 .child(MyList1.class, new MyList1Key("Althea"));
594         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
595         writeTransaction.commit();
596         adapter.assertGot(getNotifJson(jsonNotifCreate));
597
598         writeTransaction = dataBroker.newWriteOnlyTransaction();
599         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
600         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
601         writeTransaction.commit();
602         adapter.assertGot(getNotifJson(jsonNotifUpdate));
603
604         writeTransaction = dataBroker.newWriteOnlyTransaction();
605         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
606         writeTransaction.commit();
607         adapter.assertGot(getNotifJson(jsonNotifDelete));
608     }
609
610     private void xmlNotifications(final YangInstanceIdentifier pathYiid, final boolean skipData,
611             final String xmlNotifCreate, final String xmlNotifUpdate, final String xmlNotifDelete) throws Exception {
612         final var adapter = new ListenerAdapterTester(pathYiid, "Casey", NotificationOutputType.XML,
613                 false, skipData, false, false);
614         adapter.setCloseVars(domDataBroker, databindProvider);
615
616         final var changeService = domDataBroker.getExtensions()
617                 .getInstance(DOMDataTreeChangeService.class);
618         final var root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, pathYiid);
619         changeService.registerDataTreeChangeListener(root, adapter);
620
621         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
622         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
623         final var iid = InstanceIdentifier.create(PatchCont.class)
624                 .child(MyList1.class, new MyList1Key("Althea"));
625         writeTransaction.put(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
626         writeTransaction.commit();
627         adapter.assertXmlSimilar(getResultXml(xmlNotifCreate));
628
629         writeTransaction = dataBroker.newWriteOnlyTransaction();
630         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
631         writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
632         writeTransaction.commit();
633         adapter.assertXmlSimilar(getResultXml(xmlNotifUpdate));
634
635         writeTransaction = dataBroker.newWriteOnlyTransaction();
636         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
637         writeTransaction.commit();
638         adapter.assertXmlSimilar(getResultXml(xmlNotifDelete));
639     }
640 }