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;
11 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
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.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
34 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
35 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
38 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
48 abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
49 private final T identifier;
51 StreamingContext(final T identifier) {
52 this.identifier = identifier;
55 static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
56 final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
57 checkArgument(potential.isPresent(),
58 "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
59 schema.getChildNodes());
61 final DataSchemaNode result = potential.get();
62 // We try to look up if this node was added by augmentation
63 if (schema instanceof DataSchemaNode && result.isAugmenting()) {
64 for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
65 if (aug.findDataChildByName(result.getQName()).isPresent()) {
66 return new Augmentation(aug, schema);
70 return fromDataSchemaNode(result);
73 static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
74 if (potential instanceof ContainerSchemaNode) {
75 return new Container((ContainerSchemaNode) potential);
76 } else if (potential instanceof ListSchemaNode) {
77 return fromListSchemaNode((ListSchemaNode) potential);
78 } else if (potential instanceof LeafSchemaNode) {
79 return new Leaf((LeafSchemaNode) potential);
80 } else if (potential instanceof ChoiceSchemaNode) {
81 return new Choice((ChoiceSchemaNode) potential);
82 } else if (potential instanceof LeafListSchemaNode) {
83 return fromLeafListSchemaNode((LeafListSchemaNode) potential);
84 } else if (potential instanceof AnyxmlSchemaNode) {
85 return new AnyXml((AnyxmlSchemaNode) potential);
91 public final T getIdentifier() {
95 abstract StreamingContext<?> getChild(PathArgument child);
98 * Writing node structure that is described by series of {@link PathArgument}
99 * into {@link NormalizedNodeStreamWriter}.
101 * @param writer output {@link NormalizedNode} writer
102 * @param first the first {@link PathArgument}
103 * @param others iterator that points to next path arguments
104 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
106 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
110 * Writing node structure that is described by provided {@link PathNode} into {@link NormalizedNodeStreamWriter}.
112 * @param writer output {@link NormalizedNode} writer
113 * @param first the first {@link PathArgument}
114 * @param tree subtree of path arguments that starts with the first path argument
115 * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
117 abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, PathNode tree)
120 abstract boolean isMixin();
122 private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
123 final Optional<DataSchemaNode> potential = parent.findDataChildByName(child);
124 return potential.isPresent() ? potential
125 : findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
128 private static Optional<DataSchemaNode> findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
129 for (final ChoiceSchemaNode choice : choices) {
130 for (final CaseSchemaNode caze : choice.getCases()) {
131 if (findChildSchemaNode(caze, child).isPresent()) {
132 return Optional.of(choice);
136 return Optional.empty();
139 private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
140 final List<QName> keyDefinition = potential.getKeyDefinition();
141 if (keyDefinition == null || keyDefinition.isEmpty()) {
142 return new UnkeyedListMixin(potential);
144 return potential.isUserOrdered() ? new OrderedMapMixin(potential)
145 : new UnorderedMapMixin(potential);
148 private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
149 return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
150 : new UnorderedLeafListMixin(potential);
153 private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
154 AbstractComposite(final T identifier) {
159 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
160 final Iterator<PathArgument> others) throws IOException {
161 verifyActualPathArgument(first);
163 emitElementStart(writer, first);
164 if (others.hasNext()) {
165 final PathArgument childPath = others.next();
166 final StreamingContext<?> childOp = getChildOperation(childPath);
167 childOp.streamToWriter(writer, childPath, others);
173 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
174 final PathNode subtree) throws IOException {
175 verifyActualPathArgument(first);
177 emitElementStart(writer, first);
178 for (final PathNode node : subtree.children()) {
179 emitChildTreeNode(writer, node);
184 void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException {
185 final PathArgument childPath = node.element();
186 getChildOperation(childPath).streamToWriter(writer, childPath, node);
189 private void verifyActualPathArgument(final PathArgument first) {
191 final QName type = getIdentifier().getNodeType();
192 final QName firstType = first.getNodeType();
193 checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
197 abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
199 @SuppressWarnings("checkstyle:illegalCatch")
200 StreamingContext<?> getChildOperation(final PathArgument childPath) {
201 final StreamingContext<?> childOp;
203 childOp = getChild(childPath);
204 } catch (final RuntimeException e) {
205 throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
207 checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
212 private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
213 private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
214 private final DataNodeContainer schema;
216 AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
218 this.schema = schema;
222 final StreamingContext<?> getChild(final PathArgument child) {
223 StreamingContext<?> potential = byArg.get(child);
224 if (potential != null) {
227 potential = fromLocalSchema(child);
228 if (potential != null) {
229 byArg.put(potential.getIdentifier(), potential);
234 private StreamingContext<?> fromLocalSchema(final PathArgument child) {
235 if (child instanceof AugmentationIdentifier) {
236 return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
239 return fromSchemaAndQNameChecked(schema, child.getNodeType());
243 private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
244 private final ListEntry innerNode;
245 private final List<QName> keyLeaves;
247 AbstractMapMixin(final ListSchemaNode list) {
248 super(NodeIdentifier.create(list.getQName()));
249 this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list);
250 this.keyLeaves = list.getKeyDefinition();
254 final StreamingContext<?> getChild(final PathArgument child) {
255 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
259 final boolean isMixin() {
264 final void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException {
265 final NodeIdentifierWithPredicates childPath = (NodeIdentifierWithPredicates) node.element();
266 final StreamingContext<?> childOp = getChildOperation(childPath);
267 if (childPath.size() == 0 && node.isEmpty() || childPath.keySet().containsAll(keyLeaves)) {
268 // This is a query for the entire list, or the query specifies everything we need
269 childOp.streamToWriter(writer, childPath, node);
273 // Inexact query, we need to also request the leaf nodes we need to for reconstructing a valid instance
274 // NodeIdentifierWithPredicates.
275 childOp.streamToWriter(writer, childPath, node.copyWith(keyLeaves.stream()
276 .filter(qname -> !childPath.containsKey(qname))
277 .map(NodeIdentifier::new)
278 .collect(Collectors.toUnmodifiableList())));
282 private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
283 AbstractSimple(final T identifier) {
288 final StreamingContext<?> getChild(final PathArgument child) {
293 final boolean isMixin() {
298 final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
299 final PathNode tree) throws IOException {
300 streamToWriter(writer, first, Collections.emptyIterator());
304 private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
305 AnyXml(final AnyxmlSchemaNode schema) {
306 super(NodeIdentifier.create(schema.getQName()));
310 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
311 final Iterator<PathArgument> others) throws IOException {
312 writer.startAnyxmlNode(getIdentifier(), DOMSource.class);
313 // FIXME: why are we not emitting a value?
318 private static final class Choice extends AbstractComposite<NodeIdentifier> {
319 private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
321 Choice(final ChoiceSchemaNode schema) {
322 super(NodeIdentifier.create(schema.getQName()));
323 final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
325 for (final CaseSchemaNode caze : schema.getCases()) {
326 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
327 final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
328 byArgBuilder.put(childOp.getIdentifier(), childOp);
331 byArg = byArgBuilder.build();
335 StreamingContext<?> getChild(final PathArgument child) {
336 return byArg.get(child);
345 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
346 writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
350 private static final class Leaf extends AbstractSimple<NodeIdentifier> {
351 Leaf(final LeafSchemaNode potential) {
352 super(new NodeIdentifier(potential.getQName()));
356 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
357 final Iterator<PathArgument> others) throws IOException {
358 writer.startLeafNode(getIdentifier());
359 // FIXME: why are we not emitting a value?
364 private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
365 LeafListEntry(final LeafListSchemaNode potential) {
366 super(new NodeWithValue<>(potential.getQName(), null));
370 void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
371 final Iterator<PathArgument> others) throws IOException {
372 checkArgument(first instanceof NodeWithValue);
373 final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
374 writer.startLeafSetEntryNode(identifier);
375 writer.scalarValue(identifier.getValue());
380 private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
381 ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
382 super(identifier, schema);
391 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
392 final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
393 writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
395 for (Entry<QName, Object> entry : identifier.entrySet()) {
396 writer.startLeafNode(new NodeIdentifier(entry.getKey()));
397 writer.scalarValue(entry.getValue());
403 private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
404 UnkeyedListItem(final ListSchemaNode schema) {
405 super(NodeIdentifier.create(schema.getQName()), schema);
414 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
415 writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
419 private static final class Container extends AbstractDataContainer<NodeIdentifier> {
420 Container(final ContainerSchemaNode schema) {
421 super(NodeIdentifier.create(schema.getQName()), schema);
430 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
431 writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
435 private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
436 private final StreamingContext<?> innerOp;
438 LeafListMixin(final LeafListSchemaNode potential) {
439 super(NodeIdentifier.create(potential.getQName()));
440 innerOp = new LeafListEntry(potential);
444 final StreamingContext<?> getChild(final PathArgument child) {
445 return child instanceof NodeWithValue ? innerOp : null;
449 final boolean isMixin() {
454 private static final class OrderedLeafListMixin extends LeafListMixin {
455 OrderedLeafListMixin(final LeafListSchemaNode potential) {
460 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
461 writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
465 private static class UnorderedLeafListMixin extends LeafListMixin {
466 UnorderedLeafListMixin(final LeafListSchemaNode potential) {
471 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
472 writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
476 private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
477 Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
478 super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
479 EffectiveAugmentationSchema.create(augmentation, schema));
488 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
489 writer.startAugmentationNode(getIdentifier());
493 private static final class UnorderedMapMixin extends AbstractMapMixin {
494 UnorderedMapMixin(final ListSchemaNode list) {
499 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
500 writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
504 private static final class OrderedMapMixin extends AbstractMapMixin {
505 OrderedMapMixin(final ListSchemaNode list) {
510 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
511 writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
515 private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
516 private final UnkeyedListItem innerNode;
518 UnkeyedListMixin(final ListSchemaNode list) {
519 super(NodeIdentifier.create(list.getQName()));
520 this.innerNode = new UnkeyedListItem(list);
524 StreamingContext<?> getChild(final PathArgument child) {
525 return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
534 void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
535 writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);