From 43a5c0f10e6080613eaafec266003825b80de018 Mon Sep 17 00:00:00 2001 From: "miroslav.kovac" Date: Fri, 13 Mar 2020 14:49:23 +0100 Subject: [PATCH] Fix relative xPath resolution for leafref types If we have a leaf of type leafref with relative path that crosses through case and choice schema nodes. This stems from the historic disconnect about how the path should be handled: it needs to be resolved in the context of walking the data tree, not schema tree. That means that choice/case nodes are not part of the path in the expression and therefore need to be skipped over. JIRA: YANGTOOLS-1100 Change-Id: I9908c91b0068f52f4b6719545c738d7c39e2a228 Signed-off-by: miroslav.kovac Signed-off-by: Robert Varga --- .../yang/model/util/ut/YT1100Test.java | 50 +++++++++++++++++++ .../src/test/resources/yt1100.yang | 26 ++++++++++ .../yang/model/util/SchemaContextUtil.java | 47 ++++++++++++++--- 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1100Test.java create mode 100644 yang/yang-model-util-ut/src/test/resources/yt1100.yang diff --git a/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1100Test.java b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1100Test.java new file mode 100644 index 0000000000..5251082334 --- /dev/null +++ b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1100Test.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.model.util.ut; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.PathExpression; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class YT1100Test { + @Test + public void testChoiceCaseRelativeLeafref() { + final EffectiveModelContext context = YangParserTestUtils.parseYangResource("/yt1100.yang"); + final Module module = context.findModule("yt1100").orElseThrow(); + final QNameModule qnm = module.getQNameModule(); + final DataSchemaNode leaf = module.findDataTreeChild( + QName.create(qnm, "foo"), QName.create(qnm, "scheduler-node"), QName.create(qnm, "child-scheduler-nodes"), + QName.create(qnm, "name")).orElseThrow(); + assertThat(leaf, instanceOf(LeafSchemaNode.class)); + + final TypeDefinition type = ((LeafSchemaNode) leaf).getType(); + assertThat(type, instanceOf(LeafrefTypeDefinition.class)); + final PathExpression leafref = ((LeafrefTypeDefinition) type).getPathStatement(); + + final SchemaNode ref = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, leafref); + assertThat(ref, instanceOf(LeafSchemaNode.class)); + final LeafSchemaNode targetLeaf = (LeafSchemaNode) ref; + assertEquals(QName.create(qnm, "name"), targetLeaf.getQName()); + assertThat(targetLeaf.getType(), instanceOf(StringTypeDefinition.class)); + } +} diff --git a/yang/yang-model-util-ut/src/test/resources/yt1100.yang b/yang/yang-model-util-ut/src/test/resources/yt1100.yang new file mode 100644 index 0000000000..1ba2fc4e9e --- /dev/null +++ b/yang/yang-model-util-ut/src/test/resources/yt1100.yang @@ -0,0 +1,26 @@ +module yt1100 { + yang-version 1.1; + namespace "a"; + prefix a; + + container foo { + list scheduler-node { + key "name"; + leaf name { + type string; + } + choice children-type { + case scheduler-node { + list child-scheduler-nodes { + key "name"; + leaf name { + type leafref { + path '../../../a:scheduler-node/a:name'; + } + } + } + } + } + } + } +} diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java index c7e7e2bf89..58ae056503 100644 --- a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java @@ -15,6 +15,7 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -643,8 +644,8 @@ public final class SchemaContextUtil { * Schema Context * @param module * Yang Module - * @param relativeXPath - * Non conditional Revision Aware Relative XPath + * @param pathStr + * xPath of leafref * @param actualSchemaNode * actual schema node * @return target schema node @@ -665,15 +666,47 @@ public final class SchemaContextUtil { final int colCount = normalizeXPath(steps); final List xpaths = colCount == 0 ? steps : steps.subList(colCount, steps.size()); - final Iterable schemaNodePath = actualSchemaNode.getPath().getPathFromRoot(); - if (Iterables.size(schemaNodePath) - colCount >= 0) { - return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount), - Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input))); + final List walkablePath = createWalkablePath(actualSchemaNode.getPath().getPathFromRoot(), + context, colCount); + + if (walkablePath.size() - colCount >= 0) { + return Iterables.concat(Iterables.limit(walkablePath, walkablePath.size() - colCount), + Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input))); } - return Iterables.concat(schemaNodePath, + return Iterables.concat(walkablePath, Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input))); } + /** + * Return List of qNames that are walkable using xPath. When getting a path from schema node it will return path + * with parents like CaseSchemaNode and ChoiceSchemaNode as well if they are parents of the node. We need to get + * rid of these in order to find the node that xPath is pointing to. Also we can not remove any node beyond the + * amount of "../" because we will not be able to find the correct schema node from schema context + * + * @param schemaNodePath list of qNames as a path to the leaf of type leafref + * @param context create schema context + * @param colCount amount of "../" in the xPath expression + * @return list of QNames as a path where we should be able to find referenced node + */ + private static List createWalkablePath(final Iterable schemaNodePath, final SchemaContext context, + final int colCount) { + final List indexToRemove = new ArrayList<>(); + List schemaNodePathRet = Lists.newArrayList(schemaNodePath); + for (int j = 0, i = schemaNodePathRet.size() - 1; i >= 0 && j != colCount; i--, j++) { + final SchemaNode nodeIn = findTargetNode(context, schemaNodePathRet); + if (nodeIn instanceof CaseSchemaNode || nodeIn instanceof ChoiceSchemaNode) { + indexToRemove.add(i); + j--; + } + schemaNodePathRet.remove(i); + } + schemaNodePathRet = Lists.newArrayList(schemaNodePath); + for (int i : indexToRemove) { + schemaNodePathRet.remove(i); + } + return schemaNodePathRet; + } + private static SchemaNode resolveDerefPath(final SchemaContext context, final Module module, final SchemaNode actualSchemaNode, final String xpath) { final int paren = xpath.indexOf(')', 6); -- 2.36.6