Merge "Use yangtools ParserStreamUtils.findSchemaNodeByNameAndNamespace() method"
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / get / FilterContentValidator.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.mdsal.connector.ops.get;
9
10 import static org.opendaylight.yangtools.yang.data.util.ParserStreamUtils.findSchemaNodeByNameAndNamespace;
11
12 import java.net.URI;
13 import java.net.URISyntaxException;
14 import java.util.Collection;
15 import java.util.Deque;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import org.opendaylight.controller.config.util.xml.DocumentedException;
20 import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
21 import org.opendaylight.controller.config.util.xml.XmlElement;
22 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
26 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.Module;
29
30 /**
31  * Class validates filter content against schema context.
32  */
33 public class FilterContentValidator {
34
35     private final CurrentSchemaContext schemaContext;
36
37     /**
38      * @param schemaContext current schema context
39      */
40     public FilterContentValidator(final CurrentSchemaContext schemaContext) {
41         this.schemaContext = schemaContext;
42     }
43
44     /**
45      * Validates filter content against this validator schema context. If the filter is valid, method returns {@link YangInstanceIdentifier}
46      * of node which can be used as root for data selection.
47      * @param filterContent filter content
48      * @return YangInstanceIdentifier
49      * @throws DocumentedException if filter content is not valid
50      */
51     public YangInstanceIdentifier validate(final XmlElement filterContent) throws DocumentedException {
52         try {
53             final URI namespace = new URI(filterContent.getNamespace());
54             final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespace, null);
55             final DataSchemaNode schema = getRootDataSchemaNode(module, namespace, filterContent.getName());
56             final FilterTree filterTree = validateNode(filterContent, schema, new FilterTree(schema.getQName(), Type.OTHER));
57             return getFilterDataRoot(filterTree, YangInstanceIdentifier.builder());
58         } catch (final DocumentedException e) {
59             throw e;
60         } catch (final Exception e) {
61             throw new DocumentedException("Validation failed. Cause: " + e.getMessage(),
62                     DocumentedException.ErrorType.APPLICATION,
63                     DocumentedException.ErrorTag.UNKNOWN_NAMESPACE,
64                     DocumentedException.ErrorSeverity.ERROR);
65         }
66     }
67
68     /**
69      * Returns module's child data node of given name space and name
70      * @param module module
71      * @param nameSpace name space
72      * @param name name
73      * @return child data node schema
74      * @throws DocumentedException if child with given name is not present
75      */
76     private DataSchemaNode getRootDataSchemaNode(final Module module, final URI nameSpace, final String name) throws DocumentedException {
77         final Collection<DataSchemaNode> childNodes = module.getChildNodes();
78         for (final DataSchemaNode childNode : childNodes) {
79             final QName qName = childNode.getQName();
80             if (qName.getNamespace().equals(nameSpace) && qName.getLocalName().equals(name)) {
81                 return childNode;
82             }
83         }
84         throw new DocumentedException("Unable to find node with namespace: " + nameSpace + "in schema context: " + schemaContext.getCurrentContext().toString(),
85                 DocumentedException.ErrorType.APPLICATION,
86                 DocumentedException.ErrorTag.UNKNOWN_NAMESPACE,
87                 DocumentedException.ErrorSeverity.ERROR);
88     }
89
90     /**
91      * Recursively checks filter elements against the schema. Returns tree of nodes QNames as they appear in filter.
92      * @param element element to check
93      * @param parentNodeSchema parent node schema
94      * @param tree parent node tree
95      * @return tree
96      * @throws ValidationException if filter content is not valid
97      */
98     private FilterTree validateNode(final XmlElement element, final DataSchemaNode parentNodeSchema, final FilterTree tree) throws ValidationException {
99         final List<XmlElement> childElements = element.getChildElements();
100         for (final XmlElement childElement : childElements) {
101             try {
102                 final Deque<DataSchemaNode> path = findSchemaNodeByNameAndNamespace(parentNodeSchema, childElement.getName(), new URI(childElement.getNamespace()));
103                 if (path.isEmpty()) {
104                     throw new ValidationException(element, childElement);
105                 }
106                 FilterTree subtree = tree;
107                 for (final DataSchemaNode dataSchemaNode : path) {
108                         subtree = subtree.addChild(dataSchemaNode);
109                 }
110                 final DataSchemaNode childSchema = path.getLast();
111                 validateNode(childElement, childSchema, subtree);
112             } catch (URISyntaxException | MissingNameSpaceException e) {
113                 throw new RuntimeException("Wrong namespace in element + " + childElement.toString());
114             }
115         }
116         return tree;
117     }
118
119     /**
120      * Searches for YangInstanceIdentifier of node, which can be used as root for data selection.
121      * It goes as deep in tree as possible. Method stops traversing, when there are multiple child elements
122      * or when it encounters list node.
123      * @param tree QName tree
124      * @param builder builder
125      * @return YangInstanceIdentifier
126      */
127     private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, final YangInstanceIdentifier.InstanceIdentifierBuilder builder) {
128         builder.node(tree.getName());
129         while (tree.getChildren().size() == 1) {
130             final FilterTree child = tree.getChildren().iterator().next();
131             if (child.getType() == Type.CHOICE_CASE) {
132                 tree = child;
133                 continue;
134             }
135             builder.node(child.getName());
136             if (child.getType() == Type.LIST) {
137                 return builder.build();
138             }
139             tree = child;
140         }
141         return builder.build();
142     }
143
144     /**
145      * Class represents tree of QNames as they are present in the filter.
146      */
147     private static class FilterTree {
148
149         private final QName name;
150         private final Type type;
151         private final Map<QName, FilterTree> children;
152
153         FilterTree(final QName name, final Type type) {
154             this.name = name;
155             this.type = type;
156             this.children = new HashMap<>();
157         }
158
159         FilterTree addChild(final DataSchemaNode data) {
160             final Type type;
161             if (data instanceof ChoiceCaseNode) {
162                 type = Type.CHOICE_CASE;
163             } else if (data instanceof ListSchemaNode) {
164                 type = Type.LIST;
165             } else {
166                 type = Type.OTHER;
167             }
168             final QName name = data.getQName();
169             FilterTree childTree = children.get(name);
170             if (childTree == null) {
171                 childTree = new FilterTree(name, type);
172             }
173             children.put(name, childTree);
174             return childTree;
175         }
176
177         Collection<FilterTree> getChildren() {
178             return children.values();
179         }
180
181         QName getName() {
182             return name;
183         }
184
185         Type getType() {
186             return type;
187         }
188     }
189
190     private enum Type {
191         LIST, CHOICE_CASE, OTHER
192     }
193
194     private static class ValidationException extends Exception {
195         public ValidationException(final XmlElement parent, final XmlElement child) {
196             super("Element " + child + " can't be child of " + parent);
197         }
198     }
199
200 }