2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.netconf.impl;
11 import com.google.common.base.Optional;
12 import java.io.IOException;
14 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
15 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
16 import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace;
17 import org.opendaylight.controller.netconf.util.xml.XmlElement;
18 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
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;
28 * See <a href="http://tools.ietf.org/html/rfc6241#section-6">rfc6241</a> for details.
30 public class SubtreeFilter {
31 private static final Logger logger = LoggerFactory.getLogger(SubtreeFilter.class);
33 static Document applySubtreeFilter(Document requestDocument, Document rpcReply) throws NetconfDocumentedException {
34 // FIXME: rpcReply document must be reread otherwise some nodes do not inherit namespaces. (services/service)
36 rpcReply = XmlUtil.readXmlToDocument(XmlUtil.toString(rpcReply, true));
37 } catch (SAXException | IOException e) {
38 logger.error("Cannot transform document", e);
39 throw new NetconfDocumentedException("Cannot transform document");
42 OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(requestDocument);
43 if (XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0.equals(operationNameAndNamespace.getNamespace()) &&
44 XmlNetconfConstants.GET.equals(operationNameAndNamespace.getOperationName()) ||
45 XmlNetconfConstants.GET_CONFIG.equals(operationNameAndNamespace.getOperationName())) {
46 // process subtree filtering here, in case registered netconf operations do
47 // not implement filtering.
48 Optional<XmlElement> maybeFilter = operationNameAndNamespace.getOperationElement().getOnlyChildElementOptionally(
49 XmlNetconfConstants.FILTER, XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
50 if (maybeFilter.isPresent() && (
51 "subtree".equals(maybeFilter.get().getAttribute("type"))||
52 "subtree".equals(maybeFilter.get().getAttribute("type", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0))
57 return filtered(maybeFilter.get(), rpcReply);
60 return rpcReply; // return identical document
63 private static Document filtered(XmlElement filter, Document originalReplyDocument) throws NetconfDocumentedException {
64 Document result = XmlUtil.newDocument();
65 // even if filter is empty, copy /rpc/data
66 Element rpcReply = originalReplyDocument.getDocumentElement();
67 Node rpcReplyDst = result.importNode(rpcReply, false);
68 result.appendChild(rpcReplyDst);
69 XmlElement dataSrc = XmlElement.fromDomElement(rpcReply).getOnlyChildElement("data", XmlNetconfConstants.RFC4741_TARGET_NAMESPACE);
70 Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
71 rpcReplyDst.appendChild(dataDst);
72 addSubtree(filter, dataSrc, XmlElement.fromDomElement(dataDst));
77 private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) throws NetconfDocumentedException {
78 for (XmlElement srcChild : src.getChildElements()) {
79 for (XmlElement filterChild : filter.getChildElements()) {
80 addSubtree2(filterChild, srcChild, dst);
85 private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) throws NetconfDocumentedException {
86 Document document = dstParent.getDomElement().getOwnerDocument();
87 MatchingResult matches = matches(src, filter);
88 if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) {
89 // copy srcChild to dst
90 boolean filterHasChildren = filter.getChildElements().isEmpty() == false;
91 // copy to depth if this is leaf of filter tree
92 Element copied = (Element) document.importNode(src.getDomElement(), filterHasChildren == false);
93 boolean shouldAppend = filterHasChildren == false;
94 if (filterHasChildren) { // this implies TAG_MATCH
95 // do the same recursively
96 int numberOfTextMatchingChildren = 0;
97 for (XmlElement srcChild : src.getChildElements()) {
98 for (XmlElement filterChild : filter.getChildElements()) {
99 MatchingResult childMatch = addSubtree2(filterChild, srcChild, XmlElement.fromDomElement(copied));
100 if (childMatch == MatchingResult.CONTENT_MISMATCH) {
101 return MatchingResult.NO_MATCH;
103 if (childMatch == MatchingResult.CONTENT_MATCH) {
104 numberOfTextMatchingChildren++;
106 shouldAppend |= childMatch != MatchingResult.NO_MATCH;
109 // if only text matching child filters are specified..
110 if (numberOfTextMatchingChildren == filter.getChildElements().size()) {
111 // force all children to be added (to depth). This is done by copying parent node to depth.
112 // implies shouldAppend == true
113 copied = (Element) document.importNode(src.getDomElement(), true);
117 dstParent.getDomElement().appendChild(copied);
124 * Shallow compare src node to filter: tag name and namespace must match.
125 * If filter node has no children and has text content, it also must match.
127 private static MatchingResult matches(XmlElement src, XmlElement filter) throws NetconfDocumentedException {
128 boolean tagMatch = src.getName().equals(filter.getName()) &&
129 src.getNamespaceOptionally().equals(filter.getNamespaceOptionally());
130 MatchingResult result = null;
132 // match text content
133 Optional<String> maybeText = filter.getOnlyTextContentOptionally();
134 if (maybeText.isPresent()) {
135 if (maybeText.equals(src.getOnlyTextContentOptionally()) || prefixedContentMatches(filter, src)) {
136 result = MatchingResult.CONTENT_MATCH;
138 result = MatchingResult.CONTENT_MISMATCH;
141 // match attributes, combination of content and tag is not supported
142 if (result == null) {
143 for (Attr attr : filter.getAttributes().values()) {
144 // ignore namespace declarations
145 if (XmlUtil.XMLNS_URI.equals(attr.getNamespaceURI()) == false ) {
146 // find attr with matching localName(), namespaceURI(), == value() in src
147 String found = src.getAttribute(attr.getLocalName(), attr.getNamespaceURI());
148 if (attr.getValue().equals(found) && result != MatchingResult.NO_MATCH) {
149 result = MatchingResult.TAG_MATCH;
151 result = MatchingResult.NO_MATCH;
156 if (result == null) {
157 result = MatchingResult.TAG_MATCH;
160 if (result == null) {
161 result = MatchingResult.NO_MATCH;
163 logger.debug("Matching {} to {} resulted in {}", src, filter, result);
167 private static boolean prefixedContentMatches(final XmlElement filter, final XmlElement src) throws NetconfDocumentedException {
168 final Map.Entry<String, String> prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent();
169 final Map.Entry<String, String> prefixToNamespaceOfSrc = src.findNamespaceOfTextContent();
171 final String prefix = prefixToNamespaceOfFilter.getKey();
172 // If this is not a prefixed content, we do not need to continue since content do not match
173 if(prefix.equals(XmlElement.DEFAULT_NAMESPACE_PREFIX)) {
176 // Namespace mismatch
177 if(!prefixToNamespaceOfFilter.getValue().equals(prefixToNamespaceOfSrc.getValue())) {
181 final String unprefixedFilterContent = filter.getTextContent().substring(prefix.length());
182 final String unprefixedSrcCOntnet = src.getTextContent().substring(prefix.length());
183 // Finally compare unprefixed content
184 return unprefixedFilterContent.equals(unprefixedSrcCOntnet);
187 enum MatchingResult {
188 NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH