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