b9d7a7a61a45303ea13905a2f6630ed512fa7434
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / rest / impl / DepthAwareNormalizedNodeWriter.java
1 /*
2  * Copyright (c) 2015 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.netconf.sal.rest.impl;
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.collect.Iterables;
14 import java.io.IOException;
15 import java.util.Collection;
16 import java.util.Map.Entry;
17 import java.util.Optional;
18 import java.util.Set;
19 import javax.xml.transform.dom.DOMSource;
20 import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.UserLeafSetNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * This is an experimental iterator over a {@link NormalizedNode}. This is essentially the opposite of a
43  * {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over the backing data, this
44  * encapsulates a {@link NormalizedNodeStreamWriter} and allows us to write multiple nodes.
45  *
46  * @deprecated This class will be replaced by ParameterAwareNormalizedNodeWriter in restconf-nb-rfc8040
47  */
48 @Deprecated
49 public class DepthAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
50     private final NormalizedNodeStreamWriter writer;
51     protected int currentDepth = 0;
52     protected final int maxDepth;
53
54     private DepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
55         this.writer = requireNonNull(writer);
56         this.maxDepth = maxDepth;
57     }
58
59     protected final NormalizedNodeStreamWriter getWriter() {
60         return writer;
61     }
62
63     /**
64      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
65      *
66      * @param writer Back-end writer
67      * @return A new instance.
68      */
69     public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
70                                                                  final int maxDepth) {
71         return forStreamWriter(writer, true,  maxDepth);
72     }
73
74     /**
75      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
76      * Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter, int)}
77      * method, this allows the caller to switch off RFC6020 XML compliance, providing better
78      * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
79      * to emit leaf nodes which participate in a list's key first and in the order in which
80      * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
81      * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
82      * for each key and then for each emitted node we need to check whether it was already
83      * emitted.
84      *
85      * @param writer Back-end writer
86      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
87      * @return A new instance.
88      */
89     public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
90                                                                  final boolean orderKeyLeaves, final int maxDepth) {
91         return orderKeyLeaves ? new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth)
92                 : new DepthAwareNormalizedNodeWriter(writer, maxDepth);
93     }
94
95     /**
96      * Iterate over the provided {@link NormalizedNode} and emit write
97      * events to the encapsulated {@link NormalizedNodeStreamWriter}.
98      *
99      * @param node Node
100      * @return DepthAwareNormalizedNodeWriter
101      * @throws IOException when thrown from the backing writer.
102      */
103     @Override
104     public final DepthAwareNormalizedNodeWriter write(final NormalizedNode node) throws IOException {
105         if (wasProcessedAsCompositeNode(node)) {
106             return this;
107         }
108
109         if (wasProcessAsSimpleNode(node)) {
110             return this;
111         }
112
113         throw new IllegalStateException("It wasn't possible to serialize node " + node);
114     }
115
116     @Override
117     public void flush() throws IOException {
118         writer.flush();
119     }
120
121     @Override
122     public void close() throws IOException {
123         writer.flush();
124         writer.close();
125     }
126
127     /**
128      * Emit a best guess of a hint for a particular set of children. It evaluates the
129      * iterable to see if the size can be easily gotten to. If it is, we hint at the
130      * real number of child nodes. Otherwise we emit UNKNOWN_SIZE.
131      *
132      * @param children Child nodes
133      * @return Best estimate of the collection size required to hold all the children.
134      */
135     static final int childSizeHint(final Iterable<?> children) {
136         return children instanceof Collection ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
137     }
138
139     private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException {
140         if (node instanceof LeafSetEntryNode) {
141             if (currentDepth < maxDepth) {
142                 final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
143                 writer.startLeafSetEntryNode(nodeAsLeafList.getIdentifier());
144                 writer.scalarValue(nodeAsLeafList.body());
145                 writer.endNode();
146             }
147             return true;
148         } else if (node instanceof LeafNode) {
149             final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
150             writer.startLeafNode(nodeAsLeaf.getIdentifier());
151             writer.scalarValue(nodeAsLeaf.body());
152             writer.endNode();
153             return true;
154         } else if (node instanceof AnyxmlNode) {
155             final AnyxmlNode<?> anyxmlNode = (AnyxmlNode<?>)node;
156             final Class<?> objectModel = anyxmlNode.bodyObjectModel();
157             if (writer.startAnyxmlNode(anyxmlNode.getIdentifier(), objectModel)) {
158                 if (DOMSource.class.isAssignableFrom(objectModel)) {
159                     writer.domSourceValue((DOMSource) anyxmlNode.body());
160                 } else {
161                     writer.scalarValue(anyxmlNode.body());
162                 }
163                 writer.endNode();
164             }
165             return true;
166         }
167
168         return false;
169     }
170
171     /**
172      * Emit events for all children and then emit an endNode() event.
173      *
174      * @param children Child iterable
175      * @return True
176      * @throws IOException when the writer reports it
177      */
178     protected final boolean writeChildren(final Iterable<? extends NormalizedNode> children) throws IOException {
179         if (currentDepth < maxDepth) {
180             for (final NormalizedNode child : children) {
181                 write(child);
182             }
183         }
184         writer.endNode();
185         return true;
186     }
187
188     protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
189         if (currentDepth < maxDepth) {
190             writeChildren(mapEntryNode.body());
191         } else if (currentDepth == maxDepth) {
192             writeOnlyKeys(mapEntryNode.getIdentifier().entrySet());
193         }
194         return true;
195     }
196
197     private void writeOnlyKeys(final Set<Entry<QName, Object>> entries) throws IOException {
198         for (final Entry<QName, Object> entry : entries) {
199             writer.startLeafNode(new NodeIdentifier(entry.getKey()));
200             writer.scalarValue(entry.getValue());
201             writer.endNode();
202         }
203         writer.endNode();
204     }
205
206     protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
207         writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
208         currentDepth++;
209         writeMapEntryChildren(node);
210         currentDepth--;
211         return true;
212     }
213
214     private boolean wasProcessedAsCompositeNode(final NormalizedNode node) throws IOException {
215         boolean processedAsCompositeNode = false;
216         if (node instanceof ContainerNode) {
217             final ContainerNode n = (ContainerNode) node;
218             writer.startContainerNode(n.getIdentifier(), childSizeHint(n.body()));
219             currentDepth++;
220             processedAsCompositeNode = writeChildren(n.body());
221             currentDepth--;
222         } else if (node instanceof MapEntryNode) {
223             processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
224         } else if (node instanceof UnkeyedListEntryNode) {
225             final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
226             writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.body()));
227             currentDepth++;
228             processedAsCompositeNode = writeChildren(n.body());
229             currentDepth--;
230         } else if (node instanceof ChoiceNode) {
231             final ChoiceNode n = (ChoiceNode) node;
232             writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.body()));
233             processedAsCompositeNode = writeChildren(n.body());
234         } else if (node instanceof AugmentationNode) {
235             final AugmentationNode n = (AugmentationNode) node;
236             writer.startAugmentationNode(n.getIdentifier());
237             processedAsCompositeNode = writeChildren(n.body());
238         } else if (node instanceof UnkeyedListNode) {
239             final UnkeyedListNode n = (UnkeyedListNode) node;
240             writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.body()));
241             processedAsCompositeNode = writeChildren(n.body());
242         } else if (node instanceof UserMapNode) {
243             final UserMapNode n = (UserMapNode) node;
244             writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.body()));
245             processedAsCompositeNode = writeChildren(n.body());
246         } else if (node instanceof MapNode) {
247             final MapNode n = (MapNode) node;
248             writer.startMapNode(n.getIdentifier(), childSizeHint(n.body()));
249             processedAsCompositeNode = writeChildren(n.body());
250         } else if (node instanceof LeafSetNode) {
251             final LeafSetNode<?> n = (LeafSetNode<?>) node;
252             if (node instanceof UserLeafSetNode) {
253                 writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.body()));
254             } else {
255                 writer.startLeafSet(n.getIdentifier(), childSizeHint(n.body()));
256             }
257             currentDepth++;
258             processedAsCompositeNode = writeChildren(n.body());
259             currentDepth--;
260         }
261
262         return processedAsCompositeNode;
263     }
264
265     private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
266         private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
267
268         OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
269             super(writer, maxDepth);
270         }
271
272         @Override
273         protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
274             final NormalizedNodeStreamWriter writer = getWriter();
275             writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.body()));
276
277             final Set<QName> qnames = node.getIdentifier().keySet();
278             // Write out all the key children
279             for (final QName qname : qnames) {
280                 final Optional<? extends NormalizedNode> child = node.findChildByArg(new NodeIdentifier(qname));
281                 if (child.isPresent()) {
282                     write(child.get());
283                 } else {
284                     LOG.info("No child for key element {} found", qname);
285                 }
286             }
287
288             // Write all the rest
289             currentDepth++;
290             final boolean result = writeChildren(Iterables.filter(node.body(), input -> {
291                 if (input instanceof AugmentationNode) {
292                     return true;
293                 }
294                 if (!qnames.contains(input.getIdentifier().getNodeType())) {
295                     return true;
296                 }
297
298                 LOG.debug("Skipping key child {}", input);
299                 return false;
300             }));
301             currentDepth--;
302             return result;
303         }
304     }
305 }