Use keyed list as a selection node in filter 58/37058/2
authorAndrej Mak <andmak@cisco.com>
Mon, 4 Apr 2016 10:56:58 +0000 (12:56 +0200)
committerAndrej Mak <andmak@cisco.com>
Mon, 4 Apr 2016 12:01:54 +0000 (14:01 +0200)
Keyed list couldn't be used as selection node in filter
before, because filter node was parsed to normalized nodes
and yang codecs don't allow keyed list without key.
Filter content is not transformed to normalized nodes now,
only validated against schema context.

Change-Id: I5a81b268131e737d67cd3443f4f0ee4f124dd8bb
Signed-off-by: Andrej Mak <andmak@cisco.com>
15 files changed:
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/AbstractGet.java
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/NetconfMDSalMappingTest.java
netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidatorTest.java [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/expected.txt [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f1.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f2.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f3.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f4.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f5.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f6.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f7.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/filter/f8.xml [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-augment.yang [new file with mode: 0644]
netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-mod-0.yang [new file with mode: 0644]

index b4de3164e1b7abf315da4eb686ab09871a61fcca..6d220071bacd4f1f540da7ca2b2791fc6118a0f9 100644 (file)
@@ -13,11 +13,7 @@ import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
@@ -36,18 +32,11 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 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.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.slf4j.Logger;
@@ -63,10 +52,12 @@ public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
     protected static final String FILTER = "filter";
     static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
     protected final CurrentSchemaContext schemaContext;
+    private final FilterContentValidator validator;
 
     public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
         super(netconfSessionIdForReporting);
         this.schemaContext = schemaContext;
+        this.validator = new FilterContentValidator(schemaContext);
     }
 
     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
@@ -128,26 +119,6 @@ public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
         }
     }
 
