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