Remove opendaylight directory
[netconf.git] / restconf / sal-rest-connector / 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 import com.google.common.annotations.Beta;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Predicate;
15 import com.google.common.collect.Iterables;
16 import java.io.IOException;
17 import java.util.Collection;
18 import java.util.Map;
19 import java.util.Set;
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.OrderedLeafSetNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
38 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * This is an experimental iterator over a {@link NormalizedNode}. This is essentially
44  * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
45  * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
46  * us to write multiple nodes.
47  */
48 @Beta
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, final int maxDepth) {
70         return forStreamWriter(writer, true,  maxDepth);
71     }
72
73     /**
74      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter, int)}
75      * method, this allows the caller to switch off RFC6020 XML compliance, providing better
76      * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding
77      * to emit leaf nodes which participate in a list's key first and in the order in which
78      * they are defined in the key. For JSON, this requirement is completely relaxed and leaves
79      * can be ordered in any way we see fit. The former requires a bit of work: first a lookup
80      * for each key and then for each emitted node we need to check whether it was already
81      * emitted.
82      *
83      * @param writer Back-end writer
84      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
85      * @return A new instance.
86      */
87     public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer, final boolean
88             orderKeyLeaves, final int maxDepth) {
89         if (orderKeyLeaves) {
90             return new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth);
91         } else {
92             return new DepthAwareNormalizedNodeWriter(writer, maxDepth);
93         }
94     }
95
96     /**
97      * Iterate over the provided {@link NormalizedNode} and emit write
98      * events to the encapsulated {@link NormalizedNodeStreamWriter}.
99      *
100      * @param node Node
101      * @return
102      * @throws IOException when thrown from the backing writer.
103      */
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).leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes());
155             } else {
156                 writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue());
157             }
158             return true;
159         } else if (node instanceof AnyXmlNode) {
160             final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
161             writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue());
162             return true;
163         }
164
165         return false;
166     }
167
168     /**
169      * Emit events for all children and then emit an endNode() event.
170      *
171      * @param children Child iterable
172      * @return True
173      * @throws IOException when the writer reports it
174      */
175     protected final boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children) throws IOException {
176         if (currentDepth < maxDepth) {
177             for (NormalizedNode<?, ?> child : children) {
178                 write(child);
179             }
180         }
181         writer.endNode();
182         return true;
183     }
184
185     protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException {
186         if (currentDepth < maxDepth) {
187             writeChildren(mapEntryNode.getValue());
188         } else if (currentDepth == maxDepth) {
189             writeOnlyKeys(mapEntryNode.getIdentifier().getKeyValues());
190         }
191         return true;
192     }
193
194     private void writeOnlyKeys(Map<QName, Object> keyValues) throws IllegalArgumentException, IOException {
195         for (Map.Entry<QName, Object> entry : keyValues.entrySet()) {
196             writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
197         }
198         writer.endNode();
199
200     }
201
202     protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
203         if(writer instanceof NormalizedNodeStreamAttributeWriter) {
204             ((NormalizedNodeStreamAttributeWriter) writer)
205                     .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
206         } else {
207             writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
208         }
209         currentDepth++;
210         writeMapEntryChildren(node);
211         currentDepth--;
212         return true;
213     }
214
215     private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node) throws IOException {
216         boolean processedAsCompositeNode = false;
217         if (node instanceof ContainerNode) {
218             final ContainerNode n = (ContainerNode) node;
219             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
220                 ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()), n.getAttributes());
221             } else {
222                 writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()));
223             }
224             currentDepth++;
225             processedAsCompositeNode = writeChildren(n.getValue());
226             currentDepth--;
227         }
228         else if (node instanceof MapEntryNode) {
229             processedAsCompositeNode =  writeMapEntryNode((MapEntryNode) node);
230         }
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         }
238         else if (node instanceof ChoiceNode) {
239             final ChoiceNode n = (ChoiceNode) node;
240             writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue()));
241             processedAsCompositeNode = writeChildren(n.getValue());
242         }
243         else if (node instanceof AugmentationNode) {
244             final AugmentationNode n = (AugmentationNode) node;
245             writer.startAugmentationNode(n.getIdentifier());
246             processedAsCompositeNode = writeChildren(n.getValue());
247         }
248         else if (node instanceof UnkeyedListNode) {
249             final UnkeyedListNode n = (UnkeyedListNode) node;
250             writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue()));
251             processedAsCompositeNode = writeChildren(n.getValue());
252         }
253         else if (node instanceof OrderedMapNode) {
254             final OrderedMapNode n = (OrderedMapNode) node;
255             writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
256             processedAsCompositeNode = writeChildren(n.getValue());
257         }
258         else if (node instanceof MapNode) {
259             final MapNode n = (MapNode) node;
260             writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue()));
261             processedAsCompositeNode = writeChildren(n.getValue());
262         }
263         else if (node instanceof LeafSetNode) {
264             final LeafSetNode<?> n = (LeafSetNode<?>) node;
265             if (node instanceof OrderedLeafSetNode) {
266                 writer.startOrderedLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
267             } else {
268                 writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue()));
269             }
270             currentDepth++;
271             processedAsCompositeNode = writeChildren(n.getValue());
272             currentDepth--;
273         }
274
275         return processedAsCompositeNode;
276     }
277
278     private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter {
279         private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class);
280
281         OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) {
282             super(writer, maxDepth);
283         }
284
285         @Override
286         protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException {
287             final NormalizedNodeStreamWriter writer = getWriter();
288             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
289                 ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes());
290             } else {
291                 writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()));
292             }
293
294             final Set<QName> qnames = node.getIdentifier().getKeyValues().keySet();
295             // Write out all the key children
296             for (QName qname : qnames) {
297                 final Optional<? extends NormalizedNode<?, ?>> child = node.getChild(new NodeIdentifier(qname));
298                 if (child.isPresent()) {
299                     write(child.get());
300                 } else {
301                     LOG.info("No child for key element {} found", qname);
302                 }
303             }
304
305             // Write all the rest
306             currentDepth++;
307             boolean result = writeChildren(Iterables.filter(node.getValue(), new Predicate<NormalizedNode<?, ?>>() {
308                 @Override
309                 public boolean apply(final NormalizedNode<?, ?> input) {
310                     if (input instanceof AugmentationNode) {
311                         return true;
312                     }
313                     if (!qnames.contains(input.getNodeType())) {
314                         return true;
315                     }
316
317                     LOG.debug("Skipping key child {}", input);
318                     return false;
319                 }
320             }));
321             currentDepth--;
322             return result;
323         }
324     }
325 }