Add Child Nodes Only query parameter to SSE events 43/106843/6
authorOlivier Dugeon <olivier.dugeon@orange.com>
Wed, 21 Jun 2023 08:35:21 +0000 (10:35 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Sep 2023 18:07:50 +0000 (20:07 +0200)
For SSE or Websocket, events are reported from the root node of the listen
Data Tree or from the modified leafs with the 'odl-leaf-nodes-only=true'
query parameter. This patch introduce a new 'odl-child-nodes-only' query
parameter to get only modified children in a compact form. This new option
could be considered as something in the middle between getting the whole
Data Tree from the root node and a per leaf node report.

JIRA: NETCONF-1076
Change-Id: Ibeda878a38a52f911dd8fbaa0503c4bfb56ea9b7
Signed-off-by: Olivier Dugeon <olivier.dugeon@orange.com>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
18 files changed:
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ChildNodesOnlyParam.java [new file with mode: 0644]
protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/RestconfQueryParam.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/monitoring/CapabilitiesWriter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractCommonSubscriber.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractNotificationListenerAdaptor.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractWebsocketSerializer.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/EventFormatter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/JSONDataTreeCandidateFormatter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/JSONNotificationFormatter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/ListenerAdapter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/XMLDataTreeCandidateFormatter.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/XMLNotificationFormatter.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/monitoring/CapabilitiesWriterTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/JsonNotificationListenerTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/ListenerAdapterTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/XmlNotificationListenerTest.java

diff --git a/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ChildNodesOnlyParam.java b/protocol/restconf-api/src/main/java/org/opendaylight/restconf/api/query/ChildNodesOnlyParam.java
new file mode 100644 (file)
index 0000000..9fc8662
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023 Orange 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.api.query;
+
+import java.net.URI;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * OpenDaylight extension parameter. When used as {@code odl-child-nodes-only=true}, it will instruct the listener
+ * streams to only emit child nodes.
+ */
+public final class ChildNodesOnlyParam implements RestconfQueryParam<ChildNodesOnlyParam> {
+    // API consistency: must not be confused with enum constants
+    @SuppressWarnings("checkstyle:ConstantName")
+    public static final String uriName = "odl-child-nodes-only";
+
+    private static final @NonNull URI CAPABILITY =
+        URI.create("urn:opendaylight:params:restconf:capability:child-nodes-only:1.0");
+    private static final @NonNull ChildNodesOnlyParam FALSE = new ChildNodesOnlyParam(false);
+    private static final @NonNull ChildNodesOnlyParam TRUE = new ChildNodesOnlyParam(true);
+
+    private final boolean value;
+
+    private ChildNodesOnlyParam(final boolean value) {
+        this.value = value;
+    }
+
+    public static @NonNull ChildNodesOnlyParam of(final boolean value) {
+        return value ? TRUE : FALSE;
+    }
+
+    public static @NonNull ChildNodesOnlyParam forUriValue(final String uriValue) {
+        return switch (uriValue) {
+            case "false" -> FALSE;
+            case "true" -> TRUE;
+            default -> throw new IllegalArgumentException("Value can be 'false' or 'true', not '" + uriValue + "'");
+        };
+    }
+
+    @Override
+    public Class<ChildNodesOnlyParam> javaClass() {
+        return ChildNodesOnlyParam.class;
+    }
+
+    @Override
+    public String paramName() {
+        return uriName;
+    }
+
+    @Override
+    public String paramValue() {
+        return String.valueOf(value);
+    }
+
+    public boolean value() {
+        return value;
+    }
+
+    public static @NonNull URI capabilityUri() {
+        return CAPABILITY;
+    }
+}
index 9cd70d6c7a8a7f5de183b7506423f0b08d40fc30..0fc404b24f3179c3ae2ae71a9a4d2f1a9b334a38 100644 (file)
@@ -25,7 +25,9 @@ import org.opendaylight.yangtools.concepts.Immutable;
 public sealed interface RestconfQueryParam<T extends RestconfQueryParam<T>> extends Immutable
         permits ContentParam, DepthParam, FieldsParam, FilterParam, InsertParam, PointParam, WithDefaultsParam,
                 AbstractReplayParam,
-                ChangedLeafNodesOnlyParam, LeafNodesOnlyParam, PrettyPrintParam, SkipNotificationDataParam {
+                // ODL extensions
+                ChangedLeafNodesOnlyParam, LeafNodesOnlyParam, PrettyPrintParam, SkipNotificationDataParam,
+                ChildNodesOnlyParam {
     /**
      * Return the Java representation class.
      *
index ae586892af10b5f4e521fc97abfff3dca9b57a74..56bad1b86d4a6b39007bd27f55df993eeb586d12 100644 (file)
@@ -12,6 +12,7 @@ import com.google.common.base.MoreObjects;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
+import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
 import org.opendaylight.restconf.api.query.FilterParam;
 import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
 import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
@@ -29,33 +30,41 @@ public final class NotificationQueryParams implements Immutable {
     private final StopTimeParam stopTime;
     private final FilterParam filter;
     private final ChangedLeafNodesOnlyParam changedLeafNodesOnly;
+    private final ChildNodesOnlyParam childNodesOnly;
 
     private NotificationQueryParams(final StartTimeParam startTime, final StopTimeParam stopTime,
             final FilterParam filter, final LeafNodesOnlyParam leafNodesOnly,
-            final SkipNotificationDataParam skipNotificationData,
-            final ChangedLeafNodesOnlyParam changedLeafNodesOnly) {
+            final SkipNotificationDataParam skipNotificationData, final ChangedLeafNodesOnlyParam changedLeafNodesOnly,
+            final ChildNodesOnlyParam childNodesOnly) {
         this.startTime = startTime;
         this.stopTime = stopTime;
         this.filter = filter;
         this.leafNodesOnly = leafNodesOnly;
         this.skipNotificationData = skipNotificationData;
         this.changedLeafNodesOnly = changedLeafNodesOnly;
+        this.childNodesOnly = childNodesOnly;
     }
 
     public static @NonNull NotificationQueryParams of(final StartTimeParam startTime, final StopTimeParam stopTime,
             final FilterParam filter, final LeafNodesOnlyParam leafNodesOnly,
-            final SkipNotificationDataParam skipNotificationData,
-            final ChangedLeafNodesOnlyParam changedLeafNodesOnly) {
+            final SkipNotificationDataParam skipNotificationData, final ChangedLeafNodesOnlyParam changedLeafNodesOnly,
+            final ChildNodesOnlyParam childNodesOnly) {
         if (stopTime != null && startTime == null) {
             throw new IllegalArgumentException(StopTimeParam.uriName + " parameter has to be used with "
                 + StartTimeParam.uriName + " parameter");
         }
-        if (changedLeafNodesOnly != null && leafNodesOnly != null) {
-            throw new IllegalArgumentException(ChangedLeafNodesOnlyParam.uriName + " parameter cannot be used with "
-                + LeafNodesOnlyParam.uriName + " parameter");
+        if (changedLeafNodesOnly != null) {
+            if (leafNodesOnly != null) {
+                throw new IllegalArgumentException(ChangedLeafNodesOnlyParam.uriName + " parameter cannot be used with "
+                    + LeafNodesOnlyParam.uriName + " parameter");
+            }
+            if (childNodesOnly != null) {
+                throw new IllegalArgumentException(ChangedLeafNodesOnlyParam.uriName + " parameter cannot be used with "
+                    + ChildNodesOnlyParam.uriName + " parameter");
+            }
         }
         return new NotificationQueryParams(startTime, stopTime, filter, leafNodesOnly, skipNotificationData,
-                changedLeafNodesOnly);
+            changedLeafNodesOnly, childNodesOnly);
     }
 
     /**
@@ -112,6 +121,15 @@ public final class NotificationQueryParams implements Immutable {
         return changedLeafNodesOnly;
     }
 
+    /**
+     * Get odl-child-nodes-only query parameter.
+     *
+     * @return odl-child-nodes-only
+     */
+    public @Nullable ChildNodesOnlyParam childNodesOnly() {
+        return childNodesOnly;
+    }
+
     @Override
     public String toString() {
         final var helper = MoreObjects.toStringHelper(this);
@@ -133,6 +151,9 @@ public final class NotificationQueryParams implements Immutable {
         if (changedLeafNodesOnly != null) {
             helper.add("changedLeafNodesOnly", changedLeafNodesOnly.value());
         }
+        if (childNodesOnly != null) {
+            helper.add("childNodesOnly", childNodesOnly.value());
+        }
         return helper.toString();
     }
 }
index 4c6e77603a11f3de3700381de4989a3ea985b66a..7b47555ce94389da478b1683f0b85f75cd84dbca 100644 (file)
@@ -19,6 +19,7 @@ import javax.ws.rs.core.UriInfo;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
+import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
 import org.opendaylight.restconf.api.query.ContentParam;
 import org.opendaylight.restconf.api.query.DepthParam;
 import org.opendaylight.restconf.api.query.FieldsParam;
@@ -53,7 +54,8 @@ public final class QueryParams {
         InsertParam.uriName, PointParam.uriName,
         // Notifications
         FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName,
-        LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName, ChangedLeafNodesOnlyParam.uriName);
+        LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName, ChangedLeafNodesOnlyParam.uriName,
+        ChildNodesOnlyParam.uriName);
 
     private QueryParams() {
         // Utility class
@@ -66,6 +68,7 @@ public final class QueryParams {
         LeafNodesOnlyParam leafNodesOnly = null;
         SkipNotificationDataParam skipNotificationData = null;
         ChangedLeafNodesOnlyParam changedLeafNodesOnly = null;
+        ChildNodesOnlyParam childNodesOnly = null;
 
         for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
             final String paramName = entry.getKey();
@@ -93,6 +96,9 @@ public final class QueryParams {
                         changedLeafNodesOnly = optionalParam(ChangedLeafNodesOnlyParam::forUriValue, paramName,
                             paramValues);
                         break;
+                    case ChildNodesOnlyParam.uriName:
+                        childNodesOnly = optionalParam(ChildNodesOnlyParam::forUriValue, paramName, paramValues);
+                        break;
                     default:
                         throw unhandledParam("notification", paramName);
                 }
@@ -104,7 +110,7 @@ public final class QueryParams {
 
         try {
             return NotificationQueryParams.of(startTime, stopTime, filter, leafNodesOnly, skipNotificationData,
-                    changedLeafNodesOnly);
+                changedLeafNodesOnly, childNodesOnly);
         } catch (IllegalArgumentException e) {
             throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
         }
index 7fb869506dbac38e5a4aa04f7b0f70a42150e7a2..bd1fa395bb1a3f00709cf813c377e2e690d6b5ab 100644 (file)
@@ -27,6 +27,7 @@ import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
 import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
 import org.opendaylight.restconf.api.query.AbstractReplayParam;
 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
+import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
 import org.opendaylight.restconf.api.query.DepthParam;
 import org.opendaylight.restconf.api.query.FieldsParam;
 import org.opendaylight.restconf.api.query.FilterParam;
@@ -204,6 +205,7 @@ public final class CapabilitiesWriter
             .withChildValue(LeafNodesOnlyParam.capabilityUri().toString())
             .withChildValue(ChangedLeafNodesOnlyParam.capabilityUri().toString())
             .withChildValue(SkipNotificationDataParam.capabilityUri().toString())
+            .withChildValue(ChildNodesOnlyParam.capabilityUri().toString())
             .build();
     }
 }
index beda17a4561452547c0525c686b2d2ad40877352..9cbae2d7ca77787ac198fb8cdf74b61e58c9ebd9 100644 (file)
@@ -66,6 +66,7 @@ abstract class AbstractCommonSubscriber<T> extends AbstractNotificationsData imp
     private boolean leafNodesOnly = false;
     private boolean skipNotificationData = false;
     private boolean changedLeafNodesOnly = false;
+    private boolean childNodesOnly = false;
     private EventFormatter<T> formatter;
 
     AbstractCommonSubscriber(final String streamName, final NotificationOutputType outputType,
@@ -150,6 +151,9 @@ abstract class AbstractCommonSubscriber<T> extends AbstractNotificationsData imp
         final var changedLeafNodes = params.changedLeafNodesOnly();
         changedLeafNodesOnly = changedLeafNodes != null && changedLeafNodes.value();
 
+        final var childNodes = params.childNodesOnly();
+        childNodesOnly = childNodes != null && childNodes.value();
+
         final var filter = params.filter();
         final String filterValue = filter == null ? null : filter.paramValue();
         if (filterValue != null && !filterValue.isEmpty()) {
@@ -190,6 +194,15 @@ abstract class AbstractCommonSubscriber<T> extends AbstractNotificationsData imp
         return skipNotificationData;
     }
 
+    /**
+     * Check whether this query should only notify about child node changes.
+     *
+     * @return true if this query should only notify about child node changes
+     */
+    final boolean getChildNodesOnly() {
+        return childNodesOnly;
+    }
+
     final EventFormatter<T> formatter() {
         return formatter;
     }
index 7cab6107c5ebd4de689b347d7f6118addfc8d0e3..35371d4f9a4e2936f5f9a5913d1245d1e70db7bf 100644 (file)
@@ -51,7 +51,7 @@ abstract class AbstractNotificationListenerAdaptor extends AbstractCommonSubscri
         final Optional<String> maybeOutput;
         try {
             maybeOutput = formatter().eventData(effectiveModel(), notification, eventInstant, getLeafNodesOnly(),
-                isSkipNotificationData(), getChangedLeafNodesOnly());
+                isSkipNotificationData(), getChangedLeafNodesOnly(), getChildNodesOnly());
         } catch (Exception e) {
             LOG.error("Failed to process notification {}", notification, e);
             return;
index e886a722990b8a87682009875f7270f1eeb4ab49..0185e83284ed3cedd14d48d86dcee7254ac95eec 100644 (file)
@@ -44,17 +44,26 @@ abstract class AbstractWebsocketSerializer<T extends Exception> {
     }
 
     public final boolean serialize(final DataTreeCandidate candidate, final boolean leafNodesOnly,
-            final boolean skipData, final boolean changedLeafNodesOnly) throws T {
+            final boolean skipData, final boolean changedLeafNodesOnly, final boolean childNodesOnly) throws T {
         if (leafNodesOnly || changedLeafNodesOnly) {
-            final var path = new ArrayDeque<PathArgument>();
-            path.addAll(candidate.getRootPath().getPathArguments());
-            return serializeLeafNodesOnly(path, candidate.getRootNode(), skipData, changedLeafNodesOnly);
+            return serializeLeafNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData,
+                changedLeafNodesOnly);
+        }
+        if (childNodesOnly) {
+            serializeChildNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData);
+            return true;
         }
 
         serializeData(candidate.getRootPath().getPathArguments(), candidate.getRootNode(), skipData);
         return true;
     }
 
+    private static Deque<PathArgument> mutableRootPath(final DataTreeCandidate candidate) {
+        final var ret = new ArrayDeque<PathArgument>();
+        ret.addAll(candidate.getRootPath().getPathArguments());
+        return ret;
+    }
+
     final boolean serializeLeafNodesOnly(final Deque<PathArgument> path, final DataTreeCandidateNode candidate,
             final boolean skipData, final boolean changedLeafNodesOnly) throws T {
         final var node = switch (candidate.modificationType()) {
@@ -111,6 +120,22 @@ abstract class AbstractWebsocketSerializer<T extends Exception> {
         return ret;
     }
 
+    private void serializeChildNodesOnly(final Deque<PathArgument> path, final DataTreeCandidateNode current,
+            final boolean skipData) throws T {
+        switch (current.modificationType()) {
+            // just a subtree modification, recurse
+            case SUBTREE_MODIFIED -> {
+                for (var child : current.childNodes()) {
+                    path.add(child.name());
+                    serializeChildNodesOnly(path, child, skipData);
+                    path.removeLast();
+                }
+            }
+            // other modification, serialize it
+            default -> serializeData(path, current, skipData);
+        }
+    }
+
     private void serializeData(final Collection<PathArgument> dataPath, final DataTreeCandidateNode candidate,
             final boolean skipData) throws T {
         var stack = SchemaInferenceStack.of(context);
index aeeaedf601a04a2fe305a575420e4f4fd0cdce3d..89584ef818f020e65d88ca41bca028bdb3390fa3 100644 (file)
@@ -55,7 +55,7 @@ abstract class EventFormatter<T> implements Immutable {
     private final XPathExpression filter;
 
     EventFormatter()  {
-        this.filter = null;
+        filter = null;
     }
 
     EventFormatter(final String xpathFilter)  throws XPathExpressionException {
@@ -68,14 +68,13 @@ abstract class EventFormatter<T> implements Immutable {
     }
 
     final Optional<String> eventData(final EffectiveModelContext schemaContext, final T input, final Instant now,
-                                     final boolean leafNodesOnly, final boolean skipData,
-                                     final boolean changedLeafNodesOnly)
-            throws Exception {
+            final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
+            final boolean childNodeOnly) throws Exception {
         if (!filterMatches(schemaContext, input, now)) {
             return Optional.empty();
         }
         return Optional.ofNullable(
-                createText(schemaContext, input, now, leafNodesOnly, skipData, changedLeafNodesOnly));
+                createText(schemaContext, input, now, leafNodesOnly, skipData, changedLeafNodesOnly, childNodeOnly));
     }
 
     /**
@@ -97,11 +96,12 @@ abstract class EventFormatter<T> implements Immutable {
      * @param leafNodesOnly option to include only leaves in the result
      * @param skipData option to skip data in the result, only paths would be included
      * @param changedLeafNodesOnly  option to include only changed leaves in the result
+     * @param childNodesOnly option to include only children in the result
      * @return String representation of the formatted data
      * @throws Exception if the underlying formatters fail to export the data to the requested format
      */
     abstract String createText(EffectiveModelContext schemaContext, T input, Instant now, boolean leafNodesOnly,
-                               boolean skipData, boolean changedLeafNodesOnly) throws Exception;
+        boolean skipData, boolean changedLeafNodesOnly, boolean childNodeOnly) throws Exception;
 
     private boolean filterMatches(final EffectiveModelContext schemaContext, final T input, final Instant now)
             throws IOException {
index 62f82329eee976dd7e56e88b9b8bdd36251ebb3a..226db6a7a4a36c8d8580506727154063f04fa2eb 100644 (file)
@@ -54,9 +54,8 @@ public final class JSONDataTreeCandidateFormatter extends DataTreeCandidateForma
 
     @Override
     String createText(final EffectiveModelContext schemaContext, final Collection<DataTreeCandidate> input,
-                      final Instant now, final boolean leafNodesOnly, final boolean skipData,
-                      final boolean changedLeafNodesOnly)
-            throws IOException {
+            final Instant now, final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
+            final boolean childNodesOnly) throws IOException {
         final Writer writer = new StringWriter();
         final JsonWriter jsonWriter = new JsonWriter(writer).beginObject();
 
@@ -67,7 +66,7 @@ public final class JSONDataTreeCandidateFormatter extends DataTreeCandidateForma
         final var serializer = new JsonDataTreeCandidateSerializer(schemaContext, codecSupplier, jsonWriter);
         boolean nonEmpty = false;
         for (var candidate : input) {
-            nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly);
+            nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly, childNodesOnly);
         }
 
         // data-change-event
index ee0d2fcc836aa6e9c7d2e2a3e65bc1265871089a..9f49866218c7cdf789aff65d43589d878e56ce98 100644 (file)
@@ -51,8 +51,8 @@ final class JSONNotificationFormatter extends NotificationFormatter {
 
     @Override
     String createText(final EffectiveModelContext schemaContext, final DOMNotification input, final Instant now,
-                      final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly)
-            throws IOException {
+            final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
+            final boolean childNodesOnly) throws IOException {
         final Writer writer = new StringWriter();
         final JsonWriter jsonWriter = new JsonWriter(writer).beginObject();
         jsonWriter.name("ietf-restconf:notification").beginObject();
index 22a422f2bc41eb867b2ddb123b0625da171c937d..38f767d10165f9591fa4e5c72f7844901be17476 100644 (file)
@@ -76,7 +76,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber<Collection<DataTre
         final Optional<String> maybeData;
         try {
             maybeData = formatter().eventData(databindProvider.currentContext().modelContext(), dataTreeCandidates, now,
-                getLeafNodesOnly(), isSkipNotificationData(), getChangedLeafNodesOnly());
+                getLeafNodesOnly(), isSkipNotificationData(), getChangedLeafNodesOnly(), getChildNodesOnly());
         } catch (final Exception e) {
             LOG.error("Failed to process notification {}",
                     dataTreeCandidates.stream().map(Object::toString).collect(Collectors.joining(",")), e);
index 34363b16cd3f940ccd64336723301b39548e38ce..1d16ce3d72f96e8ae6ffb1f25e970887a16a7c5e 100644 (file)
@@ -46,8 +46,8 @@ public final class XMLDataTreeCandidateFormatter extends DataTreeCandidateFormat
 
     @Override
     String createText(final EffectiveModelContext schemaContext, final Collection<DataTreeCandidate> input,
-                      final Instant now, final boolean leafNodesOnly, final boolean skipData,
-                      final boolean changedLeafNodesOnly) throws Exception {
+            final Instant now, final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
+            final boolean childNodeOnly) throws Exception {
         final var writer = new StringWriter();
         boolean nonEmpty = false;
         try {
@@ -58,7 +58,8 @@ public final class XMLDataTreeCandidateFormatter extends DataTreeCandidateFormat
 
             final var serializer = new XmlDataTreeCandidateSerializer(schemaContext, xmlStreamWriter);
             for (var candidate : input) {
-                nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly);
+                nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly,
+                    childNodeOnly);
             }
 
             // data-changed-notification
index 1e1a04e62b9dfcf64dcca199cd982ed00f01e11b..ca00050c7cf3c2cb4108c4a779e8b58899b4b5e9 100644 (file)
@@ -42,8 +42,8 @@ final class XMLNotificationFormatter extends NotificationFormatter {
 
     @Override
     String createText(final EffectiveModelContext schemaContext, final DOMNotification input, final Instant now,
-                      final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly)
-            throws IOException {
+            final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
+            final boolean childNodesOnly) throws IOException {
         final var writer = new StringWriter();
 
         try {
index 22543d1344433b8f5214e8c4c68161df33bad27b..834deec89b6605d1696de15dec1781bee85c555e 100644 (file)
@@ -31,6 +31,7 @@ public class CapabilitiesWriterTest {
                 equalTo("urn:opendaylight:params:restconf:capability:pretty-print:1.0"),
                 equalTo("urn:opendaylight:params:restconf:capability:leaf-nodes-only:1.0"),
                 equalTo("urn:opendaylight:params:restconf:capability:changed-leaf-nodes-only:1.0"),
-                equalTo("urn:opendaylight:params:restconf:capability:skip-notification-data:1.0")));
+                equalTo("urn:opendaylight:params:restconf:capability:skip-notification-data:1.0"),
+                equalTo("urn:opendaylight:params:restconf:capability:child-nodes-only:1.0")));
     }
 }
index 768bd51afd2032ba3ec1e425f309a950f5515277..0744abdfd34f93a0032f406448eea2dbd2ef11db 100644 (file)
@@ -164,6 +164,6 @@ public class JsonNotificationListenerTest extends AbstractNotificationListenerTe
         final NotificationListenerAdapter notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
                 schemaPathNotifi, "json-stream", NotificationOutputType.JSON);
         return notifiAdapter.formatter()
-                .eventData(SCHEMA_CONTEXT, notificationData, Instant.now(), false, false, false).orElseThrow();
+                .eventData(SCHEMA_CONTEXT, notificationData, Instant.now(), false, false, false, false).orElseThrow();
     }
 }
index 5dedfdcf47ad731d595719fe219d72436cc440a1..24abcccef4828935c7daa6c9cba4d6cc5b3755fe 100644 (file)
@@ -161,7 +161,7 @@ public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
                               final boolean leafNodesOnly, final boolean skipNotificationData) {
             super(path, streamName, outputType);
             setQueryParams(NotificationQueryParams.of(StartTimeParam.forUriValue("1970-01-01T00:00:00Z"), null, null,
-                LeafNodesOnlyParam.of(leafNodesOnly), SkipNotificationDataParam.of(skipNotificationData), null));
+                LeafNodesOnlyParam.of(leafNodesOnly), SkipNotificationDataParam.of(skipNotificationData), null, null));
         }
 
         ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
@@ -169,7 +169,7 @@ public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
                               final boolean changedLeafNodesOnly) {
             super(path, streamName, outputType);
             setQueryParams(NotificationQueryParams.of(StartTimeParam.forUriValue("1970-01-01T00:00:00Z"), null, null,
-                    null, null, ChangedLeafNodesOnlyParam.of(changedLeafNodesOnly)));
+                    null, null, ChangedLeafNodesOnlyParam.of(changedLeafNodesOnly), null));
         }
 
         @Override
index 55aab239a71ad57ac1046d59a28e5b03977d9a89..2928898b7218cee2bf1b04328b0317461008dad7 100644 (file)
@@ -171,6 +171,6 @@ public class XmlNotificationListenerTest extends AbstractNotificationListenerTes
         final NotificationListenerAdapter notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
                 schemaPathNotifi, "xml-stream", NotificationOutputTypeGrouping.NotificationOutputType.XML);
         return notifiAdapter.formatter().eventData(SCHEMA_CONTEXT, notificationData, Instant.now(), false,
-                false, false).orElseThrow();
+                false, false, false).orElseThrow();
     }
 }