BUG-1848 : OrderedNormalizedNodeWriter implementation
[controller.git] / opendaylight / netconf / netconf-util / src / main / java / org / opendaylight / controller / netconf / util / OrderedNormalizedNodeWriter.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
9 package org.opendaylight.controller.netconf.util;
10
11 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
12
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Predicate;
16 import com.google.common.collect.ArrayListMultimap;
17 import com.google.common.collect.Iterables;
18 import java.io.Closeable;
19 import java.io.Flushable;
20 import java.io.IOException;
21 import java.util.Collection;
22 import java.util.List;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
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.OrderedMapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
39 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
40 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
41 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
49
50 //TODO this does not extend NormalizedNodeWriter from yangtools due to api freeze, make this inherit common methods to avoid code duplication
51 //TODO move this to yangtools, since this is in netconf-util due to api freeze in lithium
52 public class OrderedNormalizedNodeWriter implements Closeable, Flushable{
53
54     private final SchemaContext schemaContext;
55     private final SchemaNode root;
56     private final NormalizedNodeStreamWriter writer;
57
58     public OrderedNormalizedNodeWriter(NormalizedNodeStreamWriter writer, SchemaContext schemaContext, SchemaPath path) {
59         this.writer = writer;
60         this.schemaContext = schemaContext;
61         this.root = findParentSchemaOnPath(schemaContext, path);
62     }
63
64     public OrderedNormalizedNodeWriter write(final NormalizedNode<?, ?> node) throws IOException {
65         if (root == schemaContext) {
66             return write(node, schemaContext.getDataChildByName(node.getNodeType()));
67         }
68
69         return write(node, root);
70     }
71
72     public OrderedNormalizedNodeWriter write(final Collection<DataContainerChild<?,?>> nodes) throws IOException {
73         if (writeChildren(nodes, root, false)) {
74             return this;
75         }
76
77         throw new IllegalStateException("It wasn't possible to serialize nodes " + nodes);
78
79     }
80
81     private OrderedNormalizedNodeWriter write(NormalizedNode<?, ?> node, SchemaNode dataSchemaNode) throws IOException {
82         if (node == null) {
83             return this;
84         }
85
86         if (wasProcessedAsCompositeNode(node, dataSchemaNode)) {
87             return this;
88         }
89
90         if (wasProcessAsSimpleNode(node)) {
91             return this;
92         }
93
94         throw new IllegalStateException("It wasn't possible to serialize node " + node);
95     }
96
97     private void write(List<NormalizedNode<?, ?>> nodes, SchemaNode dataSchemaNode) throws IOException {
98         for (NormalizedNode<?, ?> node : nodes) {
99             write(node, dataSchemaNode);
100         }
101     }
102
103     private OrderedNormalizedNodeWriter writeLeaf(final NormalizedNode<?, ?> node) throws IOException {
104         if (wasProcessAsSimpleNode(node)) {
105             return this;
106         }
107
108         throw new IllegalStateException("It wasn't possible to serialize node " + node);
109     }
110
111     private boolean writeChildren(final Iterable<? extends NormalizedNode<?, ?>> children, SchemaNode parentSchemaNode, boolean endParent) throws IOException {
112         //Augmentations cannot be gotten with node.getChild so create our own structure with augmentations resolved
113         ArrayListMultimap<QName, NormalizedNode<?, ?>> qNameToNodes = ArrayListMultimap.create();
114         for (NormalizedNode<?, ?> child : children) {
115             if (child instanceof AugmentationNode) {
116                 qNameToNodes.putAll(resolveAugmentations(child));
117             } else {
118                 qNameToNodes.put(child.getNodeType(), child);
119             }
120         }
121
122         if (parentSchemaNode instanceof DataNodeContainer) {
123             if (parentSchemaNode instanceof ListSchemaNode && qNameToNodes.containsKey(parentSchemaNode.getQName())) {
124                 write(qNameToNodes.get(parentSchemaNode.getQName()), parentSchemaNode);
125             } else {
126                 for (DataSchemaNode schemaNode : ((DataNodeContainer) parentSchemaNode).getChildNodes()) {
127                     write(qNameToNodes.get(schemaNode.getQName()), schemaNode);
128                 }
129             }
130         } else if(parentSchemaNode instanceof ChoiceSchemaNode) {
131             for (ChoiceCaseNode ccNode : ((ChoiceSchemaNode) parentSchemaNode).getCases()) {
132                 for (DataSchemaNode dsn : ccNode.getChildNodes()) {
133                     if (qNameToNodes.containsKey(dsn.getQName())) {
134                         write(qNameToNodes.get(dsn.getQName()), dsn);
135                     }
136                 }
137             }
138         } else {
139             for (NormalizedNode<?, ?> child : children) {
140                 writeLeaf(child);
141             }
142         }
143         if (endParent) {
144             writer.endNode();
145         }
146         return true;
147     }
148
149     private ArrayListMultimap<QName, NormalizedNode<?, ?>> resolveAugmentations(NormalizedNode<?, ?> child) {
150         final ArrayListMultimap<QName, NormalizedNode<?, ?>> resolvedAugs = ArrayListMultimap.create();
151         for (NormalizedNode<?, ?> node : ((AugmentationNode) child).getValue()) {
152             if (node instanceof AugmentationNode) {
153                 resolvedAugs.putAll(resolveAugmentations(node));
154             } else {
155                 resolvedAugs.put(node.getNodeType(), node);
156             }
157         }
158         return resolvedAugs;
159     }
160
161     private boolean writeMapEntryNode(final MapEntryNode node, final SchemaNode dataSchemaNode) throws IOException {
162         if(writer instanceof NormalizedNodeStreamAttributeWriter) {
163             ((NormalizedNodeStreamAttributeWriter) writer)
164                     .startMapEntryNode(node.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(node.getValue()), node.getAttributes());
165         } else {
166             writer.startMapEntryNode(node.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(node.getValue()));
167         }
168         return writeChildren(node.getValue(), dataSchemaNode, true);
169     }
170
171     private boolean wasProcessAsSimpleNode(final NormalizedNode<?, ?> node) throws IOException {
172         if (node instanceof LeafSetEntryNode) {
173             final LeafSetEntryNode<?> nodeAsLeafList = (LeafSetEntryNode<?>)node;
174             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
175                 ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes());
176             } else {
177                 writer.leafSetEntryNode(nodeAsLeafList.getValue());
178             }
179             return true;
180         } else if (node instanceof LeafNode) {
181             final LeafNode<?> nodeAsLeaf = (LeafNode<?>)node;
182             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
183                 ((NormalizedNodeStreamAttributeWriter) writer).leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes());
184             } else {
185                 writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue());
186             }
187             return true;
188         } else if (node instanceof AnyXmlNode) {
189             final AnyXmlNode anyXmlNode = (AnyXmlNode)node;
190             writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue());
191             return true;
192         }
193
194         return false;
195     }
196
197     private boolean wasProcessedAsCompositeNode(final NormalizedNode<?, ?> node, SchemaNode dataSchemaNode) throws IOException {
198         if (node instanceof ContainerNode) {
199             final ContainerNode n = (ContainerNode) node;
200             if(writer instanceof NormalizedNodeStreamAttributeWriter) {
201                 ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()), n.getAttributes());
202             } else {
203                 writer.startContainerNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
204             }
205             return writeChildren(n.getValue(), dataSchemaNode, true);
206         }
207         if (node instanceof MapEntryNode) {
208             return writeMapEntryNode((MapEntryNode) node, dataSchemaNode);
209         }
210         if (node instanceof UnkeyedListEntryNode) {
211             final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node;
212             writer.startUnkeyedListItem(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
213             return writeChildren(n.getValue(), dataSchemaNode, true);
214         }
215         if (node instanceof ChoiceNode) {
216             final ChoiceNode n = (ChoiceNode) node;
217             writer.startChoiceNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
218             return writeChildren(n.getValue(), dataSchemaNode, true);
219         }
220         if (node instanceof AugmentationNode) {
221             final AugmentationNode n = (AugmentationNode) node;
222             writer.startAugmentationNode(n.getIdentifier());
223             return writeChildren(n.getValue(), dataSchemaNode, true);
224         }
225         if (node instanceof UnkeyedListNode) {
226             final UnkeyedListNode n = (UnkeyedListNode) node;
227             writer.startUnkeyedList(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
228             return writeChildren(n.getValue(), dataSchemaNode, true);
229         }
230         if (node instanceof OrderedMapNode) {
231             final OrderedMapNode n = (OrderedMapNode) node;
232             writer.startOrderedMapNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
233             return writeChildren(n.getValue(), dataSchemaNode, true);
234         }
235         if (node instanceof MapNode) {
236             final MapNode n = (MapNode) node;
237             writer.startMapNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
238             return writeChildren(n.getValue(), dataSchemaNode, true);
239         }
240         if (node instanceof LeafSetNode) {
241             //covers also OrderedLeafSetNode for which doesn't exist start* method
242             final LeafSetNode<?> n = (LeafSetNode<?>) node;
243             writer.startLeafSet(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()));
244             return writeChildren(n.getValue(), dataSchemaNode, true);
245         }
246
247         return false;
248     }
249
250     private static final int childSizeHint(final Iterable<?> children) {
251         return (children instanceof Collection) ? ((Collection<?>) children).size() : UNKNOWN_SIZE;
252     }
253
254     //TODO similar code is already present in schemaTracker, unify this when this writer is moved back to yangtools
255     private SchemaNode findParentSchemaOnPath(SchemaContext schemaContext, SchemaPath path) {
256         SchemaNode current = Preconditions.checkNotNull(schemaContext);
257         for (final QName qname : path.getPathFromRoot()) {
258             SchemaNode child;
259             if(current instanceof DataNodeContainer) {
260                 child = ((DataNodeContainer) current).getDataChildByName(qname);
261
262                 if (child == null && current instanceof SchemaContext) {
263                     child = tryFindGroupings((SchemaContext) current, qname).orNull();
264                 }
265
266                 if(child == null && current instanceof SchemaContext) {
267                     child = tryFindNotification((SchemaContext) current, qname)
268                             .or(tryFindRpc(((SchemaContext) current), qname)).orNull();
269                 }
270             } else if (current instanceof ChoiceSchemaNode) {
271                 child = ((ChoiceSchemaNode) current).getCaseNodeByName(qname);
272             } else if (current instanceof RpcDefinition) {
273                 switch (qname.getLocalName()) {
274                 case "input":
275                     child = ((RpcDefinition) current).getInput();
276                     break;
277                 case "output":
278                     child = ((RpcDefinition) current).getOutput();
279                     break;
280                 default:
281                     child = null;
282                     break;
283                 }
284             } else {
285                 throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", current));
286             }
287             current = child;
288         }
289         return current;
290     }
291
292     //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools
293     private Optional<SchemaNode> tryFindGroupings(final SchemaContext ctx, final QName qname) {
294         return Optional.<SchemaNode> fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null));
295     }
296
297     //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools
298     private Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
299         return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
300     }
301
302     //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools
303     private Optional<SchemaNode> tryFindNotification(final SchemaContext ctx, final QName qname) {
304         return Optional.<SchemaNode>fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null));
305     }
306
307     @Override
308     public void flush() throws IOException {
309         writer.flush();
310     }
311
312     @Override
313     public void close() throws IOException {
314         writer.flush();
315         writer.close();
316     }
317
318     //TODO this class is already present in schemaTracker, unify this when this writer is moved back to yangtools
319     private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
320         private final QName qname;
321
322         public SchemaNodePredicate(final QName qname) {
323             this.qname = qname;
324         }
325
326         @Override
327         public boolean apply(final SchemaNode input) {
328             return input.getQName().equals(qname);
329         }
330     }
331 }