2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.restconf.server.mdsal.streams.dtcl;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.VerifyException;
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Comparator;
18 import java.util.Deque;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.restconf.server.spi.TextParameters;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.data.changed.notification.DataChangeEvent;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.data.changed.notification.DataChangeEvent.Operation;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.remote.rev140114.data.changed.notification.data.change.event.Data;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
28 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
35 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
36 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
37 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
38 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
39 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
40 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
41 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 abstract class DataTreeCandidateSerializer<T extends Exception> {
46 private static final Logger LOG = LoggerFactory.getLogger(DataTreeCandidateSerializer.class);
47 static final @NonNull QName PATH_QNAME = QName.create(DataChangeEvent.QNAME, "path").intern();
48 static final @NonNull NodeIdentifier PATH_NID = NodeIdentifier.create(PATH_QNAME);
49 static final @NonNull QName OPERATION_QNAME = QName.create(DataChangeEvent.QNAME, "operation").intern();
50 static final @NonNull NodeIdentifier OPERATION_NID = NodeIdentifier.create(OPERATION_QNAME);
51 static final @NonNull String DATA_NAME = Data.QNAME.getLocalName();
53 private final EffectiveModelContext context;
55 DataTreeCandidateSerializer(final EffectiveModelContext context) {
56 this.context = requireNonNull(context);
59 final boolean serialize(final DataTreeCandidate candidate, final TextParameters params) throws T {
60 final var skipData = params.skipData();
61 final var changedLeafNodesOnly = params.changedLeafNodesOnly();
62 if (changedLeafNodesOnly || params.leafNodesOnly()) {
63 return serializeLeafNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData,
64 changedLeafNodesOnly);
66 if (params.childNodesOnly()) {
67 return serializeChildNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData);
70 serializeData(candidate.getRootPath().getPathArguments(), candidate.getRootNode(), skipData);
74 private static Deque<PathArgument> mutableRootPath(final DataTreeCandidate candidate) {
75 final var ret = new ArrayDeque<PathArgument>();
76 ret.addAll(candidate.getRootPath().getPathArguments());
80 final boolean serializeLeafNodesOnly(final Deque<PathArgument> path, final DataTreeCandidateNode candidate,
81 final boolean skipData, final boolean changedLeafNodesOnly) throws T {
82 final var node = switch (candidate.modificationType()) {
83 case SUBTREE_MODIFIED, APPEARED -> candidate.getDataAfter();
84 case DELETE, DISAPPEARED -> candidate.getDataBefore();
85 case WRITE -> changedLeafNodesOnly && isNotUpdate(candidate) ? null : candidate.getDataAfter();
87 // no reason to do anything with an unmodified node
88 LOG.debug("DataTreeCandidate for a notification is unmodified, not serializing leaves. Candidate: {}",
97 if (node instanceof LeafNode || node instanceof LeafSetNode) {
98 serializeData(path, candidate, skipData);
102 // Retain a modicum of sanity here: children may come from different namespaces. Report children from the same
103 // namespace first, holding others back. Once that is done, sort the remaining children by their PathArgument
104 // and report them in that order.
105 final var myNamespace = node.name().getNodeType().getModule();
106 final var heldBack = new ArrayList<DataTreeCandidateNode>();
108 for (var childNode : candidate.childNodes()) {
109 final var childName = childNode.name();
110 if (myNamespace.equals(childName.getNodeType().getModule())) {
111 ret |= serializeChild(path, childNode, skipData, changedLeafNodesOnly);
113 heldBack.add(childNode);
116 if (!heldBack.isEmpty()) {
117 // This is not exactly nice, as we really should be using schema definition order, but we do not have it
118 // available here, so we fall back to the next best thing.
119 heldBack.sort(Comparator.comparing(DataTreeCandidateNode::name));
120 for (var childNode : heldBack) {
121 ret |= serializeChild(path, childNode, skipData, changedLeafNodesOnly);
127 private boolean serializeChild(final Deque<PathArgument> path, final DataTreeCandidateNode childNode,
128 final boolean skipData, final boolean changedLeafNodesOnly) throws T {
130 path.add(childNode.name());
131 ret = serializeLeafNodesOnly(path, childNode, skipData, changedLeafNodesOnly);
136 private boolean serializeChildNodesOnly(final Deque<PathArgument> path, final DataTreeCandidateNode current,
137 final boolean skipData) throws T {
138 return switch (current.modificationType()) {
139 case APPEARED, WRITE -> serializeChildNodesOnlyTerminal(path, current, skipData, current.getDataAfter());
140 case DISAPPEARED, DELETE -> serializeChildNodesOnlyTerminal(path, current, skipData,
141 current.getDataBefore());
142 case SUBTREE_MODIFIED -> serializeChildNodesOnlyChildren(path, current, skipData);
143 case UNMODIFIED -> false;
147 private boolean serializeChildNodesOnlyTerminal(final Deque<PathArgument> path, final DataTreeCandidateNode current,
148 final boolean skipData, final NormalizedNode data) throws T {
149 if (data instanceof ChoiceNode || data instanceof LeafSetNode || data instanceof MapNode) {
150 return serializeChildNodesOnlyChildren(path, current, skipData);
153 final var updated = !isNotUpdate(current);
155 serializeData(path, current, skipData);
160 private boolean serializeChildNodesOnlyChildren(final Deque<PathArgument> path, final DataTreeCandidateNode current,
161 final boolean skipData) throws T {
163 for (var child : current.childNodes()) {
164 path.add(child.name());
165 updated |= serializeChildNodesOnly(path, child, skipData);
171 private void serializeData(final Collection<PathArgument> dataPath, final DataTreeCandidateNode candidate,
172 final boolean skipData) throws T {
173 var stack = SchemaInferenceStack.of(context);
174 DataSchemaContext current = DataSchemaContextTree.from(context).getRoot();
175 for (var arg : dataPath) {
176 final var next = current instanceof DataSchemaContext.Composite composite ? composite.enterChild(stack, arg)
178 current = verifyNotNull(next, "Failed to resolve %s: cannot find %s in %s", dataPath, arg, current);
181 // Exit to parent if needed
182 if (!stack.isEmpty()) {
186 serializeData(stack.toInference(), dataPath, candidate, skipData);
189 abstract void serializeData(Inference parent, Collection<PathArgument> dataPath, DataTreeCandidateNode candidate,
190 boolean skipData) throws T;
192 private static boolean isNotUpdate(final DataTreeCandidateNode node) {
193 final var before = node.dataBefore();
194 final var after = node.dataAfter();
196 return before != null && after != null && before.body().equals(after.body());
199 static final @Nullable NormalizedNode getDataAfter(final DataTreeCandidateNode candidate) {
200 final var data = candidate.dataAfter();
201 if (data instanceof MapEntryNode mapEntry) {
202 return ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).withChild(mapEntry).build();
207 static final @NonNull String modificationTypeToOperation(final DataTreeCandidateNode candidate) {
208 final var operation = switch (candidate.modificationType()) {
209 case APPEARED, SUBTREE_MODIFIED, WRITE -> candidate.dataBefore() != null ? Operation.Updated
211 case DELETE, DISAPPEARED -> Operation.Deleted;
213 throw new VerifyException("DataTreeCandidate for a notification is unmodified. Candidate: "
217 return operation.getName();