-    private DataSchemaNode getSchemaNodeFromNamespace(final XmlElement element) throws DocumentedException {
-
-        try {
-            final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(element.getNamespace()), null);
-            DataSchemaNode dataSchemaNode = module.getDataChildByName(element.getName());
-            if (dataSchemaNode != null) {
-                return dataSchemaNode;
-            }
-        } catch (URISyntaxException e) {
-            LOG.debug("Error during parsing of element namespace, this should not happen since namespace of an xml " +
-                    "element is valid and if the xml was parsed then the URI should be as well");
-            throw new IllegalArgumentException("Unable to parse element namespace, this should not happen since " +
-                    "namespace of an xml element is valid and if the xml was parsed then the URI should be as well");
-        }
-        throw new DocumentedException("Unable to find node with namespace: " + element.getNamespace() + "in schema context: " + schemaContext.getCurrentContext().toString(),
-                ErrorType.application,
-                ErrorTag.unknown_namespace,
-                ErrorSeverity.error);
-    }
-
     protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
         if (!dataRoot.equals(ROOT)) {
             return (Element) transformNormalizedNode(document,
@@ -186,37 +157,7 @@ public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
         }
 
         XmlElement element = filterElement.getOnlyChildElement();
-        DataSchemaNode schemaNode = getSchemaNodeFromNamespace(element);
-
-        return getReadPointFromNode(YangInstanceIdentifier.builder().build(), filterToNormalizedNode(element, schemaNode));
-    }
-
-    private YangInstanceIdentifier getReadPointFromNode(final YangInstanceIdentifier pathArg, final NormalizedNode nNode) {
-        final YangInstanceIdentifier path = pathArg.node(nNode.getIdentifier());
-        if (nNode instanceof DataContainerNode) {
-            DataContainerNode node = (DataContainerNode) nNode;
-            if (node.getValue().size() == 1) {
-                return getReadPointFromNode(path, (NormalizedNode) Lists.newArrayList(node.getValue()).get(0));
-            }
-        }
-        return path;
-    }
-
-    private NormalizedNode filterToNormalizedNode(XmlElement element, DataSchemaNode schemaNode) throws DocumentedException {
-        DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory
-                .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext());
-
-        final NormalizedNode parsedNode;
-
-        if (schemaNode instanceof ContainerSchemaNode) {
-            parsedNode = parserFactory.getContainerNodeParser().parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
-        } else if (schemaNode instanceof ListSchemaNode) {
-            parsedNode = parserFactory.getMapNodeParser().parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
-        } else {
-            throw new DocumentedException("Schema node of the top level element is not an instance of container or list",
-                    ErrorType.application, ErrorTag.unknown_element, ErrorSeverity.error);
-        }
-        return parsedNode;
+        return validator.validate(element);
     }
 
     protected static final class GetConfigExecution {
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
new file mode 100644 (file)
index 0000000..7e7a157
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2016 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.netconf.mdsal.connector.ops.get;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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.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.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+/**
+ * Class validates filter content against schema context.
+ */
+public class FilterContentValidator {
+
+    private final CurrentSchemaContext schemaContext;
+
+    /**
+     * @param schemaContext current schema context
+     */
+    public FilterContentValidator(CurrentSchemaContext schemaContext) {
+        this.schemaContext = schemaContext;
+    }
+
+    /**
+     * 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
+     */
+    public YangInstanceIdentifier validate(XmlElement filterContent) throws DocumentedException {
+        try {
+            final URI namespace = new URI(filterContent.getNamespace());
+            final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespace, null);
+            final DataSchemaNode schema = getRootDataSchemaNode(module, namespace, filterContent.getName());
+            final FilterTree filterTree = validateNode(filterContent, schema, new FilterTree(schema.getQName(), Type.OTHER));
+            return getFilterDataRoot(filterTree, YangInstanceIdentifier.builder());
+        } catch (DocumentedException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new DocumentedException("Validation failed. Cause: " + e.getMessage(),
+                    DocumentedException.ErrorType.application,
+                    DocumentedException.ErrorTag.unknown_namespace,
+                    DocumentedException.ErrorSeverity.error);
+        }
+    }
+
+    /**
+     * Returns module's child data node of given name space and name
+     * @param module module
+     * @param nameSpace name space
+     * @param name name
+     * @return child data node schema
+     * @throws DocumentedException if child with given name is not present
+     */
+    private DataSchemaNode getRootDataSchemaNode(Module module, URI nameSpace, String name) throws DocumentedException {
+        final Collection<DataSchemaNode> childNodes = module.getChildNodes();
+        for (DataSchemaNode childNode : childNodes) {
+            final QName qName = childNode.getQName();
+            if (qName.getNamespace().equals(nameSpace) && qName.getLocalName().equals(name)) {
+                return childNode;
+            }
+        }
+        throw new DocumentedException("Unable to find node with namespace: " + nameSpace + "in schema context: " + schemaContext.getCurrentContext().toString(),
+                DocumentedException.ErrorType.application,
+                DocumentedException.ErrorTag.unknown_namespace,
+                DocumentedException.ErrorSeverity.error);
+    }
+
+    /**
+     * Recursively checks filter elements against the schema. Returns tree of nodes QNames as they appear in filter.
+     * @param element element to check
+     * @param parentNodeSchema parent node schema
+     * @param tree parent node tree
+     * @return tree
+     * @throws ValidationException if filter content is not valid
+     */
+    private FilterTree validateNode(XmlElement element, DataSchemaNode parentNodeSchema, FilterTree tree) throws ValidationException {
+        final List<XmlElement> childElements = element.getChildElements();
+        for (XmlElement childElement : childElements) {
+            try {
+                final Deque<DataSchemaNode> path = findSchemaNodeByNameAndNamespace(parentNodeSchema, childElement.getName(), new URI(childElement.getNamespace()));
+                if (path.isEmpty()) {
+                    throw new ValidationException(element, childElement);
+                }
+                FilterTree subtree = tree;
+                for (DataSchemaNode dataSchemaNode : path) {
+                        subtree = subtree.addChild(dataSchemaNode);
+                }
+                final DataSchemaNode childSchema = path.getLast();
+                validateNode(childElement, childSchema, subtree);
+            } catch (URISyntaxException | MissingNameSpaceException e) {
+                throw new RuntimeException("Wrong namespace in element + " + childElement.toString());
+            }
+        }
+        return tree;
+    }
+
+    /**
+     * 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
+     * or when it encounters list node.
+     * @param tree QName tree
+     * @param builder builder
+     * @return YangInstanceIdentifier
+     */
+    private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, YangInstanceIdentifier.InstanceIdentifierBuilder builder) {
+        builder.node(tree.getName());
+        while (tree.getChildren().size() == 1) {
+            final FilterTree child = tree.getChildren().iterator().next();
+            if (child.getType() == Type.CHOICE_CASE) {
+                tree = child;
+                continue;
+            }
+            builder.node(child.getName());
+            if (child.getType() == Type.LIST) {
+                return builder.build();
+            }
+            tree = child;
+        }
+        return builder.build();
+    }
+
+    //FIXME this method will also be in yangtools ParserUtils, use that when https://git.opendaylight.org/gerrit/#/c/37031/ will be merged
+    /**
+     * Returns stack of schema nodes via which it was necessary to pass to get schema node with specified
+     * {@code childName} and {@code namespace}
+     *
+     * @param dataSchemaNode
+     * @param childName
+     * @param namespace
+     * @return stack of schema nodes via which it was passed through. If found schema node is direct child then stack
+     *         contains only one node. If it is found under choice and case then stack should contains 2*n+1 element
+     *         (where n is number of choices through it was passed)
+     */
+    private Deque<DataSchemaNode> findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode,
+                                                                   final String childName, final URI namespace) {
+        final Deque<DataSchemaNode> result = new ArrayDeque<>();
+        final List<ChoiceSchemaNode> childChoices = new ArrayList<>();
+        DataSchemaNode potentialChildNode = null;
+        if (dataSchemaNode instanceof DataNodeContainer) {
+            for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
+                if (childNode instanceof ChoiceSchemaNode) {
+                    childChoices.add((ChoiceSchemaNode) childNode);
+                } else {
+                    final QName childQName = childNode.getQName();
+
+                    if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) {
+                        if (potentialChildNode == null ||
+                                childQName.getRevision().after(potentialChildNode.getQName().getRevision())) {
+                            potentialChildNode = childNode;
+                        }
+                    }
+                }
+            }
+        }
+        if (potentialChildNode != null) {
+            result.push(potentialChildNode);
+            return result;
+        }
+
+        // try to find data schema node in choice (looking for first match)
+        for (final ChoiceSchemaNode choiceNode : childChoices) {
+            for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) {
+                final Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
+                        namespace);
+                if (!resultFromRecursion.isEmpty()) {
+                    resultFromRecursion.push(concreteCase);
+                    resultFromRecursion.push(choiceNode);
+                    return resultFromRecursion;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Class represents tree of QNames as they are present in the filter.
+     */
+    private static class FilterTree {
+
+        private final QName name;
+        private final Type type;
+        private final Map<QName, FilterTree> children;
+
+        FilterTree(QName name, Type type) {
+            this.name = name;
+            this.type = type;
+            this.children = new HashMap<>();
+        }
+
+        FilterTree addChild(DataSchemaNode data) {
+            Type type;
+            if (data instanceof ChoiceCaseNode) {
+                type = Type.CHOICE_CASE;
+            } else if (data instanceof ListSchemaNode) {
+                type = Type.LIST;
+            } else {
+                type = Type.OTHER;
+            }
+            final QName name = data.getQName();
+            FilterTree childTree = children.get(name);
+            if (childTree == null) {
+                childTree = new FilterTree(name, type);
+            }
+            children.put(name, childTree);
+            return childTree;
+        }
+
+        Collection<FilterTree> getChildren() {
+            return children.values();
+        }
+
+        QName getName() {
+            return name;
+        }
+
+        Type getType() {
+            return type;
+        }
+    }
+
+    private enum Type {
+        LIST, CHOICE_CASE, OTHER
+    }
+
+    private static class ValidationException extends Exception {
+        public ValidationException(XmlElement parent, XmlElement child) {
+            super("Element " + child + " can't be child of " + parent);
+        }
+    }
+
+}
index 4bf2a2b2738decd223b18b53e43c5b043b0d6132..87c40a5916575f2b7d7232840484c47de2be4b1e 100644 (file)
@@ -15,6 +15,7 @@ import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 
+import com.google.common.io.ByteSource;
 import com.google.common.util.concurrent.Futures;
 import java.io.IOException;
 import java.io.InputStream;
