Bug 4883 - implement query parameter - filter 68/48268/6
authorJakub Toth <jatoth@cisco.com>
Fri, 14 Oct 2016 08:43:36 +0000 (10:43 +0200)
committerJakub Toth <jatoth@cisco.com>
Mon, 14 Nov 2016 15:28:02 +0000 (15:28 +0000)
  * added test

Change-Id: I75ace69452c31b29460727f36c66cf397f44e26e
Signed-off-by: Jakub Toth <jatoth@cisco.com>
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/NotificationListenerAdapter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/SubscribeToStreamUtil.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_DOWN.xml [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_NUMBER.xml [new file with mode: 0644]

index a7e2161702ed88ab184c6d1f3bd5b8a087cef264..eff4885f630de665ee3029c7cf9ee4fe06c220a7 100644 (file)
@@ -1051,8 +1051,10 @@ public class RestconfImpl implements RestconfService {
     public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
         boolean startTime_used = false;
         boolean stopTime_used = false;
+        boolean filter_used = false;
         Date start = null;
         Date stop = null;
+        String filter = null;
 
         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
             switch (entry.getKey()) {
@@ -1072,6 +1074,14 @@ public class RestconfImpl implements RestconfService {
                         throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
                     }
                     break;
+                case "filter":
+                    if (!filter_used) {
+                        filter_used = true;
+                        filter = entry.getValue().iterator().next();
+                    } else {
+                        throw new RestconfDocumentedException("Filter parameter can be used only once.");
+                    }
+                    break;
                 default:
                     throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey());
             }
@@ -1081,9 +1091,9 @@ public class RestconfImpl implements RestconfService {
         }
         URI response = null;
         if (identifier.contains(DATA_SUBSCR)) {
-            response = dataSubs(identifier, uriInfo, start, stop);
+            response = dataSubs(identifier, uriInfo, start, stop, filter);
         } else if (identifier.contains(NOTIFICATION_STREAM)) {
-            response = notifStream(identifier, uriInfo, start, stop);
+            response = notifStream(identifier, uriInfo, start, stop, filter);
         }
 
         if(response != null){
@@ -1161,9 +1171,12 @@ public class RestconfImpl implements RestconfService {
      *            - stop-time of getting notification
      * @param start
      *            - start-time of getting notification
-     * @return {@link Response}
+     * @param filter
+     *            - indicate wh ich subset of allpossible events are of interest
+     * @return {@link URI} of location
      */
-    private URI notifStream(final String identifier, final UriInfo uriInfo, final Date start, final Date stop) {
+    private URI notifStream(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
+            final String filter) {
         final String streamName = Notificator.createStreamNameFromUri(identifier);
         if (Strings.isNullOrEmpty(streamName)) {
             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
@@ -1176,7 +1189,7 @@ public class RestconfImpl implements RestconfService {
 
         for (final NotificationListenerAdapter listener : listeners) {
             this.broker.registerToListenNotification(listener);
-            listener.setTime(start, stop);
+            listener.setQueryParams(start, stop, filter);
         }
 
         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
@@ -1204,9 +1217,12 @@ public class RestconfImpl implements RestconfService {
      *            - start-time of getting notification
      * @param start
      *            - stop-time of getting notification
-     * @return {@link Response}
+     * @param filter
+     *            - indicate which subset of all possible events are of interest
+     * @return {@link URI} of location
      */
-    private URI dataSubs(final String identifier, final UriInfo uriInfo, final Date start, final Date stop) {
+    private URI dataSubs(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
+            final String filter) {
         final String streamName = Notificator.createStreamNameFromUri(identifier);
         if (Strings.isNullOrEmpty(streamName)) {
             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
@@ -1216,7 +1232,7 @@ public class RestconfImpl implements RestconfService {
         if (listener == null) {
             throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
         }
-        listener.setTimer(start, stop);
+        listener.setQueryParams(start, stop, filter);
 
         final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
         final LogicalDatastoreType datastore = parserURIEnumParameter(LogicalDatastoreType.class,
index 0f784d11b4837e1db0b488cceb92b51587fdcac5..ccc30a5616c82b7c082b17048afdc8c493557ec3 100644 (file)
@@ -17,12 +17,12 @@ import io.netty.util.internal.ConcurrentSet;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Random;
@@ -42,6 +42,9 @@ import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMResult;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
 import org.json.JSONObject;
 import org.json.XML;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
@@ -63,6 +66,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWrit
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.slf4j.Logger;
@@ -70,6 +74,7 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
 
 /**
  * {@link ListenerAdapter} is responsible to track events, which occurred by changing data in data source.
@@ -92,6 +97,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
     private final NotificationOutputType outputType;
     private Date start = null;
     private Date stop = null;
+    private String filter = null;
 
     /**
      * Creates new {@link ListenerAdapter} listener specified by path and stream
@@ -121,7 +127,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
         final Date now = new Date();
         if (this.stop != null) {
             if ((this.start.compareTo(now) < 0) && (this.stop.compareTo(now) > 0)) {
-                prepareAndPostData(change);
+                checkFilter(change);
             }
             if (this.stop.compareTo(now) < 0) {
                 try {
@@ -133,17 +139,57 @@ public class ListenerAdapter implements DOMDataChangeListener {
         } else if (this.start != null) {
             if (this.start.compareTo(now) < 0) {
                 this.start = null;
-                prepareAndPostData(change);
+                checkFilter(change);
             }
         } else {
-            prepareAndPostData(change);
+            checkFilter(change);
         }
     }
 
-    private void prepareAndPostData(final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change) {
-        if (!change.getCreatedData().isEmpty() || !change.getUpdatedData().isEmpty()
-                || !change.getRemovedPaths().isEmpty()) {
-            final String xml = prepareXmlFrom(change);
+    /**
+     * Check if is filter used and then prepare and post data do client
+     *
+     * @param change
+     *            - data of notification
+     */
+    private void checkFilter(final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> change) {
+        final String xml = prepareXmlFrom(change);
+        if (this.filter == null) {
+            prepareAndPostData(xml);
+        } else {
+            try {
+                if (parseFilterParam(xml)) {
+                    prepareAndPostData(xml);
+                }
+            } catch (final Exception e) {
+                throw new RestconfDocumentedException("Problem while parsing filter.", e);
+            }
+        }
+    }
+
+    /**
+     * Parse and evaluate filter value by xml
+     *
+     * @param xml
+     *            - notification data in xml
+     * @return true or false - depends on filter expression and data of
+     *         notifiaction
+     * @throws Exception
+     */
+    private boolean parseFilterParam(final String xml) throws Exception {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+        final Document docOfXml = builder.parse(new InputSource(new StringReader(xml)));
+        final XPath xPath = XPathFactory.newInstance().newXPath();
+        return (boolean) xPath.compile(this.filter).evaluate(docOfXml, XPathConstants.BOOLEAN);
+    }
+
+    /**
+     * Prepare data of notification and data to client
+     *
+     * @param xml
+     */
+    private void prepareAndPostData(final String xml) {
             final Event event = new Event(EventType.NOTIFY);
             if (this.outputType.equals(NotificationOutputType.JSON)) {
                 final JSONObject jsonObject = XML.toJSONObject(xml);
@@ -152,7 +198,6 @@ public class ListenerAdapter implements DOMDataChangeListener {
                 event.setData(xml);
             }
             this.eventBus.post(event);
-        }
     }
 
     /**
@@ -499,24 +544,21 @@ public class ListenerAdapter implements DOMDataChangeListener {
      *            {@link Element}
      */
     private void addPathAsValueToElement(final YangInstanceIdentifier path, final Element element) {
-        // Map< key = namespace, value = prefix>
-        final Map<String, String> prefixes = new HashMap<>();
         final YangInstanceIdentifier normalizedPath = ControllerContext.getInstance().toXpathRepresentation(path);
         final StringBuilder textContent = new StringBuilder();
 
-        // FIXME: BUG-1281: this is duplicated code from yangtools (BUG-1275)
         for (final PathArgument pathArgument : normalizedPath.getPathArguments()) {
             if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) {
                 continue;
             }
             textContent.append("/");
-            writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType(), prefixes);
+            writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType());
             if (pathArgument instanceof NodeIdentifierWithPredicates) {
                 final Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
                 for (final QName keyValue : predicates.keySet()) {
                     final String predicateValue = String.valueOf(predicates.get(keyValue));
                     textContent.append("[");
-                    writeIdentifierWithNamespacePrefix(element, textContent, keyValue, prefixes);
+                    writeIdentifierWithNamespacePrefix(element, textContent, keyValue);
                     textContent.append("='");
                     textContent.append(predicateValue);
                     textContent.append("'");
@@ -541,21 +583,13 @@ public class ListenerAdapter implements DOMDataChangeListener {
      *            StringBuilder
      * @param qName
      *            QName
-     * @param prefixes
-     *            Map of namespaces and prefixes.
      */
     private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent,
-            final QName qName, final Map<String, String> prefixes) {
-        final String namespace = qName.getNamespace().toString();
-        String prefix = prefixes.get(namespace);
-        if (prefix == null) {
-            prefix = generateNewPrefix(prefixes.values());
-        }
-
-        element.setAttribute("xmlns:" + prefix, namespace);
-        textContent.append(prefix);
-        prefixes.put(namespace, prefix);
+            final QName qName) {
+        final Module module = ControllerContext.getInstance().getGlobalSchema()
+                .findModuleByNamespaceAndRevision(qName.getNamespace(), qName.getRevision());
 
+        textContent.append(module.getName());
         textContent.append(":");
         textContent.append(qName.getLocalName());
     }
@@ -701,10 +735,13 @@ public class ListenerAdapter implements DOMDataChangeListener {
      *            - start-time of getting notification
      * @param stop
      *            - stop-time of getting notification
+     * @param filter
+     *            - indicate which subset of all possible events are of interest
      */
-    public void setTimer(final Date start, final Date stop) {
+    public void setQueryParams(final Date start, final Date stop, final String filter) {
         this.start = start;
         this.stop = stop;
+        this.filter = filter;
     }
 
 }
index 303bfb30b5a0519dcce3ebfdb41a903380f35445..1d32b39e23bb077c59120d7bcd5a78c09db0fe35 100644 (file)
@@ -18,11 +18,14 @@ import io.netty.util.internal.ConcurrentSet;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Set;
 import java.util.concurrent.Executors;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
@@ -33,6 +36,9 @@ import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMResult;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
 import org.json.JSONObject;
 import org.json.XML;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
@@ -56,6 +62,7 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
 
 /**
  * {@link NotificationListenerAdapter} is responsible to track events on
@@ -77,6 +84,7 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
     private final String outputType;
     private Date start = null;
     private Date stop = null;
+    private String filter;
 
     /**
      * Set path of listener and stream name, register event bus.
@@ -104,7 +112,7 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
         final Date now = new Date();
         if (this.stop != null) {
             if ((this.start.compareTo(now) < 0) && (this.stop.compareTo(now) > 0)) {
-                prepareAndPostData(notification);
+                checkFilter(notification);
             }
             if (this.stop.compareTo(now) < 0) {
                 try {
@@ -116,18 +124,57 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
         } else if (this.start != null) {
             if (this.start.compareTo(now) < 0) {
                 this.start = null;
-                prepareAndPostData(notification);
+                checkFilter(notification);
             }
         } else {
-            prepareAndPostData(notification);
+            checkFilter(notification);
         }
     }
 
     /**
+     * Check if is filter used and then prepare and post data do client
+     *
      * @param notification
+     *            - data of notification
      */
-    private void prepareAndPostData(final DOMNotification notification) {
+    private void checkFilter(final DOMNotification notification) {
         final String xml = prepareXmlFrom(notification);
+        if (this.filter == null) {
+            prepareAndPostData(xml);
+        } else {
+            try {
+                if (parseFilterParam(xml)) {
+                    prepareAndPostData(xml);
+                }
+            } catch (final Exception e) {
+                throw new RestconfDocumentedException("Problem while parsing filter.", e);
+            }
+        }
+    }
+
+    /**
+     * Parse and evaluate filter value by xml
+     *
+     * @param xml
+     *            - notification data in xml
+     * @return true or false - depends on filter expression and data of
+     *         notifiaction
+     * @throws Exception
+     */
+    private boolean parseFilterParam(final String xml) throws Exception {
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final DocumentBuilder builder = factory.newDocumentBuilder();
+        final Document docOfXml = builder.parse(new InputSource(new StringReader(xml)));
+        final XPath xPath = XPathFactory.newInstance().newXPath();
+        return (boolean) xPath.compile(this.filter).evaluate(docOfXml, XPathConstants.BOOLEAN);
+    }
+
+    /**
+     * Prepare data of notification and data to client
+     *
+     * @param xml
+     */
+    private void prepareAndPostData(final String xml) {
         final Event event = new Event(EventType.NOTIFY);
         if (this.outputType.equals("JSON")) {
             final JSONObject jsonObject = XML.toJSONObject(xml);
@@ -423,9 +470,12 @@ public class NotificationListenerAdapter implements DOMNotificationListener {
      *            - start-time of getting notification
      * @param stop
      *            - stop-time of getting notification
+     * @param filter
+     *            - indicate which subset of all possible events are of interest
      */
-    public void setTime(final Date start, final Date stop) {
+    public void setQueryParams(final Date start, final Date stop, final String filter) {
         this.start = start;
         this.stop = stop;
+        this.filter = filter;
     }
 }
index 6b0cbe3c2b178e8baaabdf2664f1a48de67ad449..9852330a0e532a0e84e5989d43c296db0d405df5 100644 (file)
@@ -56,8 +56,10 @@ public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSu
     public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) {
         boolean startTime_used = false;
         boolean stopTime_used = false;
+        boolean filter_used = false;
         Date start = null;
         Date stop = null;
+        String filter = null;
 
         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
             switch (entry.getKey()) {
@@ -77,6 +79,12 @@ public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSu
                         throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
                     }
                     break;
+                case "filter":
+                    if (!filter_used) {
+                        filter_used = true;
+                        filter = entry.getValue().iterator().next();
+                    }
+                    break;
                 default:
                     throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey());
             }
@@ -86,10 +94,11 @@ public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSu
         }
         URI response = null;
         if (identifier.contains(RestconfStreamsConstants.DATA_SUBSCR)) {
-            response = SubscribeToStreamUtil.dataSubs(identifier, uriInfo, start, stop, this.domDataBrokerHandler);
+            response =
+                    SubscribeToStreamUtil.dataSubs(identifier, uriInfo, start, stop, this.domDataBrokerHandler, filter);
         } else if (identifier.contains(RestconfStreamsConstants.NOTIFICATION_STREAM)) {
             response = SubscribeToStreamUtil.notifStream(identifier, uriInfo, start, stop,
-                    this.notificationServiceHandler);
+                    this.notificationServiceHandler, filter);
         }
 
         if (response != null) {
index 0904b02e461d4ba98b8a609859525f454716b03e..f6d45e56258e22ae4ef6aa37b2cec16335a2d19c 100644 (file)
@@ -153,10 +153,12 @@ public final class SubscribeToStreamUtil {
      *            - stop-time query parameter
      * @param notifiServiceHandler
      *            - DOMNotificationService handler for register listeners
+     * @param filter
+     *            - indicate which subset of all possible events are of interest
      * @return location for listening
      */
     public static URI notifStream(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
-            final NotificationServiceHandler notifiServiceHandler) {
+            final NotificationServiceHandler notifiServiceHandler, final String filter) {
         final String streamName = Notificator.createStreamNameFromUri(identifier);
         if (Strings.isNullOrEmpty(streamName)) {
             throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
@@ -169,7 +171,7 @@ public final class SubscribeToStreamUtil {
 
         for (final NotificationListenerAdapter listener : listeners) {
             registerToListenNotification(listener, notifiServiceHandler);
-            listener.setTime(start, stop);
+            listener.setQueryParams(start, stop, filter);
         }
 
         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
@@ -232,10 +234,12 @@ public final class SubscribeToStreamUtil {
      *            - stop-time query parameter
      * @param domDataBrokerHandler
      *            - DOMDataBroker handler for register listener
+     * @param filter
+     *            - indicate which subset of all possible events are of interest
      * @return location for listening
      */
     public static URI dataSubs(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
-            final DOMDataBrokerHandler domDataBrokerHandler) {
+            final DOMDataBrokerHandler domDataBrokerHandler, final String filter) {
         final Map<String, String> mapOfValues = SubscribeToStreamUtil.mapValuesFromUri(identifier);
 
         final LogicalDatastoreType ds = SubscribeToStreamUtil.parseURIEnum(LogicalDatastoreType.class,
@@ -259,7 +263,7 @@ public final class SubscribeToStreamUtil {
         final ListenerAdapter listener = Notificator.getListenerFor(streamName);
         Preconditions.checkNotNull(listener, "Listener doesn't exist : " + streamName);
 
-        listener.setTimer(start, stop);
+        listener.setQueryParams(start, stop, filter);
 
         SubscribeToStreamUtil.registration(ds, scope, listener, domDataBrokerHandler.get());
 
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ExpressionParserTest.java
new file mode 100644 (file)
index 0000000..61cd81d
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+public class ExpressionParserTest {
+
+    private Collection<File> xmls;
+
+    @Before
+    public void setup() throws Exception {
+        this.xmls = TestRestconfUtils.loadFiles("/notifications/xml/output/");
+    }
+
+    @Test
+    public void trueDownFilterTest() throws Exception {
+        final boolean parser =
+                parser("notification/data-changed-notification/data-change-event/data/toasterStatus='down'",
+                        "data_change_notification_toaster_status_DOWN.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseDownFilterTest() throws Exception {
+        final boolean parser =
+                parser("notification/data-changed-notification/data-change-event/data/toasterStatus='up'",
+                        "data_change_notification_toaster_status_DOWN.xml");
+        Assert.assertFalse(parser);
+    }
+
+    @Test
+    public void trueNumberEqualsFilterTest() throws Exception {
+        final boolean parser = parser(
+                "notification/data-changed-notification/data-change-event/data/toasterStatus=1",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseNumberEqualsFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus=0",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertFalse(parser);
+    }
+
+    @Test
+    public void trueNumberLessFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<2",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseNumberLessFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<0",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertFalse(parser);
+    }
+
+    @Test
+    public void trueNumberLessEqualsFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<=2",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseNumberLessEqualsFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus<=-1",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertFalse(parser);
+    }
+
+    @Test
+    public void trueNumberGreaterFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>0",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseNumberGreaterFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>5",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertFalse(parser);
+    }
+
+    @Test
+    public void trueNumberGreaterEqualsFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>=0",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertTrue(parser);
+    }
+
+    @Test
+    public void falseNumberGreaterEqualsFilterTest() throws Exception {
+        final boolean parser = parser("notification/data-changed-notification/data-change-event/data/toasterStatus>=5",
+                "data_change_notification_toaster_status_NUMBER.xml");
+        Assert.assertFalse(parser);
+    }
+
+    private boolean parser(final String filter, final String fileName) throws Exception {
+        File xml = null;
+        for (final File file : this.xmls) {
+            if (file.getName().equals(fileName)) {
+                xml = file;
+            }
+        }
+        final YangInstanceIdentifier path = Mockito.mock(YangInstanceIdentifier.class);
+        final ListenerAdapter listener = Notificator.createListener(path, "streamName", NotificationOutputType.JSON);
+        listener.setQueryParams(null, null, filter);
+        final Method m = listener.getClass().getDeclaredMethod("parseFilterParam", String.class);
+        m.setAccessible(true);
+
+        return (boolean) m.invoke(listener, readFile(xml));
+    }
+
+    private String readFile(final File xml) throws Exception {
+        final BufferedReader br = new BufferedReader(new FileReader(xml));
+        try {
+            final StringBuilder sb = new StringBuilder();
+            String line = br.readLine();
+
+            while (line != null) {
+                sb.append(line);
+                sb.append("\n");
+                line = br.readLine();
+            }
+            return sb.toString();
+        } finally {
+            br.close();
+        }
+    }
+
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_DOWN.xml b/restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_DOWN.xml
new file mode 100644 (file)
index 0000000..43babd2
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+    <eventTime>2016-11-10T04:45:31+01:00</eventTime>
+    <data-changed-notification xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote">
+        <data-change-event>
+            <path>/toaster:toaster/toaster:toasterStatus</path>
+            <operation>updated</operation>
+            <data>
+                <toasterStatus xmlns="http://netconfcentral.org/ns/toaster">down</toasterStatus>
+            </data>
+        </data-change-event>
+    </data-changed-notification>
+</notification>
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_NUMBER.xml b/restconf/sal-rest-connector/src/test/resources/notifications/xml/output/data_change_notification_toaster_status_NUMBER.xml
new file mode 100644 (file)
index 0000000..8a4a866
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+    <eventTime>2016-11-10T04:45:31+01:00</eventTime>
+    <data-changed-notification xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote">
+        <data-change-event>
+            <path>/toaster:toaster/toaster:toasterStatus</path>
+            <operation>updated</operation>
+            <data>
+                <toasterStatus xmlns="http://netconfcentral.org/ns/toaster">1</toasterStatus>
+            </data>
+        </data-change-event>
+    </data-changed-notification>
+</notification>
\ No newline at end of file