Bug 8084 - FilterContentValidator.getKeyValues creates invalid YII key values 69/54869/3
authormiroslav.kovac <miroslav.kovac@pantheon.tech>
Tue, 28 Mar 2017 15:51:39 +0000 (17:51 +0200)
committerMiroslav Kovac <miroslav.kovac@pantheon.tech>
Fri, 28 Apr 2017 14:07:02 +0000 (14:07 +0000)
Change-Id: Ie6c65c9cb005f9f1be85e04a8cf643e48e07bb94
Signed-off-by: miroslav.kovac <miroslav.kovac@pantheon.tech>
netconf/mdsal-netconf-connector/pom.xml
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/UniversalNamespaceContextImpl.java [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/Bug8084.java [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java
netconf/mdsal-netconf-connector/src/test/resources/filter/bug8084.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang
netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-mapping-test.yang

index e771789a83cd910900dc151f7ea944a385d17d5a..16b90775e21b54716c9d3f871bcbd59584e97ab9 100644 (file)
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-test-util</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-data-codec-xml</artifactId>
+    </dependency>
   </dependencies>
 </project>
index 459ae7c8a443e8d9daa4547658bbe646df291305..de895bfa5d2712751940fe3414d060bdeadacfe2 100644 (file)
@@ -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<QName> keyDefinition = listSchemaNode.getKeyDefinition();
-        final Map<QName, Object> map = getKeyValues(pathToList, filterContent, keyDefinition);
+
+        final Map<QName, Object> map = getKeyValues(pathToList, filterContent, listSchemaNode);
         if (!map.isEmpty()) {
             builder.nodeWithKey(tree.getName(), map);
         }
     }
 
     private Map<QName, Object> getKeyValues(final List<String> path, final XmlElement filterContent,
-                                            final List<QName> 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<QName, Object> keys = new HashMap<>();
+        final List<QName> keyDefinition = listSchemaNode.getKeyDefinition();
         for (final QName qName : keyDefinition) {
             final Optional<XmlElement> childElements = current.getOnlyChildElementOptionally(qName.getLocalName());
             if (!childElements.isPresent()) {
@@ -191,7 +203,23 @@ public class FilterContentValidator {
             }
             final Optional<String> 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 (file)
index 0000000..d092e96
--- /dev/null
@@ -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<String, String> prefix2Uri = new HashMap<>();
+    private final Map<String, String> 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 (file)
index 0000000..6a05108
--- /dev/null
@@ -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<InputStream> 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<QName, Object> 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);
+
+    }
+}
index c3656cfd4c4dcc9de769eb7285bd74290e950af9..223f5625e12b190ed460eeec43d0f25d9e97345e 100644 (file)
@@ -78,6 +78,7 @@ public class FilterContentValidatorTest {
         final List<InputStream> 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 (file)
index 0000000..482db1a
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+  ~ 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
+  -->
+
+<mainroot xmlns="urn:dummy:mod-0">
+    <multi-key-list2>
+        <id1>aaa</id1>
+        <id2>-9</id2>
+        <id3>-30000</id3>
+        <id4>-2000000000</id4>
+        <id5>-2000000000000000</id5>
+        <id6>9</id6>
+        <id7>30000</id7>
+        <id8>2000000000</id8>
+        <id9>2000000000000000</id9>
+        <id10>true</id10>
+        <id11>128.55</id11>
+        <id12>foo</id12>
+        <id13 xmlns:map-test="urn:opendaylight:mdsal:mapping:test">map-test:foo</id13>
+    </multi-key-list2>
+</mainroot>
\ No newline at end of file
index a012a53410d69a8a9131eb57e10a167cbfdc8bbb..9b62160274d6c868d757b7250720407d22a70f89 100644 (file)
@@ -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
index 50c975e0b37efccd7cab639b4c10d319ed19329d..d338ac3e07fa044bb09a9248e4fb5e123debced8 100644 (file)
@@ -5,6 +5,9 @@ module config {
 
     revision "2015-02-26";
 
+    identity foo {
+            description "dummy identity";
+    }
     container mapping-nodes {
 
         list multiple-keys {