From: Peter Kajsa Date: Mon, 14 Aug 2017 17:35:33 +0000 (+0200) Subject: Bug 7246 - Fix of SchemaTracker initialization and lookup of schema nodes X-Git-Tag: release/carbon-sr2~2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F40%2F62540%2F2;p=yangtools.git Bug 7246 - Fix of SchemaTracker initialization and lookup of schema nodes SchemaUtils methods perform lookup in both the namespace of groupings and the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, actions, notifications, anydatas, and anyxmls. In consequence, performed lookups are ambiguous due to possible name conflicts between these namespaces. Change-Id: Icd3e141b21d0adaf0126b539454e640c0ea0b5a6 Signed-off-by: Peter Kajsa (cherry picked from commit d77eb113748d9571c5bd9588e8f6c745182b8b05) --- diff --git a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/Bug7246Test.java b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/Bug7246Test.java new file mode 100644 index 0000000000..682fcc6765 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/Bug7246Test.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.codec.gson; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URI; +import java.net.URISyntaxException; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class Bug7246Test { + private static String NS = "my-namespace"; + private static String REV = "1970-01-01"; + + @Test + public void test() throws Exception { + final SchemaContext schemaContext = YangParserTestUtils.parseYangSource("/bug7246/yang/rpc-test.yang"); + final JsonParser parser = new JsonParser(); + final JsonElement expextedJson = parser + .parse(new FileReader(new File(getClass().getResource("/bug7246/json/expected-output.json").toURI()))); + + final DataContainerChild inputStructure = ImmutableContainerNodeBuilder.create() + .withNodeIdentifier(new NodeIdentifier(qN("my-name"))) + .withChild(ImmutableNodes.leafNode(new NodeIdentifier(qN("my-name")), "my-value")).build(); + final SchemaPath rootPath = SchemaPath.create(true, qN("my-name"), qN("input")); + final Writer writer = new StringWriter(); + final String jsonOutput = normalizedNodeToJsonStreamTransformation(schemaContext, rootPath, writer, + inputStructure); + final JsonElement serializedJson = parser.parse(jsonOutput); + + assertEquals(expextedJson, serializedJson); + } + + private QName qN(final String localName) { + return QName.create(NS, REV, localName); + } + + private static String normalizedNodeToJsonStreamTransformation(final SchemaContext schemaContext, + final SchemaPath path, final Writer writer, final NormalizedNode inputStructure) + throws IOException, URISyntaxException { + + final NormalizedNodeStreamWriter jsonStream = JSONNormalizedNodeStreamWriter.createExclusiveWriter( + JSONCodecFactory.getShared(schemaContext), path, new URI(NS), + JsonWriterFactory.createJsonWriter(writer, 2)); + final NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream); + nodeWriter.write(inputStructure); + + nodeWriter.close(); + return writer.toString(); + } +} diff --git a/yang/yang-data-codec-gson/src/test/resources/bug7246/json/expected-output.json b/yang/yang-data-codec-gson/src/test/resources/bug7246/json/expected-output.json new file mode 100644 index 0000000000..6dba63e79f --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/bug7246/json/expected-output.json @@ -0,0 +1,5 @@ +{ + "my-name": { + "my-name": "my-value" + } +} diff --git a/yang/yang-data-codec-gson/src/test/resources/bug7246/yang/rpc-test.yang b/yang/yang-data-codec-gson/src/test/resources/bug7246/yang/rpc-test.yang new file mode 100644 index 0000000000..004183e286 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/bug7246/yang/rpc-test.yang @@ -0,0 +1,37 @@ +module rpc-test { + namespace my-namespace; + prefix p; + + feature my-name; + + identity my-name; + + extension my-name; + + typedef my-name { + type string; + } + + grouping my-name { + leaf my-name { + type my-name; + } + } + + rpc my-name { + input { + container my-name { + leaf my-name { + type my-name; + } + } + } + output { + container my-name { + leaf my-name { + type my-name; + } + } + } + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java index 75722e7f68..1609aaa597 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java @@ -12,8 +12,10 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import java.io.IOException; import java.util.ArrayDeque; +import java.util.Collection; import java.util.Deque; import java.util.HashSet; +import java.util.Optional; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; @@ -52,9 +54,18 @@ public final class SchemaTracker { private final DataNodeContainer root; private SchemaTracker(final SchemaContext context, final SchemaPath path) { - SchemaNode current = SchemaUtils.findParentSchemaOnPath(context, path); - Preconditions.checkArgument(current instanceof DataNodeContainer,"Schema path must point to container or list or an rpc input/output. Supplied path %s pointed to: %s",path,current); - root = (DataNodeContainer) current; + final Collection schemaNodes = SchemaUtils.findParentSchemaNodesOnPath(context, path); + Preconditions.checkArgument(!schemaNodes.isEmpty(), "Unable to find schema node for supplied schema path: %s", + path); + if (schemaNodes.size() > 1) { + LOG.warn("More possible schema nodes {} for supplied schema path {}", schemaNodes, path); + } + final Optional current = schemaNodes.stream().filter(node -> node instanceof DataNodeContainer) + .findFirst(); + Preconditions.checkArgument(current.isPresent(), + "Schema path must point to container or list or an rpc input/output. Supplied path %s pointed to: %s", + path, current); + root = (DataNodeContainer) current.get(); } /** @@ -174,7 +185,7 @@ public final class SchemaTracker { return (LeafListSchemaNode) parent; } - final SchemaNode child = SchemaUtils.findChildSchemaByQName((SchemaNode) parent, qname); + final SchemaNode child = SchemaUtils.findDataChildSchemaByQName((SchemaNode) parent, qname); Preconditions.checkArgument(child instanceof LeafListSchemaNode, "Node %s is neither a leaf-list nor currently in a leaf-list", child.getPath()); return (LeafListSchemaNode) child; diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java index 037265f94f..4cd619b811 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaOrderedNormalizedNodeWriter.java @@ -7,6 +7,7 @@ */ package org.opendaylight.yangtools.yang.data.impl.schema; +import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import java.io.IOException; import java.util.Collection; @@ -26,13 +27,15 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This is an iterator over a {@link NormalizedNode}. Unlike {@link NormalizedNodeWriter}, * this iterates over elements in order as they are defined in .yang file. */ public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter { - + private static final Logger LOG = LoggerFactory.getLogger(SchemaOrderedNormalizedNodeWriter.class); private final SchemaContext schemaContext; private final SchemaNode root; private final NormalizedNodeStreamWriter writer; @@ -41,15 +44,26 @@ public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter { /** * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. - * @param writer Back-end writer - * @param schemaContext Schema context - * @param path path + * + * @param writer + * Back-end writer + * @param schemaContext + * Schema context + * @param path + * path */ - public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext, final SchemaPath path) { + public SchemaOrderedNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext, + final SchemaPath path) { super(writer); this.writer = writer; this.schemaContext = schemaContext; - this.root = SchemaUtils.findParentSchemaOnPath(schemaContext, path); + final Collection schemaNodes = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, path); + Preconditions.checkArgument(!schemaNodes.isEmpty(), "Unable to find schema node for supplied schema path: %s", + path); + if (schemaNodes.size() > 1) { + LOG.warn("More possible schema nodes {} for supplied schema path {}", schemaNodes, path); + } + this.root = schemaNodes.iterator().next(); } @Override @@ -102,19 +116,20 @@ public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter { } private void write(final List> nodes, final SchemaNode dataSchemaNode) throws IOException { - for (NormalizedNode node : nodes) { + for (final NormalizedNode node : nodes) { write(node, dataSchemaNode); } } + @Override protected boolean writeChildren(final Iterable> children) throws IOException { return writeChildren(children, currentSchemaNode, true); } - private boolean writeChildren(final Iterable> children, final SchemaNode parentSchemaNode, boolean endParent) throws IOException { + private boolean writeChildren(final Iterable> children, final SchemaNode parentSchemaNode, final boolean endParent) throws IOException { //Augmentations cannot be gotten with node.getChild so create our own structure with augmentations resolved - ArrayListMultimap> qNameToNodes = ArrayListMultimap.create(); - for (NormalizedNode child : children) { + final ArrayListMultimap> qNameToNodes = ArrayListMultimap.create(); + for (final NormalizedNode child : children) { if (child instanceof AugmentationNode) { qNameToNodes.putAll(resolveAugmentations(child)); } else { @@ -126,20 +141,20 @@ public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter { if (parentSchemaNode instanceof ListSchemaNode && qNameToNodes.containsKey(parentSchemaNode.getQName())) { write(qNameToNodes.get(parentSchemaNode.getQName()), parentSchemaNode); } else { - for (DataSchemaNode schemaNode : ((DataNodeContainer) parentSchemaNode).getChildNodes()) { + for (final DataSchemaNode schemaNode : ((DataNodeContainer) parentSchemaNode).getChildNodes()) { write(qNameToNodes.get(schemaNode.getQName()), schemaNode); } } } else if (parentSchemaNode instanceof ChoiceSchemaNode) { - for (ChoiceCaseNode ccNode : ((ChoiceSchemaNode) parentSchemaNode).getCases()) { - for (DataSchemaNode dsn : ccNode.getChildNodes()) { + for (final ChoiceCaseNode ccNode : ((ChoiceSchemaNode) parentSchemaNode).getCases()) { + for (final DataSchemaNode dsn : ccNode.getChildNodes()) { if (qNameToNodes.containsKey(dsn.getQName())) { write(qNameToNodes.get(dsn.getQName()), dsn); } } } } else { - for (NormalizedNode child : children) { + for (final NormalizedNode child : children) { writeLeaf(child); } } @@ -159,7 +174,7 @@ public class SchemaOrderedNormalizedNodeWriter extends NormalizedNodeWriter { private ArrayListMultimap> resolveAugmentations(final NormalizedNode child) { final ArrayListMultimap> resolvedAugs = ArrayListMultimap.create(); - for (NormalizedNode node : ((AugmentationNode) child).getValue()) { + for (final NormalizedNode node : ((AugmentationNode) child).getValue()) { if (node instanceof AugmentationNode) { resolvedAugs.putAll(resolveAugmentations(node)); } else { diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtils.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtils.java index 4c9eb686c6..1999c657d5 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtils.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtils.java @@ -11,13 +11,16 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; @@ -25,12 +28,14 @@ import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.NotificationNodeContainer; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; @@ -49,19 +54,19 @@ public final class SchemaUtils { public static Optional findFirstSchema(final QName qname, final Iterable dataSchemaNode) { DataSchemaNode sNode = null; if (dataSchemaNode != null && qname != null) { - for (DataSchemaNode dsn : dataSchemaNode) { + for (final DataSchemaNode dsn : dataSchemaNode) { if (qname.isEqualWithoutRevision(dsn.getQName())) { if (sNode == null || sNode.getQName().getRevision().compareTo(dsn.getQName().getRevision()) < 0) { sNode = dsn; } } else if (dsn instanceof ChoiceSchemaNode) { - for (ChoiceCaseNode choiceCase : ((ChoiceSchemaNode) dsn).getCases()) { + for (final ChoiceCaseNode choiceCase : ((ChoiceSchemaNode) dsn).getCases()) { final DataSchemaNode dataChildByName = choiceCase.getDataChildByName(qname); if (dataChildByName != null) { return Optional.of(dataChildByName); } - Optional foundDsn = findFirstSchema(qname, choiceCase.getChildNodes()); + final Optional foundDsn = findFirstSchema(qname, choiceCase.getChildNodes()); if (foundDsn.isPresent()) { return foundDsn; } @@ -93,7 +98,7 @@ public final class SchemaUtils { return findSchemaForChild(schema, qname); } - Optional childSchemaOptional = findFirstSchema(qname, schema.getChildNodes()); + final Optional childSchemaOptional = findFirstSchema(qname, schema.getChildNodes()); if (!childSchemaOptional.isPresent()) { return null; } @@ -101,14 +106,14 @@ public final class SchemaUtils { } public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname, final Iterable childNodes) { - Optional childSchema = findFirstSchema(qname, childNodes); + final Optional childSchema = findFirstSchema(qname, childNodes); Preconditions.checkState(childSchema.isPresent(), "Unknown child(ren) node(s) detected, identified by: %s, in: %s", qname, schema); return childSchema.get(); } public static AugmentationSchema findSchemaForAugment(final AugmentationTarget schema, final Set qNames) { - Optional schemaForAugment = findAugment(schema, qNames); + final Optional schemaForAugment = findAugment(schema, qNames); Preconditions.checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s", qNames, schema); return schemaForAugment.get(); @@ -117,7 +122,7 @@ public final class SchemaUtils { public static AugmentationSchema findSchemaForAugment(final ChoiceSchemaNode schema, final Set qNames) { Optional schemaForAugment = Optional.absent(); - for (ChoiceCaseNode choiceCaseNode : schema.getCases()) { + for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) { schemaForAugment = findAugment(choiceCaseNode, qNames); if (schemaForAugment.isPresent()) { break; @@ -130,8 +135,8 @@ public final class SchemaUtils { } private static Optional findAugment(final AugmentationTarget schema, final Set qNames) { - for (AugmentationSchema augment : schema.getAvailableAugmentations()) { - HashSet qNamesFromAugment = Sets.newHashSet(Collections2.transform(augment.getChildNodes(), + for (final AugmentationSchema augment : schema.getAvailableAugmentations()) { + final HashSet qNamesFromAugment = Sets.newHashSet(Collections2.transform(augment.getChildNodes(), DataSchemaNode::getQName)); if (qNamesFromAugment.equals(qNames)) { @@ -143,8 +148,8 @@ public final class SchemaUtils { } public static DataSchemaNode findSchemaForChild(final ChoiceSchemaNode schema, final QName childPartialQName) { - for (ChoiceCaseNode choiceCaseNode : schema.getCases()) { - Optional childSchema = findFirstSchema(childPartialQName, choiceCaseNode.getChildNodes()); + for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) { + final Optional childSchema = findFirstSchema(childPartialQName, choiceCaseNode.getChildNodes()); if (childSchema.isPresent()) { return childSchema.get(); } @@ -166,7 +171,7 @@ public final class SchemaUtils { } private static Map mapChildElementsFromChoices(final DataNodeContainer schema, final Iterable childNodes) { - Map mappedChoices = Maps.newLinkedHashMap(); + final Map mappedChoices = Maps.newLinkedHashMap(); for (final DataSchemaNode childSchema : childNodes) { if (childSchema instanceof ChoiceSchemaNode) { @@ -175,9 +180,9 @@ public final class SchemaUtils { continue; } - for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) { + for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) { - for (QName qName : getChildNodesRecursive(choiceCaseNode)) { + for (final QName qName : getChildNodesRecursive(choiceCaseNode)) { mappedChoices.put(qName, (ChoiceSchemaNode) childSchema); } } @@ -192,7 +197,7 @@ public final class SchemaUtils { return false; } - for (AugmentationSchema augmentationSchema : ((AugmentationTarget) schema).getAvailableAugmentations()) { + for (final AugmentationSchema augmentationSchema : ((AugmentationTarget) schema).getAvailableAugmentations()) { if (augmentationSchema.getDataChildByName(childSchema.getQName()) != null) { return true; } @@ -209,12 +214,12 @@ public final class SchemaUtils { */ public static Map mapChildElementsFromAugments(final AugmentationTarget schema) { - Map childNodesToAugmentation = Maps.newLinkedHashMap(); + final Map childNodesToAugmentation = Maps.newLinkedHashMap(); // Find QNames of augmented child nodes - Map augments = Maps.newHashMap(); + final Map augments = Maps.newHashMap(); for (final AugmentationSchema augmentationSchema : schema.getAvailableAugmentations()) { - for (DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) { + for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) { augments.put(dataSchemaNode.getQName(), augmentationSchema); } } @@ -223,22 +228,22 @@ public final class SchemaUtils { // because nodes from augment do not contain nodes from other augmentations if (schema instanceof DataNodeContainer) { - for (DataSchemaNode child : ((DataNodeContainer) schema).getChildNodes()) { + for (final DataSchemaNode child : ((DataNodeContainer) schema).getChildNodes()) { // If is not augmented child, continue if (!(augments.containsKey(child.getQName()))) { continue; } - AugmentationSchema mostTopAugmentation = augments.get(child.getQName()); + final AugmentationSchema mostTopAugmentation = augments.get(child.getQName()); // recursively add all child nodes in case of augment, case and choice if (child instanceof AugmentationSchema || child instanceof ChoiceCaseNode) { - for (QName qName : getChildNodesRecursive((DataNodeContainer) child)) { + for (final QName qName : getChildNodesRecursive((DataNodeContainer) child)) { childNodesToAugmentation.put(qName, mostTopAugmentation); } } else if (child instanceof ChoiceSchemaNode) { - for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases()) { - for (QName qName : getChildNodesRecursive(choiceCaseNode)) { + for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases()) { + for (final QName qName : getChildNodesRecursive(choiceCaseNode)) { childNodesToAugmentation.put(qName, mostTopAugmentation); } } @@ -250,12 +255,12 @@ public final class SchemaUtils { // Choice Node has to map child nodes from all its cases if (schema instanceof ChoiceSchemaNode) { - for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases()) { + for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases()) { if (!(augments.containsKey(choiceCaseNode.getQName()))) { continue; } - for (QName qName : getChildNodesRecursive(choiceCaseNode)) { + for (final QName qName : getChildNodesRecursive(choiceCaseNode)) { childNodesToAugmentation.put(qName, augments.get(choiceCaseNode.getQName())); } } @@ -273,11 +278,11 @@ public final class SchemaUtils { * @return set of QNames */ public static Set getChildNodesRecursive(final DataNodeContainer nodeContainer) { - Set allChildNodes = Sets.newHashSet(); + final Set allChildNodes = Sets.newHashSet(); - for (DataSchemaNode childSchema : nodeContainer.getChildNodes()) { + for (final DataSchemaNode childSchema : nodeContainer.getChildNodes()) { if (childSchema instanceof ChoiceSchemaNode) { - for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) { + for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) { allChildNodes.addAll(getChildNodesRecursive(choiceCaseNode)); } } else if (childSchema instanceof AugmentationSchema || childSchema instanceof ChoiceCaseNode) { @@ -311,8 +316,8 @@ public final class SchemaUtils { if (targetSchema instanceof DataNodeContainer) { realChildNodes = getRealSchemasForAugment((DataNodeContainer)targetSchema, augmentSchema); } else if (targetSchema instanceof ChoiceSchemaNode) { - for (DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) { - for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases()) { + for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) { + for (final ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases()) { if (getChildNodesRecursive(choiceCaseNode).contains(dataSchemaNode.getQName())) { realChildNodes.add(choiceCaseNode.getDataChildByName(dataSchemaNode.getQName())); } @@ -325,16 +330,16 @@ public final class SchemaUtils { public static Set getRealSchemasForAugment(final DataNodeContainer targetSchema, final AugmentationSchema augmentSchema) { - Set realChildNodes = Sets.newHashSet(); - for (DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) { - DataSchemaNode realChild = targetSchema.getDataChildByName(dataSchemaNode.getQName()); + final Set realChildNodes = Sets.newHashSet(); + for (final DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) { + final DataSchemaNode realChild = targetSchema.getDataChildByName(dataSchemaNode.getQName()); realChildNodes.add(realChild); } return realChildNodes; } public static Optional detectCase(final ChoiceSchemaNode schema, final DataContainerChild child) { - for (ChoiceCaseNode choiceCaseNode : schema.getCases()) { + for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) { if (child instanceof AugmentationNode && belongsToCaseAugment(choiceCaseNode, (AugmentationIdentifier) child.getIdentifier())) { return Optional.of(choiceCaseNode); @@ -347,10 +352,10 @@ public final class SchemaUtils { } public static boolean belongsToCaseAugment(final ChoiceCaseNode caseNode, final AugmentationIdentifier childToProcess) { - for (AugmentationSchema augmentationSchema : caseNode.getAvailableAugmentations()) { + for (final AugmentationSchema augmentationSchema : caseNode.getAvailableAugmentations()) { - Set currentAugmentChildNodes = Sets.newHashSet(); - for (DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) { + final Set currentAugmentChildNodes = Sets.newHashSet(); + for (final DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) { currentAugmentChildNodes.add(dataSchemaNode.getQName()); } @@ -372,8 +377,8 @@ public final class SchemaUtils { */ public static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) { if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) { - for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) { - DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName()); + for (final AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) { + final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName()); if (childInAugmentation != null) { return augmentation; } @@ -388,11 +393,46 @@ public final class SchemaUtils { } /** - * Finds schema node for given path in schema context. - * @param schemaContext schema context - * @param path path + * Finds schema node for given path in schema context. This method performs + * lookup in the namespace of all leafs, leaf-lists, lists, containers, + * choices, rpcs, actions, notifications, anydatas, and anyxmls according to + * Rfc6050/Rfc7950 section 6.2.1. + * + * @param schemaContext + * schema context + * @param path + * path + * @return schema node on path + */ + public static SchemaNode findDataParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) { + SchemaNode current = Preconditions.checkNotNull(schemaContext); + for (final QName qname : path.getPathFromRoot()) { + current = findDataChildSchemaByQName(current, qname); + } + return current; + } + + /** + * Finds schema node for given path in schema context. This method performs + * lookup in both the namespace of groupings and the namespace of all leafs, + * leaf-lists, lists, containers, choices, rpcs, actions, notifications, + * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1. + * + * This method is deprecated, because name conflicts can occur between the + * namespace of groupings and namespace of data nodes and in consequence + * lookup could be ambiguous. + * + * @param schemaContext + * schema context + * @param path + * path * @return schema node on path + * + * @deprecated use + * {@link #findParentSchemaNodesOnPath(SchemaContext, SchemaPath)} + * instead. */ + @Deprecated public static SchemaNode findParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) { SchemaNode current = Preconditions.checkNotNull(schemaContext); for (final QName qname : path.getPathFromRoot()) { @@ -402,39 +442,46 @@ public final class SchemaUtils { } /** - * Find child schema node identified by its QName within a provided schema node. - * @param node schema node - * @param qname QName - * @return child schema node - * @throws java.lang.IllegalArgumentException if the schema node does not allow children + * Find child data schema node identified by its QName within a provided + * schema node. This method performs lookup in the namespace of all leafs, + * leaf-lists, lists, containers, choices, rpcs, actions, notifications, + * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1. + * + * @param node + * schema node + * @param qname + * QName + * @return data child schema node + * @throws java.lang.IllegalArgumentException + * if the schema node does not allow children */ - public static SchemaNode findChildSchemaByQName(final SchemaNode node, final QName qname) { + @Nullable + public static SchemaNode findDataChildSchemaByQName(final SchemaNode node, final QName qname) { SchemaNode child = null; - if (node instanceof DataNodeContainer) { child = ((DataNodeContainer) node).getDataChildByName(qname); - if (child == null && node instanceof SchemaContext) { - child = tryFindGroupings((SchemaContext) node, qname).orNull(); + child = tryFindRpc((SchemaContext) node, qname).orNull(); } - - if (child == null && node instanceof SchemaContext) { - child = tryFindNotification((SchemaContext) node, qname) - .or(tryFindRpc(((SchemaContext) node), qname)).orNull(); + if (child == null && node instanceof NotificationNodeContainer) { + child = tryFindNotification((NotificationNodeContainer) node, qname).orNull(); + } + if (child == null && node instanceof ActionNodeContainer) { + child = tryFindAction((ActionNodeContainer) node, qname).orNull(); } } else if (node instanceof ChoiceSchemaNode) { child = ((ChoiceSchemaNode) node).getCaseNodeByName(qname); } else if (node instanceof RpcDefinition) { switch (qname.getLocalName()) { - case "input": - child = ((RpcDefinition) node).getInput(); - break; - case "output": - child = ((RpcDefinition) node).getOutput(); - break; - default: - child = null; - break; + case "input": + child = ((RpcDefinition) node).getInput(); + break; + case "output": + child = ((RpcDefinition) node).getOutput(); + break; + default: + child = null; + break; } } else { throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node)); @@ -443,16 +490,127 @@ public final class SchemaUtils { return child; } - private static Optional tryFindGroupings(final SchemaContext ctx, final QName qname) { - return Optional.fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null)); + /** + * Find child schema node identified by its QName within a provided schema + * node. This method performs lookup in both the namespace of groupings and + * the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, + * actions, notifications, anydatas, and anyxmls according to + * Rfc6050/Rfc7950 section 6.2.1. + * + * This method is deprecated, because name conflicts can occur between the + * namespace of groupings and namespace of data nodes and in consequence + * lookup could be ambiguous. + * + * @param node + * schema node + * @param qname + * QName + * @return child schema node + * @throws java.lang.IllegalArgumentException + * if the schema node does not allow children + * + * @deprecated use + * {@link #findChildSchemaNodesByQName(SchemaNode, QName)} + * instead. + */ + @Deprecated + public static SchemaNode findChildSchemaByQName(final SchemaNode node, final QName qname) { + SchemaNode child = findDataChildSchemaByQName(node, qname); + if (child == null && node instanceof DataNodeContainer) { + child = tryFindGroupings((DataNodeContainer) node, qname).orNull(); + } + + return child; + } + + /** + * Finds schema node for given path in schema context. This method performs + * lookup in both the namespace of groupings and the namespace of all leafs, + * leaf-lists, lists, containers, choices, rpcs, actions, notifications, + * anydatas, and anyxmls according to Rfc6050/Rfc7950 section 6.2.1. + * + * This method returns collection of SchemaNodes, because name conflicts can + * occur between the namespace of groupings and namespace of data nodes. + * This method finds and collects all schema nodes that matches supplied + * SchemaPath and returns them all as collection of schema nodes. + * + * @param schemaContext + * schema context + * @param path + * path + * @return collection of schema nodes on path + * + */ + public static Collection findParentSchemaNodesOnPath(final SchemaContext schemaContext, + final SchemaPath path) { + final Collection currentNodes = new ArrayList<>(); + final Collection childNodes = new ArrayList<>(); + currentNodes.add(Preconditions.checkNotNull(schemaContext)); + for (final QName qname : path.getPathFromRoot()) { + for (final SchemaNode current : currentNodes) { + childNodes.addAll(findChildSchemaNodesByQName(current, qname)); + } + currentNodes.clear(); + currentNodes.addAll(childNodes); + childNodes.clear(); + } + + return currentNodes; + } + + /** + * Find child schema node identified by its QName within a provided schema + * node. This method performs lookup in both the namespace of groupings and + * the namespace of all leafs, leaf-lists, lists, containers, choices, rpcs, + * actions, notifications, anydatas, and anyxmls according to + * Rfc6050/Rfc7950 section 6.2.1. + * + * This method returns collection of SchemaNodes, because name conflicts can + * occur between the namespace of groupings and namespace of data nodes. + * This method finds and collects all schema nodes with supplied QName and + * returns them all as collection of schema nodes. + * + * @param node + * schema node + * @param qname + * QName + * @return collection of child schema nodes + * @throws java.lang.IllegalArgumentException + * if the schema node does not allow children + * + */ + public static Collection findChildSchemaNodesByQName(final SchemaNode node, final QName qname) { + final List childNodes = new ArrayList<>(); + final SchemaNode dataNode = findDataChildSchemaByQName(node, qname); + if (dataNode != null) { + childNodes.add(dataNode); + } + if (node instanceof DataNodeContainer) { + final SchemaNode groupingNode = tryFindGroupings((DataNodeContainer) node, qname).orNull(); + if (groupingNode != null) { + childNodes.add(groupingNode); + } + } + return childNodes.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(childNodes); + } + + private static Optional tryFindGroupings(final DataNodeContainer dataNodeContainer, final QName qname) { + return Optional + .fromNullable(Iterables.find(dataNodeContainer.getGroupings(), new SchemaNodePredicate(qname), null)); } private static Optional tryFindRpc(final SchemaContext ctx, final QName qname) { return Optional.fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null)); } - private static Optional tryFindNotification(final SchemaContext ctx, final QName qname) { - return Optional.fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null)); + private static Optional tryFindNotification(final NotificationNodeContainer notificationContanier, + final QName qname) { + return Optional.fromNullable( + Iterables.find(notificationContanier.getNotifications(), new SchemaNodePredicate(qname), null)); + } + + private static Optional tryFindAction(final ActionNodeContainer actionContanier, final QName qname) { + return Optional.fromNullable(Iterables.find(actionContanier.getActions(), new SchemaNodePredicate(qname), null)); } private static final class SchemaNodePredicate implements Predicate { diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtilsTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtilsTest.java new file mode 100644 index 0000000000..7fb8e4aebb --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/SchemaUtilsTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.impl.schema; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.ActionDefinition; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class SchemaUtilsTest { + private static String NS = "my-namespace"; + private static String REV = "1970-01-01"; + + @Test + public void test() throws Exception { + final SchemaContext schemaContext = YangParserTestUtils.parseYangSource("/schema-utils-test/foo.yang"); + assertTrue(SchemaUtils.findDataParentSchemaOnPath(schemaContext, + SchemaPath.create(true, qN("my-name"), qN("my-name"))) instanceof ContainerSchemaNode); + assertTrue(SchemaUtils.findDataParentSchemaOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-2"), qN("my-name"))) instanceof NotificationDefinition); + assertTrue(SchemaUtils.findDataParentSchemaOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-2"), qN("my-name-2"))) instanceof ActionDefinition); + } + + @Test + public void testNameConflicts() throws Exception { + final SchemaContext schemaContext = YangParserTestUtils + .parseYangSource("/schema-utils-test/name-conflicts.yang"); + // test my-name conflicts + assertEquals(8, SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name"), qN("my-name"), qN("my-name"))).size()); + + // test target container + final Collection target = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-2"), qN("my-name-2"), qN("target"))); + assertEquals(1, target.size()); + assertTrue(target.iterator().next() instanceof ContainerSchemaNode); + + // test l schema nodes (i.e. container and two leafs) + Collection l = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-3"), qN("input"), qN("con-3"), qN("l"))); + assertEquals(1, l.size()); + assertTrue(l.iterator().next() instanceof ContainerSchemaNode); + + l = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-3"), qN("input"), qN("con-1"), qN("l"))); + assertEquals(1, l.size()); + assertTrue(l.iterator().next() instanceof LeafSchemaNode); + + l = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-3"), qN("input"), qN("con-2"), qN("l"))); + assertTrue(l.isEmpty()); + + l = SchemaUtils.findParentSchemaNodesOnPath(schemaContext, + SchemaPath.create(true, qN("my-name-3"), qN("output"), qN("con-2"), qN("l"))); + assertEquals(1, l.size()); + assertTrue(l.iterator().next() instanceof LeafSchemaNode); + } + + private QName qN(final String localName) { + return QName.create(NS, REV, localName); + } + +} diff --git a/yang/yang-data-impl/src/test/resources/schema-utils-test/foo.yang b/yang/yang-data-impl/src/test/resources/schema-utils-test/foo.yang new file mode 100644 index 0000000000..4311cd2ffd --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/schema-utils-test/foo.yang @@ -0,0 +1,39 @@ +module foo { + namespace my-namespace; + prefix p; + yang-version 1.1; + + feature my-name; + + identity my-name; + + extension my-name; + + typedef my-name { + type string; + } + + grouping my-name { + } + + grouping my-name-2 { + } + + notification my-name { + grouping my-name { + } + container my-name { + } + } + + container my-name-2 { + grouping my-name { + } + notification my-name { + } + grouping my-name-2 { + } + action my-name-2 { + } + } +} diff --git a/yang/yang-data-impl/src/test/resources/schema-utils-test/name-conflicts.yang b/yang/yang-data-impl/src/test/resources/schema-utils-test/name-conflicts.yang new file mode 100644 index 0000000000..e0209c768f --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/schema-utils-test/name-conflicts.yang @@ -0,0 +1,90 @@ +module name-conflicts { + namespace "my-namespace"; + prefix nc; + + grouping my-name { + grouping my-name { + grouping my-name { + } + container my-name { + } + } + container my-name { + grouping my-name { + } + container my-name { + } + } + } + + container my-name { + grouping my-name { + grouping my-name { + } + container my-name { + } + } + container my-name { + grouping my-name { + } + container my-name { + } + } + } + + grouping my-name-2 { + grouping my-name-2 { + grouping my-name-2 { + } + container my-name-2 { + } + } + container my-name-2 { + grouping my-name-2 { + } + container my-name-2 { + } + } + } + + container my-name-2 { + grouping my-name-2 { + grouping my-name-2 { + } + container target { + } + } + container my-name-2 { + grouping my-name-2 { + } + container my-name-2 { + } + } + } + + rpc my-name-3 { + input { + container con-1 { + leaf l { + type string; + } + } + } + output { + container con-2 { + leaf l { + type string; + } + } + } + } + + grouping my-name-3 { + container input { + container con-3 { + container l { + } + } + } + } +}