Further warnings mitigation
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / mdsal / streams / dtcl / DataTreeCandidateSerializer.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.server.mdsal.streams.dtcl;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
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.spi.node.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;
44
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();
52
53     private final EffectiveModelContext context;
54
55     DataTreeCandidateSerializer(final EffectiveModelContext context) {
56         this.context = requireNonNull(context);
57     }
58
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);
65         }
66         if (params.childNodesOnly()) {
67             return serializeChildNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData);
68         }
69
70         serializeData(candidate.getRootPath().getPathArguments(), candidate.getRootNode(), skipData);
71         return true;
72     }
73
74     private static Deque<PathArgument> mutableRootPath(final DataTreeCandidate candidate) {
75         final var ret = new ArrayDeque<PathArgument>();
76         ret.addAll(candidate.getRootPath().getPathArguments());
77         return ret;
78     }
79
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();
86             case UNMODIFIED -> {
87                 // no reason to do anything with an unmodified node
88                 LOG.debug("DataTreeCandidate for a notification is unmodified, not serializing leaves. Candidate: {}",
89                         candidate);
90                 yield null;
91             }
92         };
93
94         if (node == null) {
95             return false;
96         }
97         if (node instanceof LeafNode || node instanceof LeafSetNode) {
98             serializeData(path, candidate, skipData);
99             return true;
100         }
101
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>();
107         boolean ret = false;
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);
112             } else {
113                 heldBack.add(childNode);
114             }
115         }
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);
122             }
123         }
124         return ret;
125     }
126
127     private boolean serializeChild(final Deque<PathArgument> path, final DataTreeCandidateNode childNode,
128             final boolean skipData, final boolean changedLeafNodesOnly) throws T {
129         final boolean ret;
130         path.add(childNode.name());
131         ret = serializeLeafNodesOnly(path, childNode, skipData, changedLeafNodesOnly);
132         path.removeLast();
133         return ret;
134     }
135
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;
144         };
145     }
146
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);
151         }
152
153         final var updated = !isNotUpdate(current);
154         if (updated) {
155             serializeData(path, current, skipData);
156         }
157         return updated;
158     }
159
160     private boolean serializeChildNodesOnlyChildren(final Deque<PathArgument> path, final DataTreeCandidateNode current,
161             final boolean skipData) throws T {
162         var updated = false;
163         for (var child : current.childNodes()) {
164             path.add(child.name());
165             updated |= serializeChildNodesOnly(path, child, skipData);
166             path.removeLast();
167         }
168         return updated;
169     }
170
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)
177                 : null;
178             current = verifyNotNull(next,  "Failed to resolve %s: cannot find %s in %s", dataPath, arg, current);
179         }
180
181         // Exit to parent if needed
182         if (!stack.isEmpty()) {
183             stack.exit();
184         }
185
186         serializeData(stack.toInference(), dataPath, candidate, skipData);
187     }
188
189     abstract void serializeData(Inference parent, Collection<PathArgument> dataPath, DataTreeCandidateNode candidate,
190         boolean skipData) throws T;
191
192     private static boolean isNotUpdate(final DataTreeCandidateNode node) {
193         final var before = node.dataBefore();
194         final var after = node.dataAfter();
195
196         return before != null && after != null && before.body().equals(after.body());
197     }
198
199     static final @Nullable NormalizedNode getDataAfter(final DataTreeCandidateNode candidate) {
200         final var data = candidate.dataAfter();
201         if (data instanceof MapEntryNode mapEntry) {
202             return ImmutableNodes.newSystemMapBuilder()
203                 .withNodeIdentifier(new NodeIdentifier(data.name().getNodeType()))
204                 .withChild(mapEntry)
205                 .build();
206         }
207         return data;
208     }
209
210     static final @NonNull String modificationTypeToOperation(final DataTreeCandidateNode candidate) {
211         final var operation = switch (candidate.modificationType()) {
212             case APPEARED, SUBTREE_MODIFIED, WRITE -> candidate.dataBefore() != null ? Operation.Updated
213                 : Operation.Created;
214             case DELETE, DISAPPEARED -> Operation.Deleted;
215             case UNMODIFIED -> {
216                 throw new VerifyException("DataTreeCandidate for a notification is unmodified. Candidate: "
217                     + candidate);
218             }
219         };
220         return operation.getName();
221     }
222 }