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.netconf.util.messages;
11 import com.google.common.base.Optional;
12 import java.io.IOException;
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.netconf.api.xml.XmlNetconfConstants;
18 import org.opendaylight.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;
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 LOG = LoggerFactory.getLogger(SubtreeFilter.class);
33 public static Document applyRpcSubtreeFilter(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()) {
46 rpcReply = reReadDocument(rpcReply);
47 XmlElement filter = maybeFilter.get();
48 if (isSupported(filter)) {
51 return filtered(maybeFilter.get(), rpcReply);
55 return rpcReply; // return identical document
59 * Filters notification content. If filter type isn't of type "subtree", returns unchanged notification content.
60 * If no match is found, absent is returned.
61 * @param filter filter
62 * @param notification notification
63 * @return document containing filtered notification content
64 * @throws DocumentedException
66 public static Optional<Document> applySubtreeNotificationFilter(XmlElement filter, Document notification) throws DocumentedException {
67 notification = reReadDocument(notification);
68 removeEventTimeNode(notification);
69 if (isSupported(filter)) {
70 return Optional.fromNullable(filteredNotification(filter, notification));
72 return Optional.of(extractNotificationContent(notification));
75 private static Document reReadDocument(Document notification) throws DocumentedException {
76 // FIXME: rpcReply document must be reread otherwise some nodes do not inherit namespaces. (services/service)
78 notification = XmlUtil.readXmlToDocument(XmlUtil.toString(notification, true));
79 } catch (SAXException | IOException e) {
80 LOG.error("Cannot transform document", e);
81 throw new DocumentedException("Cannot transform document" + e);
86 private static void removeEventTimeNode(Document document) {
87 final Node eventTimeNode = document.getDocumentElement().getElementsByTagNameNS(
88 XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0, XmlNetconfConstants.EVENT_TIME).item(0);
89 document.getDocumentElement().removeChild(eventTimeNode);
92 private static boolean isSupported(XmlElement filter) {
93 return "subtree".equals(filter.getAttribute("type"))||
94 "subtree".equals(filter.getAttribute("type", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
97 private static Document extractNotificationContent(Document notification) throws DocumentedException {
98 XmlElement root = XmlElement.fromDomElement(notification.getDocumentElement());
99 XmlElement content = root.getOnlyChildElement();
100 notification.removeChild(root.getDomElement());
101 notification.appendChild(content.getDomElement());
105 private static Document filteredNotification(XmlElement filter, Document originalNotification) throws DocumentedException {
106 Document result = XmlUtil.newDocument();
107 XmlElement dataSrc = XmlElement.fromDomDocument(originalNotification);
108 Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
109 for (XmlElement filterChild : filter.getChildElements()) {
110 addSubtree2(filterChild, dataSrc.getOnlyChildElement(), XmlElement.fromDomElement(dataDst));
112 if(dataDst.getFirstChild() != null) {
113 result.appendChild(dataDst.getFirstChild());
120 private static Document filtered(XmlElement filter, Document originalReplyDocument) throws DocumentedException {
121 Document result = XmlUtil.newDocument();
122 // even if filter is empty, copy /rpc/data
123 Element rpcReply = originalReplyDocument.getDocumentElement();
124 Node rpcReplyDst = result.importNode(rpcReply, false);
125 result.appendChild(rpcReplyDst);
126 XmlElement dataSrc = XmlElement.fromDomElement(rpcReply).getOnlyChildElement("data", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
127 Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
128 rpcReplyDst.appendChild(dataDst);
129 addSubtree(filter, dataSrc, XmlElement.fromDomElement(dataDst));
134 private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) throws DocumentedException {
135 for (XmlElement srcChild : src.getChildElements()) {
136 for (XmlElement filterChild : filter.getChildElements()) {
137 addSubtree2(filterChild, srcChild, dst);
142 private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) throws DocumentedException {
143 Document document = dstParent.getDomElement().getOwnerDocument();
144 MatchingResult matches = matches(src, filter);
145 if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) {
146 // copy srcChild to dst
147 boolean filterHasChildren = filter.getChildElements().isEmpty() == false;
148 // copy to depth if this is leaf of filter tree
149 Element copied = (Element) document.importNode(src.getDomElement(), filterHasChildren == false);
150 boolean shouldAppend = filterHasChildren == false;
151 if (filterHasChildren) { // this implies TAG_MATCH
152 // do the same recursively
153 int numberOfTextMatchingChildren = 0;
154 for (XmlElement srcChild : src.getChildElements()) {
155 for (XmlElement filterChild : filter.getChildElements()) {
156 MatchingResult childMatch = addSubtree2(filterChild, srcChild, XmlElement.fromDomElement(copied));
157 if (childMatch == MatchingResult.CONTENT_MISMATCH) {
158 return MatchingResult.NO_MATCH;
160 if (childMatch == MatchingResult.CONTENT_MATCH) {
161 numberOfTextMatchingChildren++;
163 shouldAppend |= childMatch != MatchingResult.NO_MATCH;
166 // if only text matching child filters are specified..
167 if (numberOfTextMatchingChildren == filter.getChildElements().size()) {
168 // force all children to be added (to depth). This is done by copying parent node to depth.
169 // implies shouldAppend == true
170 copied = (Element) document.importNode(src.getDomElement(), true);
174 dstParent.getDomElement().appendChild(copied);
181 * Shallow compare src node to filter: tag name and namespace must match.
182 * If filter node has no children and has text content, it also must match.
184 private static MatchingResult matches(XmlElement src, XmlElement filter) throws DocumentedException {
185 boolean tagMatch = src.getName().equals(filter.getName()) &&
186 src.getNamespaceOptionally().equals(filter.getNamespaceOptionally());
187 MatchingResult result = null;
189 // match text content
190 Optional<String> maybeText = filter.getOnlyTextContentOptionally();
191 if (maybeText.isPresent()) {
192 if (maybeText.equals(src.getOnlyTextContentOptionally()) || prefixedContentMatches(filter, src)) {
193 result = MatchingResult.CONTENT_MATCH;
195 result = MatchingResult.CONTENT_MISMATCH;
198 // match attributes, combination of content and tag is not supported
199 if (result == null) {
200 for (Attr attr : filter.getAttributes().values()) {
201 // ignore namespace declarations
202 if (XmlUtil.XMLNS_URI.equals(attr.getNamespaceURI()) == false ) {
203 // find attr with matching localName(), namespaceURI(), == value() in src
204 String found = src.getAttribute(attr.getLocalName(), attr.getNamespaceURI());
205 if (attr.getValue().equals(found) && result != MatchingResult.NO_MATCH) {
206 result = MatchingResult.TAG_MATCH;
208 result = MatchingResult.NO_MATCH;
213 if (result == null) {
214 result = MatchingResult.TAG_MATCH;
217 if (result == null) {
218 result = MatchingResult.NO_MATCH;
220 LOG.debug("Matching {} to {} resulted in {}", src, filter, result);
224 private static boolean prefixedContentMatches(final XmlElement filter, final XmlElement src) throws DocumentedException {
225 final Map.Entry<String, String> prefixToNamespaceOfFilter;
226 final Map.Entry<String, String> prefixToNamespaceOfSrc;
228 prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent();
229 prefixToNamespaceOfSrc = src.findNamespaceOfTextContent();
230 } catch (IllegalArgumentException e) {
231 //if we can't find namespace of prefix - it's not a prefix, so it doesn't match
235 final String prefix = prefixToNamespaceOfFilter.getKey();
236 // If this is not a prefixed content, we do not need to continue since content do not match
237 if (prefix.equals(XmlElement.DEFAULT_NAMESPACE_PREFIX)) {
240 // Namespace mismatch
241 if (!prefixToNamespaceOfFilter.getValue().equals(prefixToNamespaceOfSrc.getValue())) {
245 final String unprefixedFilterContent = filter.getTextContent().substring(prefixToNamespaceOfFilter.getKey().length() + 1);
246 final String unprefixedSrcContnet = src.getTextContent().substring(prefixToNamespaceOfSrc.getKey().length() + 1);
247 // Finally compare unprefixed content
248 return unprefixedFilterContent.equals(unprefixedSrcContnet);
251 enum MatchingResult {
252 NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH