2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.netconf.util;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Iterables;
14 import java.io.IOException;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.Optional;
23 import java.util.stream.Collectors;
24 import javax.xml.transform.dom.DOMSource;
25 import org.opendaylight.yangtools.concepts.Identifiable;
26 import org.opendaylight.yangtools.yang.common.Empty;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
35 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
36 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
39 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
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.LeafListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
49 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
50 private final T identifier;
52 StreamingContext(final T identifier) {
53 this.identifier = identifier;
56 static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
57 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
58 checkArgument(potential.isPresent(),
59 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
60 schema.getChildNodes());
62 final DataSchemaNode result = potential.get();
63 // We try to look up if this node was added by augmentation
64 if (schema instanceof DataSchemaNode && result.isAugmenting()) {
65 for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
66 if (aug.dataChildByName(result.getQName()) != null) {
67 return new Augmentation(aug, schema);
71 return fromDataSchemaNode(result);
74 static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
75 if (potential instanceof ContainerSchemaNode container) {
76 return new Container(container);
77 } else if (potential instanceof ListSchemaNode list) {
78 return fromListSchemaNode(list);
79 } else if (potential instanceof LeafSchemaNode leaf) {
80 return new Leaf(leaf);
81 } else if (potential instanceof ChoiceSchemaNode choice) {
82 return new Choice(choice);
83 } else if (potential instanceof LeafListSchemaNode leafList) {
84 return fromLeafListSchemaNode(leafList);
85 } else if (potential instanceof AnyxmlSchemaNode anyxml) {
86 return new AnyXml(anyxml);
88 // FIXME: unhandled anydata!
93 public final T getIdentifier() {
97 abstract StreamingContext<?> getChild(PathArgument child);
100 * Writing node structure that is described by series of {@link PathArgument}
101 * into {@link NormalizedNodeStreamWriter}.
103 * @param writer output {@link NormalizedNode} writer
104 * @param first the first {@link PathArgument}
105 * @param others iterator that points to next path arguments
106 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
108 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
112 * Writing node structure that is described by provided {@link PathNode} into {@link NormalizedNodeStreamWriter}.
114 * @param writer output {@link NormalizedNode} writer
115 * @param first the first {@link PathArgument}
116 * @param tree subtree of path arguments that starts with the first path argument
117 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
119 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, PathNode tree)
122 abstract boolean isMixin();
124 private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
125 final Optional<DataSchemaNode> potential = parent.findDataChildByName(child);
126 return potential.isPresent() ? potential
127 : findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
130 private static Optional<DataSchemaNode> findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
131 for (final ChoiceSchemaNode choice : choices) {
132 for (final CaseSchemaNode caze : choice.getCases()) {
133 if (findChildSchemaNode(caze, child).isPresent()) {
134 return Optional.of(choice);
138 return Optional.empty();
141 private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
142 final List<QName> keyDefinition = potential.getKeyDefinition();
143 if (keyDefinition == null || keyDefinition.isEmpty()) {
144 return new UnkeyedListMixin(potential);
146 return potential.isUserOrdered() ? new OrderedMapMixin(potential)
147 : new UnorderedMapMixin(potential);
150 private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
151 return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
152 : new UnorderedLeafListMixin(potential);
155 private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
156 AbstractComposite(final T identifier) {
161 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
162 final Iterator<PathArgument> others) throws IOException {
163 verifyActualPathArgument(first);
165 emitElementStart(writer, first, others.hasNext() ? 1 : 0);
166 if (others.hasNext()) {
167 final PathArgument childPath = others.next();
168 final StreamingContext<?> childOp = getChildOperation(childPath);
169 childOp.streamToWriter(writer, childPath, others);
175 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
176 final PathNode subtree) throws IOException {
177 verifyActualPathArgument(first);
179 final Collection<PathNode> children = subtree.children();
180 emitElementStart(writer, first, children.size());
181 for (final PathNode node : subtree.children()) {
182 emitChildTreeNode(writer, node);
187 void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException {
188 final PathArgument childPath = node.element();
189 getChildOperation(childPath).streamToWriter(writer, childPath, node);
192 private void verifyActualPathArgument(final PathArgument first) {
194 final QName type = getIdentifier().getNodeType();
195 final QName firstType = first.getNodeType();
196 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
200 abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg,
201 int childSizeHint) throws IOException;
203 @SuppressWarnings("checkstyle:illegalCatch")
204 StreamingContext<?> getChildOperation(final PathArgument childPath) {
205 final StreamingContext<?> childOp;
207 childOp = getChild(childPath);
208 } catch (final RuntimeException e) {
209 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
211 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
216 private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
217 private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
218 private final DataNodeContainer schema;
220 AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
222 this.schema = schema;
226 final StreamingContext<?> getChild(final PathArgument child) {
227 StreamingContext<?> potential = byArg.get(child);
228 if (potential != null) {
231 potential = fromLocalSchema(child);
232 if (potential != null) {
233 byArg.put(potential.getIdentifier(), potential);
238 private StreamingContext<?> fromLocalSchema(final PathArgument child) {
239 if (child instanceof AugmentationIdentifier aid) {
240 return fromSchemaAndQNameChecked(schema, aid.getPossibleChildNames().iterator().next());
242 return fromSchemaAndQNameChecked(schema, child.getNodeType());
246 private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
247 private final ListEntry innerNode;
248 private final List<QName> keyLeaves;
250 AbstractMapMixin(final ListSchemaNode list) {
251 super(NodeIdentifier.create(list.getQName()));
252 innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
253 keyLeaves = list.getKeyDefinition();
257 final StreamingContext<?> getChild(final PathArgument child) {
258 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
262 final boolean isMixin() {
267 final void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException {
268 final PathArgument element = node.element();
269 if (!(element instanceof NodeIdentifierWithPredicates childPath)) {
270 throw new IOException("Child identifier " + element + " is invalid in parent " + getIdentifier());
273 final StreamingContext<?> childOp = getChildOperation(childPath);
274 if (childPath.size() == 0 && node.isEmpty() || childPath.keySet().containsAll(keyLeaves)) {
275 // This is a query for the entire list, or the query specifies everything we need
276 childOp.streamToWriter(writer, childPath, node);
280 // Inexact query, we need to also request the leaf nodes we need to for reconstructing a valid instance
281 // NodeIdentifierWithPredicates.
282 childOp.streamToWriter(writer, childPath, node.copyWith(keyLeaves.stream()
283 .filter(qname -> !childPath.containsKey(qname))
284 .map(NodeIdentifier::new)
285 .collect(Collectors.toUnmodifiableList())));
289 private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
290 AbstractSimple(final T identifier) {
295 final StreamingContext<?> getChild(final PathArgument child) {
300 final boolean isMixin() {
305 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
306 final PathNode tree) throws IOException {
307 streamToWriter(writer, first, Collections.emptyIterator());
311 private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
312 AnyXml(final AnyxmlSchemaNode schema) {
313 super(NodeIdentifier.create(schema.getQName()));
317 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
318 final Iterator<PathArgument> others) throws IOException {
319 writer.startAnyxmlNode(getIdentifier(), DOMSource.class);
320 // FIXME: why are we not emitting a value?
325 private static final class Choice extends AbstractComposite<NodeIdentifier> {
326 private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
328 Choice(final ChoiceSchemaNode schema) {
329 super(NodeIdentifier.create(schema.getQName()));
330 final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
332 for (final CaseSchemaNode caze : schema.getCases()) {
333 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
334 final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
335 byArgBuilder.put(childOp.getIdentifier(), childOp);
338 byArg = byArgBuilder.build();
342 StreamingContext<?> getChild(final PathArgument child) {
343 return byArg.get(child);
352 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
353 final int childSizeHint) throws IOException {
354 writer.startChoiceNode(getIdentifier(), childSizeHint);
358 private static final class Leaf extends AbstractSimple<NodeIdentifier> {
359 Leaf(final LeafSchemaNode potential) {
360 super(new NodeIdentifier(potential.getQName()));
364 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
365 final Iterator<PathArgument> others) throws IOException {
366 writer.startLeafNode(getIdentifier());
367 // FIXME: why are we not emitting a value?
372 private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
373 LeafListEntry(final LeafListSchemaNode potential) {
374 super(new NodeWithValue<>(potential.getQName(), Empty.value()));
378 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
379 final Iterator<PathArgument> others) throws IOException {
380 checkArgument(first instanceof NodeWithValue);
381 final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
382 writer.startLeafSetEntryNode(identifier);
383 writer.scalarValue(identifier.getValue());
388 private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
389 ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
390 super(identifier, schema);
399 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
400 final int childSizeHint) throws IOException {
401 final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
402 writer.startMapEntryNode(identifier, childSizeHint);
404 for (Entry<QName, Object> entry : identifier.entrySet()) {
405 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
406 writer.scalarValue(entry.getValue());
412 private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
413 UnkeyedListItem(final ListSchemaNode schema) {
414 super(NodeIdentifier.create(schema.getQName()), schema);
423 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
424 final int childSizeHint) throws IOException {
425 writer.startUnkeyedListItem(getIdentifier(), childSizeHint);
429 private static final class Container extends AbstractDataContainer<NodeIdentifier> {
430 Container(final ContainerSchemaNode schema) {
431 super(NodeIdentifier.create(schema.getQName()), schema);
440 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
441 final int childSizeHint) throws IOException {
442 writer.startContainerNode(getIdentifier(), childSizeHint);
446 private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
447 private final StreamingContext<?> innerOp;
449 LeafListMixin(final LeafListSchemaNode potential) {
450 super(NodeIdentifier.create(potential.getQName()));
451 innerOp = new LeafListEntry(potential);
455 final StreamingContext<?> getChild(final PathArgument child) {
456 return child instanceof NodeWithValue ? innerOp : null;
460 final boolean isMixin() {
465 private static final class OrderedLeafListMixin extends LeafListMixin {
466 OrderedLeafListMixin(final LeafListSchemaNode potential) {
471 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
472 final int childSizeHint) throws IOException {
473 writer.startOrderedLeafSet(getIdentifier(), childSizeHint);
477 private static class UnorderedLeafListMixin extends LeafListMixin {
478 UnorderedLeafListMixin(final LeafListSchemaNode potential) {
483 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
484 final int childSizeHint) throws IOException {
485 writer.startLeafSet(getIdentifier(), childSizeHint);
489 private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
490 Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
491 super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
492 new EffectiveAugmentationSchema(augmentation, schema));
501 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
502 final int childSizeHint) throws IOException {
503 writer.startAugmentationNode(getIdentifier());
507 private static final class UnorderedMapMixin extends AbstractMapMixin {
508 UnorderedMapMixin(final ListSchemaNode list) {
513 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
514 final int childSizeHint) throws IOException {
515 writer.startMapNode(getIdentifier(), childSizeHint);
519 private static final class OrderedMapMixin extends AbstractMapMixin {
520 OrderedMapMixin(final ListSchemaNode list) {
525 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
526 final int childSizeHint) throws IOException {
527 writer.startOrderedMapNode(getIdentifier(), childSizeHint);
531 private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
532 private final UnkeyedListItem innerNode;
534 UnkeyedListMixin(final ListSchemaNode list) {
535 super(NodeIdentifier.create(list.getQName()));
536 innerNode = new UnkeyedListItem(list);
540 StreamingContext<?> getChild(final PathArgument child) {
541 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
550 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
551 final int childSizeHint) throws IOException {
552 writer.startUnkeyedList(getIdentifier(), childSizeHint);