Remove use NodeIdentifierWithPredices.getKeyValues()
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / jersey / providers / ParameterAwareNormalizedNodeWriter.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, 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.jersey.providers;
9
10 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.Map.Entry;
19 import java.util.Optional;
20 import java.util.Set;
21 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
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.api.schema.OrderedLeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * This is an experimental iterator over a {@link NormalizedNode}. This is essentially
44  * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
45  * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
46  * us to write multiple nodes.
47  */
48 @Beta
49 public class ParameterAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
50     private static final QName ROOT_DATA_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "data");
51
52     private final NormalizedNodeStreamWriter writer;
53     private final Integer maxDepth;
54     protected final List<Set<QName>> fields;
55     protected int currentDepth = 0;
56
57     private ParameterAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final Integer maxDepth,
58                                                final List<Set<QName>> fields) {
59         this.writer = Preconditions.checkNotNull(writer);
60         this.maxDepth = maxDepth;
61         this.fields = fields;
62     }
63
64     protected final NormalizedNodeStreamWriter getWriter() {
65         return writer;
66     }
67
68     /**
69      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
70      *
71      * @param writer Back-end writer
72      * @param maxDepth Maximal depth to write
73      * @param fields Selected child nodes to write
74      * @return A new instance.
75      */
76     public static ParameterAwareNormalizedNodeWriter forStreamWriter(
77             final NormalizedNodeStreamWriter writer, final Integer maxDepth, final List<Set<QName>> fields) {
78         return forStreamWriter(writer, true,  maxDepth, fields);
79     }
80
81     /**
82      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple
83      * {@link #forStreamWriter(NormalizedNodeStreamWriter, Integer, List)}
84      * method, this allows the caller to switch off RFC6020 XML compliance, providing better
85      * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
86      * to emit leaf nodes which participate in a list's key first and in the order in which
87      * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
88      * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
89      * for each key and then for each emitted node we need to check whether it was already
90      * emitted.
91      *
92      * @param writer Back-end writer
93      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
94      * @param maxDepth Maximal depth to write
95      * @param fields Selected child nodes to write
96      * @return A new instance.
97      */
98     public static ParameterAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
99                                                                      final boolean orderKeyLeaves,
100                                                                      final Integer maxDepth,
101                                                                      final List<Set<QName>> fields) {
102         return orderKeyLeaves ? new OrderedParameterAwareNormalizedNodeWriter(writer, maxDepth, fields)
103                 : new ParameterAwareNormalizedNodeWriter(writer, maxDepth, fields);
104     }
105
106     /**
107      * Iterate over the provided {@link NormalizedNode} and emit write
108      * events to the encapsulated {@link NormalizedNodeStreamWriter}.
109      *
110      * @param node Node
111      * @return {@code ParameterAwareNormalizedNodeWriter}
112      * @throws IOException when thrown from the backing writer.
113      */
114     @Override
115     public final ParameterAwareNormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
116         if (wasProcessedAsCompositeNode(node)) {
117             return this;
118         }
119
120         if (wasProcessAsSimpleNode(node)) {
121             return this;
122         }
123
124         throw new IllegalStateException("It wasn't possible to serialize node " + node);
125     }
126
127     @Override
128     public void flush() throws IOException {
129         writer.flush();
130     }
131
132     @Override
133     public void close() throws IOException {
134         writer.flush();
135         writer.close();
136     }
137
138     /**
139      * Emit a best guess of a hint for a particular set of children. It evaluates the
140      * iterable to see if the size can be easily gotten to. If it is, we hint at the
141      * real number of child nodes. Otherwise we emit UNKNOWN_SIZE.
142      *
143      * @param children Child nodes
144      * @return Best estimate of the collection size required to hold all the children.
145      */
146     static final int childSizeHint(final Iterable<?> children) {
147         return children instanceof Collection ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
148     }
149
150     private boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
151         if (node instanceof LeafSetEntryNode) {
152             if (selectedByParameters(node, false)) {
153                 final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
154                 writer.startLeafSetEntryNode(nodeAsLeafList.getIdentifier());
155                 writer.scalarValue(nodeAsLeafList.getValue());
156                 writer.endNode();
157             }
158             return true;
159         } else if (node instanceof LeafNode) {
160             final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
161             writer.startLeafNode(nodeAsLeaf.getIdentifier());
162             writer.scalarValue(nodeAsLeaf.getValue());
163             writer.endNode();
164             return true;
165         } else if (node instanceof AnyXmlNode) {
166             final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
167             writer.startAnyxmlNode(anyXmlNode.getIdentifier());
168             writer.domSourceValue(anyXmlNode.getValue());
169             writer.endNode();
170             return true;
171         }
172
173         return false;
174     }
175
176     /**
177      * Check if node should be written according to parameters fields and depth.
178      * See <a href="https://tools.ietf.org/html/draft-ietf-netconf-restconf-18#page-49">Restconf draft</a>.
179      * @param node Node to be written
180      * @param mixinParent {@code true} if parent is mixin, {@code false} otherwise
181      * @return {@code true} if node will be written, {@code false} otherwise
182      */
183     protected boolean selectedByParameters(final NormalizedNode<?, ?> node, final boolean mixinParent) {
184         // nodes to be written are not limited by fields, only by depth
185         if (fields == null) {
186             return maxDepth == null || currentDepth < maxDepth;
187         }
188
189         // children of mixin nodes are never selected in fields but must be written if they are first in selected target
190         if (mixinParent && currentDepth == 0) {
191             return true;
192         }
193
194         // always write augmentation nodes
195         if (node instanceof AugmentationNode) {
196             return true;
197         }
198
199         // write only selected nodes
200         if (currentDepth > 0 && currentDepth <= fields.size()) {
201             return fields.get(currentDepth - 1).contains(node.getNodeType());
202         }
203
204         // after this depth only depth parameter is used to determine when to write node
205         return maxDepth == null || currentDepth < maxDepth;
206     }
207
208     /**
209      * Emit events for all children and then emit an endNode() event.
210      *
211      * @param children Child iterable
212      * @param mixinParent {@code true} if parent is mixin, {@code false} otherwise
213      * @return True
214      * @throws IOException when the writer reports it
215      */
216     protected final boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children,
217                                           final boolean mixinParent) throws IOException {
218         for (final NormalizedNode<?, ?> child : children) {
219             if (selectedByParameters(child, mixinParent)) {
220                 write(child);
221             }
222         }
223         writer.endNode();
224         return true;
225     }
226
227     protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
228         if (selectedByParameters(mapEntryNode, false)) {
229             writeChildren(mapEntryNode.getValue(), false);
230         } else if (fields == null && maxDepth != null && currentDepth == maxDepth) {
231             writeOnlyKeys(mapEntryNode.getIdentifier().entrySet());
232         }
233         return true;
234     }
235
236     private void writeOnlyKeys(final Set<Entry<QName, Object>> entries) throws IOException {
237         for (final Entry<QName, Object> entry : entries) {
238             writer.startLeafNode(new NodeIdentifier(entry.getKey()));
239             writer.scalarValue(entry.getValue());
240             writer.endNode();
241         }
242         writer.endNode();
243     }
244
245     protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
246         writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
247         currentDepth++;
248         writeMapEntryChildren(node);
249         currentDepth--;
250         return true;
251     }
252
253     private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
254         boolean processedAsCompositeNode = false;
255         if (node instanceof ContainerNode) {
256             final ContainerNode n = (ContainerNode) node;
257             if (!n.getNodeType().withoutRevision().equals(ROOT_DATA_QNAME)) {
258                 writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()));
259                 currentDepth++;
260                 processedAsCompositeNode = writeChildren(n.getValue(), false);
261                 currentDepth--;
262             } else {
263                 // write child nodes of data root container
264                 for (final NormalizedNode<?, ?> child : n.getValue()) {
265                     currentDepth++;
266                     if (selectedByParameters(child, false)) {
267                         write(child);
268                     }
269                     currentDepth--;
270                     processedAsCompositeNode = true;
271                 }
272             }
273         } else if (node instanceof MapEntryNode) {
274             processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
275         } else if (node instanceof UnkeyedListEntryNode) {
276             final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
277             writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue()));
278             currentDepth++;
279             processedAsCompositeNode = writeChildren(n.getValue(), false);
280             currentDepth--;
281         } else if (node instanceof ChoiceNode) {
282             final ChoiceNode n = (ChoiceNode) node;
283             writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue()));
284             processedAsCompositeNode = writeChildren(n.getValue(), true);
285         } else if (node instanceof AugmentationNode) {
286             final AugmentationNode n = (AugmentationNode) node;
287             writer.startAugmentationNode(n.getIdentifier());
288             processedAsCompositeNode = writeChildren(n.getValue(), true);
289         } else if (node instanceof UnkeyedListNode) {
290             final UnkeyedListNode n = (UnkeyedListNode) node;
291             writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue()));
292             processedAsCompositeNode = writeChildren(n.getValue(), false);
293         } else if (node instanceof OrderedMapNode) {
294             final OrderedMapNode n = (OrderedMapNode) node;
295             writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
296             processedAsCompositeNode = writeChildren(n.getValue(), true);
297         } else if (node instanceof MapNode) {
298             final MapNode n = (MapNode) node;
299             writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
300             processedAsCompositeNode = writeChildren(n.getValue(), true);
301         } else if (node instanceof LeafSetNode) {
302             final LeafSetNode<?> n = (LeafSetNode<?>) node;
303             if (node instanceof OrderedLeafSetNode) {
304                 writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
305             } else {
306                 writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
307             }
308             currentDepth++;
309             processedAsCompositeNode = writeChildren(n.getValue(), true);
310             currentDepth--;
311         }
312
313         return processedAsCompositeNode;
314     }
315
316     private static final class OrderedParameterAwareNormalizedNodeWriter extends ParameterAwareNormalizedNodeWriter {
317         private static final Logger LOG = LoggerFactory.getLogger(OrderedParameterAwareNormalizedNodeWriter.class);
318
319         OrderedParameterAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final Integer maxDepth,
320                                                   final List<Set<QName>> fields) {
321             super(writer, maxDepth, fields);
322         }
323
324         @Override
325         protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
326             final NormalizedNodeStreamWriter writer = getWriter();
327             writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
328
329             final Set<QName> qnames = node.getIdentifier().keySet();
330             // Write out all the key children
331             currentDepth++;
332             for (final QName qname : qnames) {
333                 final Optional<? extends NormalizedNode<?, ?>> child = node.getChild(new NodeIdentifier(qname));
334                 if (child.isPresent()) {
335                     if (selectedByParameters(child.get(), false)) {
336                         write(child.get());
337                     }
338                 } else {
339                     LOG.info("No child for key element {} found", qname);
340                 }
341             }
342             currentDepth--;
343
344             currentDepth++;
345             // Write all the rest
346             final boolean result =
347                     writeChildren(Iterables.filter(node.getValue(), input -> {
348                         if (input instanceof AugmentationNode) {
349                             return true;
350                         }
351                         if (!qnames.contains(input.getNodeType())) {
352                             return true;
353                         }
354
355                         LOG.debug("Skipping key child {}", input);
356                         return false;
357                     }), false);
358             currentDepth--;
359             return result;
360         }
361     }
362 }