Bug 977: Return RpcError result on neconf failure
[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 org.opendaylight.controller.netconf.api.NetconfDocumentedException;
14 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
15 import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace;
16 import org.opendaylight.controller.netconf.util.xml.XmlElement;
17 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
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;
24 import org.xml.sax.SAXException;
25
26 /**
27  * See <a href="http://tools.ietf.org/html/rfc6241#section-6">rfc6241</a> for details.
28  */
29 public class SubtreeFilter {
30     private static final Logger logger = LoggerFactory.getLogger(SubtreeFilter.class);
31
32     static Document applySubtreeFilter(Document requestDocument, Document rpcReply) throws NetconfDocumentedException {
33         // FIXME: rpcReply document must be reread otherwise some nodes do not inherit namespaces. (services/service)
34         try {
35             rpcReply = XmlUtil.readXmlToDocument(XmlUtil.toString(rpcReply, true));
36         } catch (SAXException | IOException e) {
37             logger.error("Cannot transform document", e);
38             throw new NetconfDocumentedException("Cannot transform document");
39         }
40
41         OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(requestDocument);
42         if (XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0.equals(operationNameAndNamespace.getNamespace()) &&
43                 XmlNetconfConstants.GET.equals(operationNameAndNamespace.getOperationName()) ||
44                 XmlNetconfConstants.GET_CONFIG.equals(operationNameAndNamespace.getOperationName())) {
45             // process subtree filtering here, in case registered netconf operations do
46             // not implement filtering.
47             Optional<XmlElement> maybeFilter = operationNameAndNamespace.getOperationElement().getOnlyChildElementOptionally(
48                     XmlNetconfConstants.FILTER, XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
49             if (maybeFilter.isPresent() && (
50                     "subtree".equals(maybeFilter.get().getAttribute("type"))||
51                             "subtree".equals(maybeFilter.get().getAttribute("type", XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0))
52             )) {
53
54
55                 // do
56                 return filtered(maybeFilter.get(), rpcReply);
57             }
58         }
59         return rpcReply; // return identical document
60     }
61
62     private static Document filtered(XmlElement filter, Document originalReplyDocument) throws NetconfDocumentedException {
63         Document result = XmlUtil.newDocument();
64         // even if filter is empty, copy /rpc/data
65         Element rpcReply = originalReplyDocument.getDocumentElement();
66         Node rpcReplyDst = result.importNode(rpcReply, false);
67         result.appendChild(rpcReplyDst);
68         XmlElement dataSrc = XmlElement.fromDomElement(rpcReply).getOnlyChildElement("data", XmlNetconfConstants.RFC4741_TARGET_NAMESPACE);
69         Element dataDst = (Element) result.importNode(dataSrc.getDomElement(), false);
70         rpcReplyDst.appendChild(dataDst);
71         addSubtree(filter, dataSrc, XmlElement.fromDomElement(dataDst));
72
73         return result;
74     }
75
76     private static void addSubtree(XmlElement filter, XmlElement src, XmlElement dst) {
77         for (XmlElement srcChild : src.getChildElements()) {
78             for (XmlElement filterChild : filter.getChildElements()) {
79                 addSubtree2(filterChild, srcChild, dst);
80             }
81         }
82     }
83
84     private static MatchingResult addSubtree2(XmlElement filter, XmlElement src, XmlElement dstParent) {
85         Document document = dstParent.getDomElement().getOwnerDocument();
86         MatchingResult matches = matches(src, filter);
87         if (matches != MatchingResult.NO_MATCH && matches != MatchingResult.CONTENT_MISMATCH) {
88             // copy srcChild to dst
89             boolean filterHasChildren = filter.getChildElements().isEmpty() == false;
90             // copy to depth if this is leaf of filter tree
91             Element copied = (Element) document.importNode(src.getDomElement(), filterHasChildren == false);
92             boolean shouldAppend = filterHasChildren == false;
93             if (filterHasChildren) { // this implies TAG_MATCH
94                 // do the same recursively
95                 int numberOfTextMatchingChildren = 0;
96                 for (XmlElement srcChild : src.getChildElements()) {
97                     for (XmlElement filterChild : filter.getChildElements()) {
98                         MatchingResult childMatch = addSubtree2(filterChild, srcChild, XmlElement.fromDomElement(copied));
99                         if (childMatch == MatchingResult.CONTENT_MISMATCH) {
100                             return MatchingResult.NO_MATCH;
101                         }
102                         if (childMatch == MatchingResult.CONTENT_MATCH) {
103                             numberOfTextMatchingChildren++;
104                         }
105                         shouldAppend |= childMatch != MatchingResult.NO_MATCH;
106                     }
107                 }
108                 // if only text matching child filters are specified..
109                 if (numberOfTextMatchingChildren == filter.getChildElements().size()) {
110                     // force all children to be added (to depth). This is done by copying parent node to depth.
111                     // implies shouldAppend == true
112                     copied = (Element) document.importNode(src.getDomElement(), true);
113                 }
114             }
115             if (shouldAppend) {
116                 dstParent.getDomElement().appendChild(copied);
117             }
118         }
119         return matches;
120     }
121
122     /**
123      * Shallow compare src node to filter: tag name and namespace must match.
124      * If filter node has no children and has text content, it also must match.
125      */
126     private static MatchingResult matches(XmlElement src, XmlElement filter) {
127         boolean tagMatch = src.getName().equals(filter.getName()) &&
128                 src.getNamespaceOptionally().equals(filter.getNamespaceOptionally());
129         MatchingResult result = null;
130         if (tagMatch) {
131             // match text content
132             Optional<String> maybeText = filter.getOnlyTextContentOptionally();
133             if (maybeText.isPresent()) {
134                 if (maybeText.equals(src.getOnlyTextContentOptionally())) {
135                     result = MatchingResult.CONTENT_MATCH;
136                 } else {
137                     result = MatchingResult.CONTENT_MISMATCH;
138                 }
139             }
140             // match attributes, combination of content and tag is not supported
141             if (result == null) {
142                 for (Attr attr : filter.getAttributes().values()) {
143                     // ignore namespace declarations
144                     if (XmlUtil.XMLNS_URI.equals(attr.getNamespaceURI()) == false ) {
145                         // find attr with matching localName(),  namespaceURI(),  == value() in src
146                         String found = src.getAttribute(attr.getLocalName(), attr.getNamespaceURI());
147                         if (attr.getValue().equals(found) && result != MatchingResult.NO_MATCH) {
148                             result = MatchingResult.TAG_MATCH;
149                         } else {
150                             result = MatchingResult.NO_MATCH;
151                         }
152                     }
153                 }
154             }
155             if (result == null) {
156                 result = MatchingResult.TAG_MATCH;
157             }
158         }
159         if (result == null) {
160             result = MatchingResult.NO_MATCH;
161         }
162         logger.debug("Matching {} to {} resulted in {}", src, filter, tagMatch);
163         return result;
164     }
165
166     enum MatchingResult {
167         NO_MATCH, TAG_MATCH, CONTENT_MATCH, CONTENT_MISMATCH
168     }
169 }