From: miroslav.kovac Date: Tue, 28 Mar 2017 15:51:39 +0000 (+0200) Subject: Bug 8084 - FilterContentValidator.getKeyValues creates invalid YII key values X-Git-Tag: release/carbon~9 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=4a19fdc05da79aa174fa67f7d21e62df4d17b1d4;p=netconf.git Bug 8084 - FilterContentValidator.getKeyValues creates invalid YII key values Change-Id: Ie6c65c9cb005f9f1be85e04a8cf643e48e07bb94 Signed-off-by: miroslav.kovac --- diff --git a/netconf/mdsal-netconf-connector/pom.xml b/netconf/mdsal-netconf-connector/pom.xml index e771789a83..16b90775e2 100644 --- a/netconf/mdsal-netconf-connector/pom.xml +++ b/netconf/mdsal-netconf-connector/pom.xml @@ -117,5 +117,9 @@ org.opendaylight.yangtools yang-test-util + + org.opendaylight.yangtools + yang-data-codec-xml + diff --git a/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java index 459ae7c8a4..de895bfa5d 100644 --- a/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java +++ b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java @@ -21,18 +21,25 @@ import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; import org.opendaylight.controller.config.util.xml.DocumentedException; import org.opendaylight.controller.config.util.xml.MissingNameSpaceException; import org.opendaylight.controller.config.util.xml.XmlElement; import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlCodecFactory; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.data.util.codec.TypeAwareCodec; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; /** * Class validates filter content against schema context. @@ -52,6 +59,7 @@ public class FilterContentValidator { /** * Validates filter content against this validator schema context. If the filter is valid, * method returns {@link YangInstanceIdentifier} of node which can be used as root for data selection. + * * @param filterContent filter content * @return YangInstanceIdentifier * @throws DocumentedException if filter content is not valid @@ -77,9 +85,10 @@ public class FilterContentValidator { /** * Returns module's child data node of given name space and name - * @param module module + * + * @param module module * @param nameSpace name space - * @param name name + * @param name name * @return child data node schema * @throws DocumentedException if child with given name is not present */ @@ -101,9 +110,10 @@ public class FilterContentValidator { /** * Recursively checks filter elements against the schema. Returns tree of nodes QNames as they appear in filter. - * @param element element to check + * + * @param element element to check * @param parentNodeSchema parent node schema - * @param tree parent node tree + * @param tree parent node tree * @return tree * @throws ValidationException if filter content is not valid */ @@ -134,9 +144,10 @@ public class FilterContentValidator { * Searches for YangInstanceIdentifier of node, which can be used as root for data selection. * It goes as deep in tree as possible. Method stops traversing, when there are multiple child elements. If element * represents list and child elements are key values, then it builds YangInstanceIdentifier of list entry. - * @param tree QName tree + * + * @param tree QName tree * @param filterContent filter element - * @param builder builder @return YangInstanceIdentifier + * @param builder builder @return YangInstanceIdentifier */ private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, final XmlElement filterContent, final InstanceIdentifierBuilder builder) { @@ -164,15 +175,15 @@ public class FilterContentValidator { final InstanceIdentifierBuilder builder) { Preconditions.checkArgument(tree.getSchemaNode() instanceof ListSchemaNode); final ListSchemaNode listSchemaNode = (ListSchemaNode) tree.getSchemaNode(); - final List keyDefinition = listSchemaNode.getKeyDefinition(); - final Map map = getKeyValues(pathToList, filterContent, keyDefinition); + + final Map map = getKeyValues(pathToList, filterContent, listSchemaNode); if (!map.isEmpty()) { builder.nodeWithKey(tree.getName(), map); } } private Map getKeyValues(final List path, final XmlElement filterContent, - final List keyDefinition) { + final ListSchemaNode listSchemaNode) { XmlElement current = filterContent; //find list element for (final String pathElement : path) { @@ -184,6 +195,7 @@ public class FilterContentValidator { current = childElements.get(0); } final Map keys = new HashMap<>(); + final List keyDefinition = listSchemaNode.getKeyDefinition(); for (final QName qName : keyDefinition) { final Optional childElements = current.getOnlyChildElementOptionally(qName.getLocalName()); if (!childElements.isPresent()) { @@ -191,7 +203,23 @@ public class FilterContentValidator { } final Optional keyValue = childElements.get().getOnlyTextContentOptionally(); if (keyValue.isPresent()) { - keys.put(qName, keyValue.get()); + final LeafSchemaNode listKey = (LeafSchemaNode) listSchemaNode.getDataChildByName(qName); + if (listKey instanceof IdentityrefTypeDefinition) { + keys.put(qName, keyValue.get()); + } else { + if (listKey.getType() instanceof IdentityrefTypeDefinition) { + final Document document = filterContent.getDomElement().getOwnerDocument(); + final NamespaceContext nsContext = new UniversalNamespaceContextImpl(document, false); + final XmlCodecFactory xmlCodecFactory = XmlCodecFactory.create(schemaContext.getCurrentContext()); + final TypeAwareCodec identityrefTypeCodec = xmlCodecFactory.codecFor(listKey); + final QName deserializedKey = (QName) identityrefTypeCodec.parseValue(nsContext, keyValue.get()); + keys.put(qName, deserializedKey); + } else { + final Object deserializedKey = TypeDefinitionAwareCodec.from(listKey.getType()) + .deserialize(keyValue.get()); + keys.put(qName, deserializedKey); + } + } } } return keys; diff --git a/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/UniversalNamespaceContextImpl.java b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/UniversalNamespaceContextImpl.java new file mode 100644 index 0000000000..d092e96997 --- /dev/null +++ b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/UniversalNamespaceContextImpl.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017 Pantheon Technologies 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.netconf.mdsal.connector.ops.get; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class UniversalNamespaceContextImpl implements NamespaceContext { + private static final String DEFAULT_NS = "DEFAULT"; + private final Map prefix2Uri = new HashMap<>(); + private final Map uri2Prefix = new HashMap<>(); + + /** + * This constructor parses the document and stores all namespaces it can + * find. If toplevelOnly is true, only namespaces in the root are used. + * + * @param document source document + * @param toplevelOnly restriction of the search to enhance performance + */ + public UniversalNamespaceContextImpl(final Document document, final boolean toplevelOnly) { + readNode(document.getFirstChild(), toplevelOnly); + } + + /** + * A single node is read, the namespace attributes are extracted and stored. + * + * @param node to examine + * @param attributesOnly, if true no recursion happens + */ + private void readNode(final Node node, final boolean attributesOnly) { + final NamedNodeMap attributes = node.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + final Node attribute = attributes.item(i); + storeAttr((Attr) attribute); + } + + if (!attributesOnly) { + final NodeList chields = node.getChildNodes(); + for (int i = 0; i < chields.getLength(); i++) { + final Node chield = chields.item(i); + if (chield.getNodeType() == Node.ELEMENT_NODE) + readNode(chield, false); + } + } + } + + /** + * This method looks at an attribute and stores it, if it is a namespace + * attribute. + * + * @param attribute to examine + */ + private void storeAttr(final Attr attribute) { + // examine the attributes in namespace xmlns + if (attribute.getNamespaceURI() != null + && attribute.getNamespaceURI().equals( + XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { + // Default namespace xmlns="uri goes here" + if (attribute.getNodeName().equals(XMLConstants.XMLNS_ATTRIBUTE)) { + putInCache(DEFAULT_NS, attribute.getNodeValue()); + } else { + // The defined prefixes are stored here + putInCache(attribute.getLocalName(), attribute.getNodeValue()); + } + } + + } + + private void putInCache(final String prefix, final String uri) { + prefix2Uri.put(prefix, uri); + uri2Prefix.put(uri, prefix); + } + + /** + * This method is called by XPath. It returns the default namespace, if the + * prefix is null or "". + * + * @param prefix to search for + * @return uri + */ + public String getNamespaceURI(final String prefix) { + if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) { + return prefix2Uri.get(DEFAULT_NS); + } else { + return prefix2Uri.get(prefix); + } + } + + /** + * This method is not needed in this context, but can be implemented in a + * similar way. + */ + public String getPrefix(final String namespaceURI) { + return uri2Prefix.get(namespaceURI); + } + + public Iterator getPrefixes(final String namespaceURI) { + // Not implemented + return null; + } + +} \ No newline at end of file diff --git a/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/Bug8084.java b/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/Bug8084.java new file mode 100644 index 0000000000..6a05108a29 --- /dev/null +++ b/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/Bug8084.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017 Pantheon Technologies 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.netconf.mdsal.connector.ops.get; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.opendaylight.controller.config.util.xml.XmlElement; +import org.opendaylight.controller.config.util.xml.XmlUtil; +import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; +import org.w3c.dom.Document; + +public class Bug8084 { + + private static final QName base = QName.create("urn:dummy:mod-0", "2016-03-01", "mainroot"); + + @Test + public void testValidateTypes() throws Exception { + final List sources = new ArrayList<>(); + sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-mod-0.yang")); + sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-augment.yang")); + sources.add(getClass().getResourceAsStream("/yang/mdsal-netconf-mapping-test.yang")); + final SchemaContext context = YangParserTestUtils.parseYangStreams(sources); + final CurrentSchemaContext currentContext = mock(CurrentSchemaContext.class); + doReturn(context).when(currentContext).getCurrentContext(); + final FilterContentValidator validator = new FilterContentValidator(currentContext); + + final Document document = XmlUtil.readXmlToDocument(FilterContentValidatorTest.class + .getResourceAsStream("/filter/bug8084.xml")); + + final XmlElement xmlElement = XmlElement.fromDomDocument(document); + final YangInstanceIdentifier actual = validator.validate(xmlElement); + + final Map inputs = new HashMap<>(); + inputs.put(QName.create(base, "id1"), "aaa"); + inputs.put(QName.create(base, "id2"), Byte.valueOf("-9")); + inputs.put(QName.create(base, "id3"), Short.valueOf("-30000")); + inputs.put(QName.create(base, "id4"), Integer.valueOf("-2000000000")); + inputs.put(QName.create(base, "id5"), Long.valueOf("-2000000000000000")); + inputs.put(QName.create(base, "id6"), Short.valueOf("9")); + inputs.put(QName.create(base, "id7"), Integer.valueOf("30000")); + inputs.put(QName.create(base, "id8"), Long.valueOf("2000000000")); + inputs.put(QName.create(base, "id9"), BigInteger.valueOf(Long.valueOf("2000000000000000"))); + inputs.put(QName.create(base, "id10"), true); + inputs.put(QName.create(base, "id11"), BigDecimal.valueOf(128.55)); + inputs.put(QName.create(base, "id12"), QName.create(base, "foo")); + inputs.put(QName.create(base, "id13"), QName.create("urn:opendaylight:mdsal:mapping:test", "2015-02-26", "foo")); + final QName idActual = (QName) ((YangInstanceIdentifier.NodeIdentifierWithPredicates) actual.getLastPathArgument()). + getKeyValues().get(QName.create(base, "id12")); + + + final YangInstanceIdentifier expected = YangInstanceIdentifier.builder() + .node(base) + .node(QName.create(base, "multi-key-list2")) + .nodeWithKey(QName.create(base, "multi-key-list2"), inputs) + .build(); + final QName idExpected = (QName) ((YangInstanceIdentifier.NodeIdentifierWithPredicates) expected.getLastPathArgument()). + getKeyValues().get(QName.create(base, "id12")); + assertEquals(idExpected, idActual); + assertEquals(expected, actual); + + } +} diff --git a/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java b/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java index c3656cfd4c..223f5625e1 100644 --- a/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java +++ b/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java @@ -78,6 +78,7 @@ public class FilterContentValidatorTest { final List sources = new ArrayList<>(); sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-mod-0.yang")); sources.add(getClass().getResourceAsStream("/yang/filter-validator-test-augment.yang")); + sources.add(getClass().getResourceAsStream("/yang/mdsal-netconf-mapping-test.yang")); final SchemaContext context = YangParserTestUtils.parseYangStreams(sources); final CurrentSchemaContext currentContext = mock(CurrentSchemaContext.class); doReturn(context).when(currentContext).getCurrentContext(); diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/bug8084.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/bug8084.xml new file mode 100644 index 0000000000..482db1a81f --- /dev/null +++ b/netconf/mdsal-netconf-connector/src/test/resources/filter/bug8084.xml @@ -0,0 +1,25 @@ + + + + + aaa + -9 + -30000 + -2000000000 + -2000000000000000 + 9 + 30000 + 2000000000 + 2000000000000000 + true + 128.55 + foo + map-test:foo + + \ No newline at end of file diff --git a/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang b/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang index a012a53410..9b62160274 100644 --- a/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang +++ b/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang @@ -2,6 +2,16 @@ module filter-validator-test-mod-0 { namespace "urn:dummy:mod-0"; prefix "mod-0"; revision "2016-03-01"; + + import config { + prefix map-test; + revision-date "2015-02-26"; + } + + identity foo { + description "dummy identity"; + } + container mainroot { leaf maincontent { mandatory true; @@ -48,5 +58,54 @@ module filter-validator-test-mod-0 { type string; } } + + list multi-key-list2 { + key "id1 id2 id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13"; + leaf id1 { + type string; + } + leaf id2 { + type int8; + } + leaf id3 { + type int16; + } + leaf id4 { + type int32; + } + leaf id5 { + type int64; + } + leaf id6 { + type uint8; + } + leaf id7 { + type uint16; + } + leaf id8 { + type uint32; + } + leaf id9 { + type uint64; + } + leaf id10 { + type boolean; + } + leaf id11 { + type decimal64{ + fraction-digits 2; + } + } + leaf id12 { + type identityref { + base "foo"; + } + } + leaf id13 { + type identityref { + base "map-test:foo"; + } + } + } } } \ No newline at end of file diff --git a/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-mapping-test.yang b/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-mapping-test.yang index 50c975e0b3..d338ac3e07 100644 --- a/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-mapping-test.yang +++ b/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-mapping-test.yang @@ -5,6 +5,9 @@ module config { revision "2015-02-26"; + identity foo { + description "dummy identity"; + } container mapping-nodes { list multiple-keys {