feb60142b4f6fbc6556bae582987f547a76922e5
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / AbstractQueryParams.java
1 /*
2  * Copyright (c) 2016 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 package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Preconditions;
12 import java.io.StringReader;
13 import java.time.Instant;
14 import javax.xml.XMLConstants;
15 import javax.xml.parsers.DocumentBuilderFactory;
16 import javax.xml.parsers.ParserConfigurationException;
17 import javax.xml.xpath.XPath;
18 import javax.xml.xpath.XPathConstants;
19 import javax.xml.xpath.XPathFactory;
20 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
21 import org.w3c.dom.Document;
22 import org.xml.sax.InputSource;
23
24 /**
25  * Features of query parameters part of both notifications.
26  */
27 abstract class AbstractQueryParams extends AbstractNotificationsData {
28     // FIXME: BUG-7956: switch to using UntrustedXML
29     private static final DocumentBuilderFactory DBF;
30
31     static {
32         final DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
33         f.setCoalescing(true);
34         f.setExpandEntityReferences(false);
35         f.setIgnoringElementContentWhitespace(true);
36         f.setIgnoringComments(true);
37         f.setXIncludeAware(false);
38         try {
39             f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
40             f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
41             f.setFeature("http://xml.org/sax/features/external-general-entities", false);
42             f.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
43         } catch (final ParserConfigurationException e) {
44             throw new ExceptionInInitializerError(e);
45         }
46         DBF = f;
47     }
48
49     // FIXME: these should be final
50     private Instant start = null;
51     private Instant stop = null;
52     private String filter = null;
53     private boolean leafNodesOnly = false;
54
55     @VisibleForTesting
56     public final Instant getStart() {
57         return start;
58     }
59
60     /**
61      * Set query parameters for listener.
62      *
63      * @param start         Start-time of getting notification.
64      * @param stop          Stop-time of getting notification.
65      * @param filter        Indicates which subset of all possible events are of interest.
66      * @param leafNodesOnly If TRUE, notifications will contain changes of leaf nodes only.
67      */
68     @SuppressWarnings("checkstyle:hiddenField")
69     public void setQueryParams(final Instant start, final Instant stop, final String filter,
70             final boolean leafNodesOnly) {
71         this.start = Preconditions.checkNotNull(start);
72         this.stop = stop;
73         this.filter = filter;
74         this.leafNodesOnly = leafNodesOnly;
75     }
76
77     /**
78      * Check whether this query should only notify about leaf node changes.
79      *
80      * @return true if this query should only notify about leaf node changes
81      */
82     boolean getLeafNodesOnly() {
83         return leafNodesOnly;
84     }
85
86     @SuppressWarnings("checkstyle:IllegalCatch")
87     <T extends BaseListenerInterface> boolean checkStartStop(final Instant now, final T listener) {
88         if (this.stop != null) {
89             if (this.start.compareTo(now) < 0 && this.stop.compareTo(now) > 0) {
90                 return true;
91             }
92             if (this.stop.compareTo(now) < 0) {
93                 try {
94                     listener.close();
95                 } catch (final Exception e) {
96                     throw new RestconfDocumentedException("Problem with unregister listener." + e);
97                 }
98             }
99         } else if (this.start != null) {
100             if (this.start.compareTo(now) < 0) {
101                 this.start = null;
102                 return true;
103             }
104         } else {
105             return true;
106         }
107         return false;
108     }
109
110     /**
111      * Check if is filter used and then prepare and post data do client.
112      *
113      * @param xml XML data of notification.
114      */
115     @SuppressWarnings("checkstyle:IllegalCatch")
116     boolean checkFilter(final String xml) {
117         if (this.filter == null) {
118             return true;
119         }
120         try {
121             return parseFilterParam(xml);
122         } catch (final Exception e) {
123             throw new RestconfDocumentedException("Problem while parsing filter.", e);
124         }
125     }
126
127     /**
128      * Parse and evaluate filter statement by XML format.
129      *
130      * @return {@code true} or {@code false} depending on filter expression and data of notification.
131      * @throws Exception If operation fails.
132      */
133     private boolean parseFilterParam(final String xml) throws Exception {
134         final Document docOfXml = DBF.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
135         final XPath xPath = XPathFactory.newInstance().newXPath();
136         // FIXME: BUG-7956: xPath.setNamespaceContext(nsContext);
137         return (boolean) xPath.compile(this.filter).evaluate(docOfXml, XPathConstants.BOOLEAN);
138     }
139 }