Adjust to yangtools-2.0.0/odlparent-3.0.0 changes
[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 org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.Iterables;
14 import java.io.IOException;
15 import java.util.Collection;
16 import java.util.Map;
17 import java.util.Optional;
18 import java.util.Set;
19 import org.opendaylight.netconf.sal.rest.api.RestconfNormalizedNodeWriter;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
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 = Preconditions.checkNotNull(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                 if (writer instanceof NormalizedNodeStreamAttributeWriter) {
144                     ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getNodeType(),
145                         nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes());
146                 } else {
147                     writer.leafSetEntryNode(nodeAsLeafList.getNodeType(), nodeAsLeafList.getValue());
148                 }
149             }
150             return true;
151         } else if (node instanceof LeafNode) {
152             final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
153             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
154                 ((NormalizedNodeStreamAttributeWriter) writer)
155                         .leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes());
156             } else {
157                 writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue());
158             }
159             return true;
160         } else if (node instanceof AnyXmlNode) {
161             final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
162             writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue());
163             return true;
164         }
165
166         return false;
167     }
168
169     /**
170      * Emit events for all children and then emit an endNode() event.
171      *
172      * @param children Child iterable
173      * @return True
174      * @throws IOException when the writer reports it
175      */
176     protected final boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children) throws IOException {
177         if (currentDepth < maxDepth) {
178             for (final NormalizedNode<?, ?> child : children) {
179                 write(child);
180             }
181         }
182         writer.endNode();
183         return true;
184     }
185
186     protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
187         if (currentDepth < maxDepth) {
188             writeChildren(mapEntryNode.getValue());
189         } else if (currentDepth == maxDepth) {
190             writeOnlyKeys(mapEntryNode.getIdentifier().getKeyValues());
191         }
192         return true;
193     }
194
195     private void writeOnlyKeys(final Map<QName, Object> keyValues) throws IllegalArgumentException, IOException {
196         for (final Map.Entry<QName, Object> entry : keyValues.entrySet()) {
197             writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
198         }
199         writer.endNode();
200
201     }
202
203     protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
204         if (writer instanceof NormalizedNodeStreamAttributeWriter) {
205             ((NormalizedNodeStreamAttributeWriter) writer)
206                     .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
207         } else {
208             writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
209         }
210         currentDepth++;
211         writeMapEntryChildren(node);
212         currentDepth--;
213         return true;
214     }
215
216     private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
217         boolean processedAsCompositeNode = false;
218         if (node instanceof ContainerNode) {
219             final ContainerNode n = (ContainerNode) node;
220             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
221                 ((NormalizedNodeStreamAttributeWriter) writer)
222                         .startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()), n.getAttributes());
223             } else {
224                 writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()));
225             }
226             currentDepth++;
227             processedAsCompositeNode = writeChildren(n.getValue());
228             currentDepth--;
229         } else if (node instanceof MapEntryNode) {
230             processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
231         } else if (node instanceof UnkeyedListEntryNode) {
232             final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
233             writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue()));
234             currentDepth++;
235             processedAsCompositeNode = writeChildren(n.getValue());
236             currentDepth--;
237         } else if (node instanceof ChoiceNode) {
238             final ChoiceNode n = (ChoiceNode) node;
239             writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue()));
240             processedAsCompositeNode = writeChildren(n.getValue());
241         } else if (node instanceof AugmentationNode) {
242             final AugmentationNode n = (AugmentationNode) node;
243             writer.startAugmentationNode(n.getIdentifier());
244             processedAsCompositeNode = writeChildren(n.getValue());
245         } else if (node instanceof UnkeyedListNode) {
246             final UnkeyedListNode n = (UnkeyedListNode) node;
247             writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue()));
248             processedAsCompositeNode = writeChildren(n.getValue());
249         } else if (node instanceof OrderedMapNode) {
250             final OrderedMapNode n = (OrderedMapNode) node;
251             writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
252             processedAsCompositeNode = writeChildren(n.getValue());
253         } else if (node instanceof MapNode) {
254             final MapNode n = (MapNode) node;
255             writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
256             processedAsCompositeNode = writeChildren(n.getValue());
257         } else if (node instanceof LeafSetNode) {
258             final LeafSetNode<?> n = (LeafSetNode<?>) node;
259             if (node instanceof OrderedLeafSetNode) {
260                 writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
261             } else {
262                 writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
263             }
264             currentDepth++;
265             processedAsCompositeNode = writeChildren(n.getValue());
266             currentDepth--;
267         }
268
269         return processedAsCompositeNode;
270     }
271
272     private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
273         private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
274
275         OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
276             super(writer, maxDepth);
277         }
278
279         @Override
280         protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
281             final NormalizedNodeStreamWriter writer = getWriter();
282             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
283                 ((NormalizedNodeStreamAttributeWriter) writer)
284                         .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
285             } else {
286                 writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
287             }
288
289             final Set<QName> qnames = node.getIdentifier().getKeyValues().keySet();
290             // Write out all the key children
291             for (final QName qname : qnames) {
292                 final Optional<? extends NormalizedNode<?, ?>> child = node.getChild(new NodeIdentifier(qname));
293                 if (child.isPresent()) {
294                     write(child.get());
295                 } else {
296                     LOG.info("No child for key element {} found", qname);
297                 }
298             }
299
300             // Write all the rest
301             currentDepth++;
302             final boolean result = writeChildren(Iterables.filter(node.getValue(), input -> {
303                 if (input instanceof AugmentationNode) {
304                     return true;
305                 }
306                 if (!qnames.contains(input.getNodeType())) {
307                     return true;
308                 }
309
310                 LOG.debug("Skipping key child {}", input);
311                 return false;
312             }));
313             currentDepth--;
314             return result;
315         }
316     }
317 }