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