Remove netconf from commons/opendaylight pom
[controller.git] / opendaylight / netconf / netconf-impl / src / main / java / org / opendaylight / controller / netconf / impl / SubtreeFilter.java
1 /*
2  * Copyright (c) 2014 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
9 package org.opendaylight.controller.netconf.impl;
10
11 import com.google.common.base.Optional;
12 import java.io.IOException;
13 import java.util.Map;
14 import org.opendaylight.controller.config.util.xml.DocumentedException;
15 import org.opendaylight.controller.config.util.xml.XmlElement;
16 import org.opendaylight.controller.config.util.xml.XmlUtil;
17 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
18 import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21 import org.w3c.dom.Attr;
22 import org.w3c.dom.Document;
23 import org.w3c.dom.Element;
24 import org.w3c.dom.Node;
25 import org.xml.sax.SAXException;
26
27 /**
28  * See <a href="http://tools.ietf.org/html/rfc6241#section-6">rfc6241</a> for details.
29  */
30 public class SubtreeFilter {
31     private static final Logger LOG = LoggerFactory.getLogger(SubtreeFilter.class);
32
33     static Document applySubtreeFilter(Document requestDocument, Document rpcReply) throws DocumentedException {
34         OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(requestDocument);
35         if (XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0.equals(operationNameAndNamespace.getNamespace()) &&
36                 XmlNetconfConstants.GET.equals(operationNameAndNamespace.getOperationName()) ||
37                 XmlNetconfConstants.GET_CONFIG.equals(operationNameAndNamespace.getOperationName())) {
38             // process subtree filtering here, in case registered netconf operations do
39             // not implement filtering.
40             Optional<XmlElement> maybeFilter = operationNameAndNamespace.getOperationElement().getOnlyChildElementOptionally(
41                     XmlNetconfConstants.FILTER, XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
42             if (!maybeFilter.isPresent()) {
43                 return rpcReply;
44             }
45
46             // FIXME: rpcReply document must be reread otherwise some nodes do not inherit namespaces. (services/service)
47             try {
48                 rpcReply = XmlUtil.readXmlToDocument(XmlUtil.toString(rpcReply, true));
49             } catch (SAXException | IOException e) {
50                 LOG.error("Cannot transform document", e);
51                 throw new DocumentedException("Cannot transform document" + e);
52             }
53             XmlElement filter = maybeFilter.get();
54             if ("subtree".equals(filter.getAttribute("type"))||
55                     "subtree".equals(filter.getAttribute("type", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0))) {
56
57                 // do
58                 return filtered(maybeFilter.get(), rpcReply);
59             }
60         }
61
62         return rpcReply; // return identical document
63     }
64
65     private static Document filtered(XmlElement filter, Document originalReplyDocument) throws DocumentedException {
66         Document result = XmlUtil.newDocument();
67         // even if filter is empty, copy /rpc/data
68         Element rpcReply = originalReplyDocument.getDocumentElement();
69         Node rpcReplyDst = result.importNode(rpcReply, false);
70         result.appendChild(rpcReplyDst);
71         XmlElement dataSrc = XmlElement.fromDomElement(rpcReply).getOnlyChildElement("data", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
72         Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
73         rpcReplyDst.appendChild(dataDst);
74         addSubtree(filter, dataSrc, XmlElement.fromDomElement(dataDst));
75
76         return result;
77     }
78
79     private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) throws DocumentedException {
80         for (XmlElement srcChild : src.getChildElements()) {
81             for (XmlElement filterChild : filter.getChildElements()) {
82                 addSubtree2(filterChild, srcChild, dst);
83             }
84         }
85     }
86
87     private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) throws DocumentedException {
88         Document document = dstParent.getDomElement().getOwnerDocument();
89         MatchingResult matches = matches(src, filter);
90         if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) {
91             // copy srcChild to dst
92             boolean filterHasChildren = filter.getChildElements().isEmpty() == false;
93             // copy to depth if this is leaf of filter tree
94             Element copied = (Element) document.importNode(src.getDomElement(), filterHasChildren == false);
95             boolean shouldAppend = filterHasChildren == false;
96             if (filterHasChildren) { // this implies TAG_MATCH
97                 // do the same recursively
98                 int numberOfTextMatchingChildren = 0;
99                 for (XmlElement srcChild : src.getChildElements()) {
100                     for (XmlElement filterChild : filter.getChildElements()) {
101                         MatchingResult childMatch = addSubtree2(filterChild, srcChild, XmlElement.fromDomElement(copied));
102                         if (childMatch == MatchingResult.CONTENT_MISMATCH) {
103                             return MatchingResult.NO_MATCH;
104                         }
105                         if (childMatch == MatchingResult.CONTENT_MATCH) {
106                             numberOfTextMatchingChildren++;
107                         }
108                         shouldAppend |= childMatch != MatchingResult.NO_MATCH;
109                     }
110                 }
111                 // if only text matching child filters are specified..
112                 if (numberOfTextMatchingChildren == filter.getChildElements().size()) {
113                     // force all children to be added (to depth). This is done by copying parent node to depth.
114                     // implies shouldAppend == true
115                     copied = (Element) document.importNode(src.getDomElement(), true);
116                 }
117             }
118             if (shouldAppend) {
119                 dstParent.getDomElement().appendChild(copied);
120             }
121         }
122         return matches;
123     }
124
125     /**
126      * Shallow compare src node to filter: tag name and namespace must match.
127      * If filter node has no children and has text content, it also must match.
128      */
129     private static MatchingResult matches(XmlElement src, XmlElement filter) throws DocumentedException {
130         boolean tagMatch = src.getName().equals(filter.getName()) &&
131                 src.getNamespaceOptionally().equals(filter.getNamespaceOptionally());
132         MatchingResult result = null;
133         if (tagMatch) {
134             // match text content
135             Optional<String> maybeText = filter.getOnlyTextContentOptionally();
136             if (maybeText.isPresent()) {
137                 if (maybeText.equals(src.getOnlyTextContentOptionally()) || prefixedContentMatches(filter, src)) {
138                     result = MatchingResult.CONTENT_MATCH;
139                 } else {
140                     result = MatchingResult.CONTENT_MISMATCH;
141                 }
142             }
143             // match attributes, combination of content and tag is not supported
144             if (result == null) {
145                 for (Attr attr : filter.getAttributes().values()) {
146                     // ignore namespace declarations
147                     if (XmlUtil.XMLNS_URI.equals(attr.getNamespaceURI()) == false ) {
148                         // find attr with matching localName(),  namespaceURI(),  == value() in src
149                         String found = src.getAttribute(attr.getLocalName(), attr.getNamespaceURI());
150                         if (attr.getValue().equals(found) && result != MatchingResult.NO_MATCH) {
151                             result = MatchingResult.TAG_MATCH;
152                         } else {
153                             result = MatchingResult.NO_MATCH;
154                         }
155                     }
156                 }
157             }
158             if (result == null) {
159                 result = MatchingResult.TAG_MATCH;
160             }
161         }
162         if (result == null) {
163             result = MatchingResult.NO_MATCH;
164         }
165         LOG.debug("Matching {} to {} resulted in {}", src, filter, result);
166         return result;
167     }
168
169     private static boolean prefixedContentMatches(final XmlElement filter, final XmlElement src) throws DocumentedException {
170         final Map.Entry<String, String> prefixToNamespaceOfFilter;
171         final Map.Entry<String, String> prefixToNamespaceOfSrc;
172         try {
173             prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent();
174             prefixToNamespaceOfSrc = src.findNamespaceOfTextContent();
175         } catch (IllegalArgumentException e) {
176             //if we can't find namespace of prefix - it's not a prefix, so it doesn't match
177             return false;
178         }
179
180         final String prefix = prefixToNamespaceOfFilter.getKey();
181         // If this is not a prefixed content, we do not need to continue since content do not match
182         if (prefix.equals(XmlElement.DEFAULT_NAMESPACE_PREFIX)) {
183             return false;
184         }
185         // Namespace mismatch
186         if (!prefixToNamespaceOfFilter.getValue().equals(prefixToNamespaceOfSrc.getValue())) {
187             return false;
188         }
189
190         final String unprefixedFilterContent = filter.getTextContent().substring(prefixToNamespaceOfFilter.getKey().length() + 1);
191         final String unprefixedSrcContnet = src.getTextContent().substring(prefixToNamespaceOfSrc.getKey().length() + 1);
192         // Finally compare unprefixed content
193         return unprefixedFilterContent.equals(unprefixedSrcContnet);
194     }
195
196     enum MatchingResult {
197         NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH
198     }
199 }