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;
13 import org.opendaylight.controller.config.util.xml.DocumentedException;
14 import org.opendaylight.controller.config.util.xml.XmlElement;
15 import org.opendaylight.controller.config.util.xml.XmlUtil;
16 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
17 import org.opendaylight.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 import org.w3c.dom.Attr;
21 import org.w3c.dom.Document;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
26 * See <a href="http://tools.ietf.org/html/rfc6241#section-6">rfc6241</a> for details.
28 public class SubtreeFilter {
29 private static final Logger LOG = LoggerFactory.getLogger(SubtreeFilter.class);
31 public static Document applyRpcSubtreeFilter(Document requestDocument,
32 Document rpcReply) throws DocumentedException {
33 OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(requestDocument);
34 if (XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0.equals(operationNameAndNamespace.getNamespace())
35 && XmlNetconfConstants.GET.equals(operationNameAndNamespace.getOperationName())
36 || XmlNetconfConstants.GET_CONFIG.equals(operationNameAndNamespace.getOperationName())) {
37 // process subtree filtering here, in case registered netconf operations do
38 // not implement filtering.
39 Optional<XmlElement> maybeFilter = operationNameAndNamespace.getOperationElement()
40 .getOnlyChildElementOptionally(XmlNetconfConstants.FILTER,
41 XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
42 if (!maybeFilter.isPresent()) {
45 XmlElement filter = maybeFilter.get();
46 if (isSupported(filter)) {
49 return filtered(maybeFilter.get(), rpcReply);
53 return rpcReply; // return identical document
57 * Filters notification content. If filter type isn't of type "subtree", returns unchanged notification content.
58 * If no match is found, absent is returned.
59 * @param filter filter
60 * @param notification notification
61 * @return document containing filtered notification content
62 * @throws DocumentedException if operation fails
64 public static Optional<Document> applySubtreeNotificationFilter(XmlElement filter,
65 Document notification) throws DocumentedException {
66 removeEventTimeNode(notification);
67 if (isSupported(filter)) {
68 return Optional.fromNullable(filteredNotification(filter, notification));
70 return Optional.of(extractNotificationContent(notification));
73 private static void removeEventTimeNode(Document document) {
74 final Node eventTimeNode = document.getDocumentElement().getElementsByTagNameNS(XmlNetconfConstants
75 .URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0, XmlNetconfConstants.EVENT_TIME).item(0);
76 document.getDocumentElement().removeChild(eventTimeNode);
79 private static boolean isSupported(XmlElement filter) {
80 return "subtree".equals(filter.getAttribute("type"))
81 || "subtree".equals(filter.getAttribute("type",
82 XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
85 private static Document extractNotificationContent(Document notification) throws DocumentedException {
86 XmlElement root = XmlElement.fromDomElement(notification.getDocumentElement());
87 XmlElement content = root.getOnlyChildElement();
88 notification.removeChild(root.getDomElement());
89 notification.appendChild(content.getDomElement());
93 private static Document filteredNotification(XmlElement filter,
94 Document originalNotification) throws DocumentedException {
95 Document result = XmlUtil.newDocument();
96 XmlElement dataSrc = XmlElement.fromDomDocument(originalNotification);
97 Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
98 for (XmlElement filterChild : filter.getChildElements()) {
99 addSubtree2(filterChild, dataSrc.getOnlyChildElement(), XmlElement.fromDomElement(dataDst));
101 if (dataDst.getFirstChild() != null) {
102 result.appendChild(dataDst.getFirstChild());
109 private static Document filtered(XmlElement filter, Document originalReplyDocument) throws DocumentedException {
110 Document result = XmlUtil.newDocument();
111 // even if filter is empty, copy /rpc/data
112 Element rpcReply = originalReplyDocument.getDocumentElement();
113 Node rpcReplyDst = result.importNode(rpcReply, false);
114 result.appendChild(rpcReplyDst);
115 XmlElement dataSrc = XmlElement.fromDomElement(rpcReply).getOnlyChildElement("data",
116 XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
117 Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
118 rpcReplyDst.appendChild(dataDst);
119 addSubtree(filter, dataSrc, XmlElement.fromDomElement(dataDst));
124 private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) throws DocumentedException {
125 for (XmlElement srcChild : src.getChildElements()) {
126 for (XmlElement filterChild : filter.getChildElements()) {
127 addSubtree2(filterChild, srcChild, dst);
132 private static MatchingResult addSubtree2(XmlElement filter, XmlElement src,
133 XmlElement dstParent) throws DocumentedException {
134 Document document = dstParent.getDomElement().getOwnerDocument();
135 MatchingResult matches = matches(src, filter);
136 if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) {
137 // copy srcChild to dst
138 boolean filterHasChildren = !filter.getChildElements().isEmpty();
139 // copy to depth if this is leaf of filter tree
140 Element copied = (Element) document.importNode(src.getDomElement(), !filterHasChildren);
141 boolean shouldAppend = !filterHasChildren;
142 if (filterHasChildren) { // this implies TAG_MATCH
143 // do the same recursively
144 int numberOfTextMatchingChildren = 0;
145 for (XmlElement srcChild : src.getChildElements()) {
146 for (XmlElement filterChild : filter.getChildElements()) {
147 MatchingResult childMatch =
148 addSubtree2(filterChild, srcChild, XmlElement.fromDomElement(copied));
149 if (childMatch == MatchingResult.CONTENT_MISMATCH) {
150 return MatchingResult.NO_MATCH;
152 if (childMatch == MatchingResult.CONTENT_MATCH) {
153 numberOfTextMatchingChildren++;
155 shouldAppend |= childMatch != MatchingResult.NO_MATCH;
158 // if only text matching child filters are specified..
159 if (numberOfTextMatchingChildren == filter.getChildElements().size()) {
160 // force all children to be added (to depth). This is done by copying parent node to depth.
161 // implies shouldAppend == true
162 copied = (Element) document.importNode(src.getDomElement(), true);
166 dstParent.getDomElement().appendChild(copied);
173 * Shallow compare src node to filter: tag name and namespace must match.
174 * If filter node has no children and has text content, it also must match.
176 private static MatchingResult matches(XmlElement src, XmlElement filter) throws DocumentedException {
177 boolean tagMatch = src.getName().equals(filter.getName())
178 && src.getNamespaceOptionally().equals(filter.getNamespaceOptionally());
179 MatchingResult result = null;
181 // match text content
182 Optional<String> maybeText = filter.getOnlyTextContentOptionally();
183 if (maybeText.isPresent()) {
184 if (maybeText.equals(src.getOnlyTextContentOptionally()) || prefixedContentMatches(filter, src)) {
185 result = MatchingResult.CONTENT_MATCH;
187 result = MatchingResult.CONTENT_MISMATCH;
190 // match attributes, combination of content and tag is not supported
191 if (result == null) {
192 for (Attr attr : filter.getAttributes().values()) {
193 // ignore namespace declarations
194 if (!XmlUtil.XMLNS_URI.equals(attr.getNamespaceURI())) {
195 // find attr with matching localName(), namespaceURI(), == value() in src
196 String found = src.getAttribute(attr.getLocalName(), attr.getNamespaceURI());
197 if (attr.getValue().equals(found) && result != MatchingResult.NO_MATCH) {
198 result = MatchingResult.TAG_MATCH;
200 result = MatchingResult.NO_MATCH;
205 if (result == null) {
206 result = MatchingResult.TAG_MATCH;
209 if (result == null) {
210 result = MatchingResult.NO_MATCH;
212 LOG.debug("Matching {} to {} resulted in {}", src, filter, result);
216 private static boolean prefixedContentMatches(final XmlElement filter,
217 final XmlElement src) throws DocumentedException {
218 final Map.Entry<String, String> prefixToNamespaceOfFilter;
219 final Map.Entry<String, String> prefixToNamespaceOfSrc;
221 prefixToNamespaceOfFilter = filter.findNamespaceOfTextContent();
222 prefixToNamespaceOfSrc = src.findNamespaceOfTextContent();
223 } catch (IllegalArgumentException e) {
224 //if we can't find namespace of prefix - it's not a prefix, so it doesn't match
228 final String prefix = prefixToNamespaceOfFilter.getKey();
229 // If this is not a prefixed content, we do not need to continue since content do not match
230 if (prefix.equals(XmlElement.DEFAULT_NAMESPACE_PREFIX)) {
233 // Namespace mismatch
234 if (!prefixToNamespaceOfFilter.getValue().equals(prefixToNamespaceOfSrc.getValue())) {
238 final String unprefixedFilterContent =
239 filter.getTextContent().substring(prefixToNamespaceOfFilter.getKey().length() + 1);
240 final String unprefixedSrcContnet =
241 src.getTextContent().substring(prefixToNamespaceOfSrc.getKey().length() + 1);
242 // Finally compare unprefixed content
243 return unprefixedFilterContent.equals(unprefixedSrcContnet);
246 enum MatchingResult {
247 NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH