52ce54fd6ecfa0c45cff658dc3298889b08bf826
[netconf.git] / netconf / netconf-util / src / main / java / org / opendaylight / netconf / util / StreamingContext.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
12
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import javax.xml.transform.dom.DOMSource;
23 import org.opendaylight.yangtools.concepts.Identifiable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
30 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
31 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
32 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
35 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
44
45 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
46     private final T identifier;
47
48     StreamingContext(final T identifier) {
49         this.identifier = identifier;
50     }
51
52     static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
53         final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
54         checkArgument(potential.isPresent(),
55                 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
56                 schema.getChildNodes());
57
58         final DataSchemaNode result = potential.get();
59         // We try to look up if this node was added by augmentation
60         if (schema instanceof DataSchemaNode && result.isAugmenting()) {
61             for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
62                 if (aug.findDataChildByName(result.getQName()).isPresent()) {
63                     return new Augmentation(aug, schema);
64                 }
65             }
66         }
67         return fromDataSchemaNode(result);
68     }
69
70     static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
71         if (potential instanceof ContainerSchemaNode) {
72             return new Container((ContainerSchemaNode) potential);
73         } else if (potential instanceof ListSchemaNode) {
74             return fromListSchemaNode((ListSchemaNode) potential);
75         } else if (potential instanceof LeafSchemaNode) {
76             return new Leaf((LeafSchemaNode) potential);
77         } else if (potential instanceof ChoiceSchemaNode) {
78             return new Choice((ChoiceSchemaNode) potential);
79         } else if (potential instanceof LeafListSchemaNode) {
80             return fromLeafListSchemaNode((LeafListSchemaNode) potential);
81         } else if (potential instanceof AnyxmlSchemaNode) {
82             return new AnyXml((AnyxmlSchemaNode) potential);
83         }
84         return null;
85     }
86
87     @Override
88     public final T getIdentifier() {
89         return identifier;
90     }
91
92     abstract StreamingContext<?> getChild(PathArgument child);
93
94     abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
95             throws IOException;
96
97     abstract boolean isMixin();
98
99     private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
100         final Optional<DataSchemaNode> potential = parent.findDataChildByName(child);
101         return potential.isPresent() ? potential
102                 : findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
103     }
104
105     private static Optional<DataSchemaNode> findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
106         for (final ChoiceSchemaNode choice : choices) {
107             for (final CaseSchemaNode caze : choice.getCases()) {
108                 if (findChildSchemaNode(caze, child).isPresent()) {
109                     return Optional.of(choice);
110                 }
111             }
112         }
113         return Optional.empty();
114     }
115
116     private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
117         final List<QName> keyDefinition = potential.getKeyDefinition();
118         if (keyDefinition == null || keyDefinition.isEmpty()) {
119             return new UnkeyedListMixin(potential);
120         }
121         return potential.isUserOrdered() ? new OrderedMapMixin(potential)
122                 : new UnorderedMapMixin(potential);
123     }
124
125     private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
126         return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
127                 : new UnorderedLeafListMixin(potential);
128     }
129
130     private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
131         AbstractComposite(final T identifier) {
132             super(identifier);
133         }
134
135         @Override
136         final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
137                 final Iterator<PathArgument> others) throws IOException {
138             if (!isMixin()) {
139                 final QName type = getIdentifier().getNodeType();
140                 if (type != null) {
141                     final QName firstType = first.getNodeType();
142                     checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
143                 }
144             }
145
146             emitElementStart(writer, first);
147             if (others.hasNext()) {
148                 final PathArgument childPath = others.next();
149                 final StreamingContext<?> childOp = getChildOperation(childPath);
150                 childOp.streamToWriter(writer, childPath, others);
151             }
152             writer.endNode();
153         }
154
155         abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
156
157         @SuppressWarnings("checkstyle:illegalCatch")
158         private StreamingContext<?> getChildOperation(final PathArgument childPath) {
159             final StreamingContext<?> childOp;
160             try {
161                 childOp = getChild(childPath);
162             } catch (final RuntimeException e) {
163                 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
164             }
165             checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
166             return childOp;
167         }
168     }
169
170     private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
171         private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
172         private final DataNodeContainer schema;
173
174         AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
175             super(identifier);
176             this.schema = schema;
177         }
178
179         @Override
180         final StreamingContext<?> getChild(final PathArgument child) {
181             StreamingContext<?> potential = byArg.get(child);
182             if (potential != null) {
183                 return potential;
184             }
185             potential = fromLocalSchema(child);
186             if (potential != null) {
187                 byArg.put(potential.getIdentifier(), potential);
188             }
189             return potential;
190         }
191
192         private StreamingContext<?> fromLocalSchema(final PathArgument child) {
193             if (child instanceof AugmentationIdentifier) {
194                 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
195                         .iterator().next());
196             }
197             return fromSchemaAndQNameChecked(schema, child.getNodeType());
198         }
199     }
200
201     private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
202         private final ListEntry innerNode;
203
204         AbstractMapMixin(final ListSchemaNode list) {
205             super(NodeIdentifier.create(list.getQName()));
206             this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
207         }
208
209         @Override
210         final StreamingContext<?> getChild(final PathArgument child) {
211             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
212         }
213
214         @Override
215         final boolean isMixin() {
216             return true;
217         }
218     }
219
220     private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
221         AbstractSimple(final T identifier) {
222             super(identifier);
223         }
224
225         @Override
226         final StreamingContext<?> getChild(final PathArgument child) {
227             return null;
228         }
229
230         @Override
231         final boolean isMixin() {
232             return false;
233         }
234     }
235
236     private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
237         AnyXml(final AnyxmlSchemaNode schema) {
238             super(NodeIdentifier.create(schema.getQName()));
239         }
240
241         @Override
242         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
243                 final Iterator<PathArgument> others) throws IOException {
244             writer.startAnyxmlNode(getIdentifier(), DOMSource.class);
245             // FIXME: why are we not emitting a value?
246             writer.endNode();
247         }
248     }
249
250     private static final class Choice extends AbstractComposite<NodeIdentifier> {
251         private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
252
253         Choice(final ChoiceSchemaNode schema) {
254             super(NodeIdentifier.create(schema.getQName()));
255             final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
256
257             for (final CaseSchemaNode caze : schema.getCases()) {
258                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
259                     final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
260                     byArgBuilder.put(childOp.getIdentifier(), childOp);
261                 }
262             }
263             byArg = byArgBuilder.build();
264         }
265
266         @Override
267         StreamingContext<?> getChild(final PathArgument child) {
268             return byArg.get(child);
269         }
270
271         @Override
272         boolean isMixin() {
273             return true;
274         }
275
276         @Override
277         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
278             writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
279         }
280     }
281
282     private static final class Leaf extends AbstractSimple<NodeIdentifier> {
283         Leaf(final LeafSchemaNode potential) {
284             super(new NodeIdentifier(potential.getQName()));
285         }
286
287         @Override
288         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
289                 final Iterator<PathArgument> others) throws IOException {
290             writer.startLeafNode(getIdentifier());
291             // FIXME: why are we not emitting a value?
292             writer.endNode();
293         }
294     }
295
296     private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
297         LeafListEntry(final LeafListSchemaNode potential) {
298             super(new NodeWithValue<>(potential.getQName(), null));
299         }
300
301         @Override
302         void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
303                 final Iterator<PathArgument> others) throws IOException {
304             checkArgument(first instanceof NodeWithValue);
305             final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
306             writer.startLeafSetEntryNode(identifier);
307             writer.scalarValue(identifier.getValue());
308             writer.endNode();
309         }
310     }
311
312     private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
313         ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
314             super(identifier, schema);
315         }
316
317         @Override
318         boolean isMixin() {
319             return false;
320         }
321
322         @Override
323         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
324             final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
325             writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
326
327             for (Entry<QName, Object> entry : identifier.entrySet()) {
328                 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
329                 writer.scalarValue(entry.getValue());
330                 writer.endNode();
331             }
332         }
333     }
334
335     private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
336         UnkeyedListItem(final ListSchemaNode schema) {
337             super(NodeIdentifier.create(schema.getQName()), schema);
338         }
339
340         @Override
341         boolean isMixin() {
342             return false;
343         }
344
345         @Override
346         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
347             writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
348         }
349     }
350
351     private static final class Container extends AbstractDataContainer<NodeIdentifier> {
352         Container(final ContainerSchemaNode schema) {
353             super(NodeIdentifier.create(schema.getQName()), schema);
354         }
355
356         @Override
357         boolean isMixin() {
358             return false;
359         }
360
361         @Override
362         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
363             writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
364         }
365     }
366
367     private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
368         private final StreamingContext<?> innerOp;
369
370         LeafListMixin(final LeafListSchemaNode potential) {
371             super(NodeIdentifier.create(potential.getQName()));
372             innerOp = new LeafListEntry(potential);
373         }
374
375         @Override
376         final StreamingContext<?> getChild(final PathArgument child) {
377             return child instanceof NodeWithValue ? innerOp : null;
378         }
379
380         @Override
381         final boolean isMixin() {
382             return true;
383         }
384     }
385
386     private static final class OrderedLeafListMixin extends LeafListMixin {
387         OrderedLeafListMixin(final LeafListSchemaNode potential) {
388             super(potential);
389         }
390
391         @Override
392         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
393             writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
394         }
395     }
396
397     private static class UnorderedLeafListMixin extends LeafListMixin {
398         UnorderedLeafListMixin(final LeafListSchemaNode potential) {
399             super(potential);
400         }
401
402         @Override
403         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
404             writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
405         }
406     }
407
408     private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
409         Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
410             super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
411                     EffectiveAugmentationSchema.create(augmentation, schema));
412         }
413
414         @Override
415         boolean isMixin() {
416             return true;
417         }
418
419         @Override
420         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
421             writer.startAugmentationNode(getIdentifier());
422         }
423     }
424
425     private static final class UnorderedMapMixin extends AbstractMapMixin {
426         UnorderedMapMixin(final ListSchemaNode list) {
427             super(list);
428         }
429
430         @Override
431         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
432             writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
433         }
434     }
435
436     private static final class OrderedMapMixin extends AbstractMapMixin {
437         OrderedMapMixin(final ListSchemaNode list) {
438             super(list);
439         }
440
441         @Override
442         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
443             writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
444         }
445     }
446
447     private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
448         private final UnkeyedListItem innerNode;
449
450         UnkeyedListMixin(final ListSchemaNode list) {
451             super(NodeIdentifier.create(list.getQName()));
452             this.innerNode = new UnkeyedListItem(list);
453         }
454
455         @Override
456         StreamingContext<?> getChild(final PathArgument child) {
457             return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
458         }
459
460         @Override
461         boolean isMixin() {
462             return true;
463         }
464
465         @Override
466         void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
467             writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);
468         }
469     }
470 }