@@ -22,11 +23,9 @@ import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
-
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Transformer;
@@ -34,7 +33,6 @@ import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
-
 import org.custommonkey.xmlunit.DetailedDiff;
 import org.custommonkey.xmlunit.Diff;
 import org.custommonkey.xmlunit.XMLUnit;
@@ -67,7 +65,6 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
@@ -84,8 +81,6 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
-import com.google.common.io.ByteSource;
-
 public class NetconfMDSalMappingTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(NetconfMDSalMappingTest.class);
@@ -107,7 +102,7 @@ public class NetconfMDSalMappingTest {
     private static final QName INNER_CHOICE_TEXT = QName.create("urn:opendaylight:mdsal:mapping:test", "2015-02-26", "text");
 
     private static final YangInstanceIdentifier AUGMENTED_CONTAINER_IN_MODULES =
-            YangInstanceIdentifier.builder().node(TOP).node(MODULES).build().node(new AugmentationIdentifier(Collections.singleton(AUGMENTED_CONTAINER)));
+            YangInstanceIdentifier.builder().node(TOP).node(MODULES).build();
 
     private static Document RPC_REPLY_OK = null;
 
@@ -493,19 +488,18 @@ public class NetconfMDSalMappingTest {
         verifyResponse(edit("messages/mapping/editConfigs/editConfig-filtering-setup.xml"), RPC_REPLY_OK);
         verifyResponse(commit(), RPC_REPLY_OK);
 
-        //TODO uncomment these tests once we can parse KeyedListNode as a selection node, currently you cannot use a KeyedList as a selection node in filter
-//        verifyFilterIdentifier("messages/mapping/filters/get-filter-alluser.xml",
-//                YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
+        verifyFilterIdentifier("messages/mapping/filters/get-filter-alluser.xml",
+                YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
         verifyFilterIdentifier("messages/mapping/filters/get-filter-company-info.xml",
                 YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
         verifyFilterIdentifier("messages/mapping/filters/get-filter-modules-and-admin.xml",
                 YangInstanceIdentifier.builder().node(TOP).build());
         verifyFilterIdentifier("messages/mapping/filters/get-filter-only-names-types.xml",
                 YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
-//        verifyFilterIdentifier("messages/mapping/filters/get-filter-specific-module-type-and-user.xml",
-//                YangInstanceIdentifier.builder().node(TOP).build());
-//        verifyFilterIdentifier("messages/mapping/filters/get-filter-superuser.xml",
-//                YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
+        verifyFilterIdentifier("messages/mapping/filters/get-filter-specific-module-type-and-user.xml",
+                YangInstanceIdentifier.builder().node(TOP).build());
+        verifyFilterIdentifier("messages/mapping/filters/get-filter-superuser.xml",
+                YangInstanceIdentifier.builder().node(TOP).node(USERS).node(USER).build());
         verifyFilterIdentifier("messages/mapping/filters/get-filter-users.xml",
                 YangInstanceIdentifier.builder().node(TOP).node(USERS).build());
 
@@ -535,10 +529,10 @@ public class NetconfMDSalMappingTest {
         //verifyResponse(edit("messages/mapping/editConfigs/editConfig-filtering-setup2.xml"), RPC_REPLY_OK);
         //verifyResponse(commit(), RPC_REPLY_OK);
 
-//        verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-choice.xml",
-//                YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).build());
-//        verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-case.xml",
-//                YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).node(INNER_CHOICE).node(INNER_CHOICE_TEXT).build());
+        verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-choice.xml",
+                YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).build());
+        verifyFilterIdentifier("messages/mapping/filters/get-filter-augmented-case-inner-case.xml",
+                YangInstanceIdentifier.builder().node(TOP).node(CHOICE_NODE).node(CHOICE_WRAPPER).node(INNER_CHOICE).node(INNER_CHOICE_TEXT).build());
 
 //        verifyResponse(getConfigWithFilter("messages/mapping/filters/get-filter-augmented-string.xml"),
 //                XmlFileLoader.xmlFileToDocument("messages/mapping/filters/response-augmented-string.xml"));
@@ -556,7 +550,7 @@ public class NetconfMDSalMappingTest {
         TestingGetConfig getConfig = new TestingGetConfig(sessionIdForReporting, currentSchemaContext, transactionProvider);
         Document request = XmlFileLoader.xmlFileToDocument(resource);
         YangInstanceIdentifier iid = getConfig.getInstanceIdentifierFromDocument(request);
-        assertTrue(iid.equals(identifier));
+        assertEquals(identifier, iid);
     }
 
     private class TestingGetConfig extends GetConfig{
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
new file mode 100644 (file)
index 0000000..bd0b59f
--- /dev/null
@@ -0,0 +1,99 @@
+package org.opendaylight.netconf.mdsal.connector.ops.get;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.InitializationError;
+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.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+@RunWith(value = Parameterized.class)
+public class FilterContentValidatorTest {
+
+    private static final int TEST_CASE_COUNT = 8;
+    private final XmlElement filterContent;
+    private final String expected;
+    private FilterContentValidator validator;
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() throws IOException, SAXException, URISyntaxException, InitializationError {
+        List<Object[]> result = new ArrayList<>();
+        final Path path = Paths.get(FilterContentValidatorTest.class.getResource("/filter/expected.txt").toURI());
+        final List<String> expected = Files.readAllLines(path);
+        if (expected.size() != TEST_CASE_COUNT) {
+            throw new InitializationError("Number of lines in results file must be same as test case count");
+        }
+        for (int i = 1; i <= TEST_CASE_COUNT; i++) {
+            final Document document = XmlUtil.readXmlToDocument(FilterContentValidatorTest.class.getResourceAsStream("/filter/f" + i + ".xml"));
+            result.add(new Object[]{document, expected.get(i-1)});
+        }
+        return result;
+    }
+
+    public FilterContentValidatorTest(Document filterContent, String expected) {
+        this.filterContent = XmlElement.fromDomDocument(filterContent);
+        this.expected = expected;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        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"));
+        SchemaContext context = parseYangSources(sources);
+        CurrentSchemaContext currentContext = mock(CurrentSchemaContext.class);
+        doReturn(context).when(currentContext).getCurrentContext();
+        validator = new FilterContentValidator(currentContext);
+    }
+
+    @Test
+    public void testValidate() throws Exception {
+        if (expected.startsWith("success")) {
+            final String expId = expected.replace("success=", "");
+            Assert.assertEquals(expId, validator.validate(filterContent).toString());
+        } else if (expected.startsWith("error")) {
+            try {
+                validator.validate(filterContent);
+                Assert.fail(XmlUtil.toString(filterContent) + " is not valid and should throw exception.");
+            } catch (Exception e) {
+                final String expectedExceptionClass = expected.replace("error=", "");
+                Assert.assertEquals(expectedExceptionClass, e.getClass().getName());
+            }
+        }
+
+    }
+
+    public static SchemaContext parseYangSources(Collection<InputStream> testFiles)
+            throws SourceException, ReactorException, FileNotFoundException {
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR
+                .newBuild();
+        for (InputStream testFile : testFiles) {
+            reactor.addSource(new YangStatementSourceImpl(testFile));
+        }
+        return reactor.buildEffective();
+    }
+}
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/expected.txt b/netconf/mdsal-netconf-connector/src/test/resources/filter/expected.txt
new file mode 100644 (file)
index 0000000..8b15a01
--- /dev/null
@@ -0,0 +1,8 @@
+error=org.opendaylight.controller.config.util.xml.DocumentedException
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/choiceList
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/maincontent
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/choiceList
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot
+success=/(urn:dummy:mod-0?revision=2016-03-01)mainroot/(urn:dummy:aug?revision=1999-08-17)augmented-leaf
+error=org.opendaylight.controller.config.util.xml.DocumentedException
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f1.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f1.xml
new file mode 100644 (file)
index 0000000..c2c5ef1
--- /dev/null
@@ -0,0 +1,4 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <maincontent></maincontent>
+    <not-in-schema/>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f2.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f2.xml
new file mode 100644 (file)
index 0000000..9dd67df
--- /dev/null
@@ -0,0 +1,8 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <maincontent/>
+    <choiceList>
+        <choice-leaf>
+
+        </choice-leaf>
+    </choiceList>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f3.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f3.xml
new file mode 100644 (file)
index 0000000..3c5b656
--- /dev/null
@@ -0,0 +1,12 @@
+<mainroot xmlns="urn:dummy:mod-0">
+        <choiceList>
+            <choice-leaf>
+                aaa
+            </choice-leaf>
+        </choiceList>
+        <choiceList>
+            <choice-leaf>
+                bbb
+            </choice-leaf>
+        </choiceList>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f4.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f4.xml
new file mode 100644 (file)
index 0000000..502d0b5
--- /dev/null
@@ -0,0 +1,3 @@
+<mainroot xmlns="urn:dummy:mod-0">
+        <maincontent/>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f5.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f5.xml
new file mode 100644 (file)
index 0000000..6e13113
--- /dev/null
@@ -0,0 +1,3 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <choiceList></choiceList>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f6.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f6.xml
new file mode 100644 (file)
index 0000000..2fe4c78
--- /dev/null
@@ -0,0 +1,4 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <maincontent/>
+    <choiceList></choiceList>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f7.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f7.xml
new file mode 100644 (file)
index 0000000..a59c407
--- /dev/null
@@ -0,0 +1,3 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <augmented-leaf xmlns="urn:dummy:aug"/>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/filter/f8.xml b/netconf/mdsal-netconf-connector/src/test/resources/filter/f8.xml
new file mode 100644 (file)
index 0000000..e3eec67
--- /dev/null
@@ -0,0 +1,6 @@
+<mainroot xmlns="urn:dummy:mod-0">
+    <maincontent>
+        <choiceList>
+        </choiceList>
+    </maincontent>
+</mainroot>
\ No newline at end of file
diff --git a/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-augment.yang b/netconf/mdsal-netconf-connector/src/test/resources/yang/filter-validator-test-augment.yang
new file mode 100644 (file)
index 0000000..0723989
--- /dev/null
@@ -0,0 +1,14 @@
+module filter-validator-test-augment {
+    namespace "urn:dummy:aug";
+    prefix "aug-0";
+    revision "1999-08-17";
+    import filter-validator-test-mod-0 {
+        prefix mod-0;
+        revision-date "2016-03-01";
+    }
+    augment "/mod-0:mainroot" {
+        leaf augmented-leaf {
+                type string;
+        }
+    }
+}
\ 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
new file mode 100644 (file)
index 0000000..bee040a
--- /dev/null
@@ -0,0 +1,31 @@
+module filter-validator-test-mod-0 {
+    namespace "urn:dummy:mod-0";
+    prefix "mod-0";
+    revision "2016-03-01";
+    container mainroot {
+        leaf maincontent {
+            mandatory true;
+            type string;
+        }
+        list choiceList {
+            key name;
+            leaf name {
+                type string;
+            }
+            choice v {
+                case a {
+                    leaf choice-leaf {
+                        type string;
+                    }
+                }
+                case b {
+                    container choice-cont {
+                        leaf content {
+                            type string;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file