Merge "Use keyed list as a selection node in filter"
authorTomas Cere <tcere@cisco.com>
Tue, 12 Apr 2016 12:22:16 +0000 (12:22 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 12 Apr 2016 12:22:16 +0000 (12:22 +0000)
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