Split Restconf implementations (draft02 and RFC) - Prepare modules
[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.Optional;
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.Map;
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
43  * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
44  * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
45  * us to write multiple nodes.
46  *
47  * @deprecated This class will be replaced by
48  * {@link org.opendaylight.restconf.jersey.providers.ParameterAwareNormalizedNodeWriter}
49  */
50 @Deprecated
51 public class DepthAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter {
52     private final NormalizedNodeStreamWriter writer;
53     protected int currentDepth = 0;
54     protected final int maxDepth;
55
56     private DepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
57         this.writer = Preconditions.checkNotNull(writer);
58         this.maxDepth = maxDepth;
59     }
60
61     protected final NormalizedNodeStreamWriter getWriter() {
62         return writer;
63     }
64
65     /**
66      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
67      *
68      * @param writer Back-end writer
69      * @return A new instance.
70      */
71     public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
72                                                                  final int maxDepth) {
73         return forStreamWriter(writer, true,  maxDepth);
74     }
75
76     /**
77      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
78      * Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter, int)}
79      * method, this allows the caller to switch off RFC6020 XML compliance, providing better
80      * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
81      * to emit leaf nodes which participate in a list's key first and in the order in which
82      * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
83      * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
84      * for each key and then for each emitted node we need to check whether it was already
85      * emitted.
86      *
87      * @param writer Back-end writer
88      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
89      * @return A new instance.
90      */
91     public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
92                                                                  final boolean orderKeyLeaves, final int maxDepth) {
93         return orderKeyLeaves ? new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth)
94                 : new DepthAwareNormalizedNodeWriter(writer, maxDepth);
95     }
96
97     /**
98      * Iterate over the provided {@link NormalizedNode} and emit write
99      * events to the encapsulated {@link NormalizedNodeStreamWriter}.
100      *
101      * @param node Node
102      * @return DepthAwareNormalizedNodeWriter
103      * @throws IOException when thrown from the backing writer.
104      */
105     @Override
106     public final DepthAwareNormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
107         if (wasProcessedAsCompositeNode(node)) {
108             return this;
109         }
110
111         if (wasProcessAsSimpleNode(node)) {
112             return this;
113         }
114
115         throw new IllegalStateException("It wasn't possible to serialize node " + node);
116     }
117
118     @Override
119     public void flush() throws IOException {
120         writer.flush();
121     }
122
123     @Override
124     public void close() throws IOException {
125         writer.flush();
126         writer.close();
127     }
128
129     /**
130      * Emit a best guess of a hint for a particular set of children. It evaluates the
131      * iterable to see if the size can be easily gotten to. If it is, we hint at the
132      * real number of child nodes. Otherwise we emit UNKNOWN_SIZE.
133      *
134      * @param children Child nodes
135      * @return Best estimate of the collection size required to hold all the children.
136      */
137     static final int childSizeHint(final Iterable<?> children) {
138         return children instanceof Collection ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
139     }
140
141     private boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
142         if (node instanceof LeafSetEntryNode) {
143             if (currentDepth < maxDepth) {
144                 final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>) node;
145                 if (writer instanceof NormalizedNodeStreamAttributeWriter) {
146                     ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getNodeType(),
147                         nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes());
148                 } else {
149                     writer.leafSetEntryNode(nodeAsLeafList.getNodeType(), nodeAsLeafList.getValue());
150                 }
151             }
152             return true;
153         } else if (node instanceof LeafNode) {
154             final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
155             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
156                 ((NormalizedNodeStreamAttributeWriter) writer)
157                         .leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes());
158             } else {
159                 writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue());
160             }
161             return true;
162         } else if (node instanceof AnyXmlNode) {
163             final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
164             writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue());
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 (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.getValue());
191         } else if (currentDepth == maxDepth) {
192             writeOnlyKeys(mapEntryNode.getIdentifier().getKeyValues());
193         }
194         return true;
195     }
196
197     private void writeOnlyKeys(final Map<QName, Object> keyValues) throws IllegalArgumentException, IOException {
198         for (Map.Entry<QName, Object> entry : keyValues.entrySet()) {
199             writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
200         }
201         writer.endNode();
202
203     }
204
205     protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
206         if (writer instanceof NormalizedNodeStreamAttributeWriter) {
207             ((NormalizedNodeStreamAttributeWriter) writer)
208                     .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
209         } else {
210             writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
211         }
212         currentDepth++;
213         writeMapEntryChildren(node);
214         currentDepth--;
215         return true;
216     }
217
218     private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
219         boolean processedAsCompositeNode = false;
220         if (node instanceof ContainerNode) {
221             final ContainerNode n = (ContainerNode) node;
222             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
223                 ((NormalizedNodeStreamAttributeWriter) writer)
224                         .startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()), n.getAttributes());
225             } else {
226                 writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()));
227             }
228             currentDepth++;
229             processedAsCompositeNode = writeChildren(n.getValue());
230             currentDepth--;
231         } else if (node instanceof MapEntryNode) {
232             processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node);
233         } else if (node instanceof UnkeyedListEntryNode) {
234             final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
235             writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue()));
236             currentDepth++;
237             processedAsCompositeNode = writeChildren(n.getValue());
238             currentDepth--;
239         } else if (node instanceof ChoiceNode) {
240             final ChoiceNode n = (ChoiceNode) node;
241             writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue()));
242             processedAsCompositeNode = writeChildren(n.getValue());
243         } else if (node instanceof AugmentationNode) {
244             final AugmentationNode n = (AugmentationNode) node;
245             writer.startAugmentationNode(n.getIdentifier());
246             processedAsCompositeNode = writeChildren(n.getValue());
247         } else if (node instanceof UnkeyedListNode) {
248             final UnkeyedListNode n = (UnkeyedListNode) node;
249             writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue()));
250             processedAsCompositeNode = writeChildren(n.getValue());
251         } else if (node instanceof OrderedMapNode) {
252             final OrderedMapNode n = (OrderedMapNode) node;
253             writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
254             processedAsCompositeNode = writeChildren(n.getValue());
255         } else if (node instanceof MapNode) {
256             final MapNode n = (MapNode) node;
257             writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
258             processedAsCompositeNode = writeChildren(n.getValue());
259         } else if (node instanceof LeafSetNode) {
260             final LeafSetNode<?> n = (LeafSetNode<?>) node;
261             if (node instanceof OrderedLeafSetNode) {
262                 writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
263             } else {
264                 writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
265             }
266             currentDepth++;
267             processedAsCompositeNode = writeChildren(n.getValue());
268             currentDepth--;
269         }
270
271         return processedAsCompositeNode;
272     }
273
274     private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
275         private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
276
277         OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
278             super(writer, maxDepth);
279         }
280
281         @Override
282         protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
283             final NormalizedNodeStreamWriter writer = getWriter();
284             if (writer instanceof NormalizedNodeStreamAttributeWriter) {
285                 ((NormalizedNodeStreamAttributeWriter) writer)
286                         .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
287             } else {
288                 writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
289             }
290
291             final Set<QName> qnames = node.getIdentifier().getKeyValues().keySet();
292             // Write out all the key children
293             for (QName qname : qnames) {
294                 final Optional<? extends NormalizedNode<?, ?>> child = node.getChild(new NodeIdentifier(qname));
295                 if (child.isPresent()) {
296                     write(child.get());
297                 } else {
298                     LOG.info("No child for key element {} found", qname);
299                 }
300             }
301
302             // Write all the rest
303             currentDepth++;
304             boolean result = writeChildren(Iterables.filter(node.getValue(), input -> {
305                 if (input instanceof AugmentationNode) {
306                     return true;
307                 }
308                 if (!qnames.contains(input.getNodeType())) {
309                     return true;
310                 }
311
312                 LOG.debug("Skipping key child {}", input);
313                 return false;
314             }));
315             currentDepth--;
316             return result;
317         }
318     }
319 }