RESTCONF notification changes

* Added a callback function mechanism to the RESTCONF notification
  listener. Applications can register their callbacks to a listener.
  This is to allow multiple SB providers share one generic listener.
  while still being able to process app-specific events.
* Created a default RESTCONF notification listener implementation.
* Refactored TE provider code to allow TE-topology and TE-tunnel share one
  RESTCONF event listener

Change-Id: I26dc4972683fcda3eefacde131353312809aa95e
diff --git a/providers/ietfte/pom.xml b/providers/ietfte/pom.xml
index 1931d2f..8661bdf 100644
--- a/providers/ietfte/pom.xml
+++ b/providers/ietfte/pom.xml
@@ -28,18 +28,6 @@
 
     <artifactId>onos-ietfte-providers</artifactId>
     <packaging>pom</packaging>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
     <modules>
         <module>topology</module>
         <module>tunnel</module>
diff --git a/providers/ietfte/topology/src/main/java/org/onosproject/provider/te/topology/TeTopologyRestconfProvider.java b/providers/ietfte/topology/src/main/java/org/onosproject/provider/te/topology/TeTopologyRestconfProvider.java
index c75f01d..a377e51 100644
--- a/providers/ietfte/topology/src/main/java/org/onosproject/provider/te/topology/TeTopologyRestconfProvider.java
+++ b/providers/ietfte/topology/src/main/java/org/onosproject/provider/te/topology/TeTopologyRestconfProvider.java
@@ -34,9 +34,10 @@
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.protocol.rest.RestSBDevice;
-import org.onosproject.protocol.restconf.RestConfNotificationEventListener;
 import org.onosproject.protocol.restconf.RestConfSBController;
 import org.onosproject.provider.te.utils.DefaultJsonCodec;
+import org.onosproject.provider.te.utils.RestconfNotificationEventProcessor;
+import org.onosproject.provider.te.utils.TeTopologyRestconfEventListener;
 import org.onosproject.provider.te.utils.YangCompositeEncodingImpl;
 import org.onosproject.tetopology.management.api.TeTopologyProvider;
 import org.onosproject.tetopology.management.api.TeTopologyProviderRegistry;
@@ -78,6 +79,8 @@
 import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_ADDED;
 import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_UPDATED;
 import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+import static org.onosproject.provider.te.utils.TeTopologyRestconfEventType.TE_TOPOLOGY_LINK_NOTIFICATION;
+import static org.onosproject.provider.te.utils.TeTopologyRestconfEventType.TE_TOPOLOGY_NODE_NOTIFICATION;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -93,8 +96,6 @@
     private static final String IETF_NETWORK_URI = "ietf-network:networks";
     private static final String IETF_NETWORKS_PREFIX =
             "{\"ietf-network:networks\":";
-    private static final String TE_NOTIFICATION_PREFIX =
-            "{\"ietf-te-topology:ietf-te-topology\":";
     private static final String TE_LINK_EVENT_PREFIX =
             "{\"ietf-te-topology:te-link-event\":";
     private static final String TE_NODE_EVENT_PREFIX =
@@ -290,11 +291,27 @@
 //            topologyProviderService.networkUpdated(network);
 //        }
 
-        RestConfNotificationEventListener<String> callBackListener =
-                new InternalRestconfNotificationEventListener();
-        restconfClient.enableNotifications(deviceId, IETF_NOTIFICATION_URI,
-                                           "application/json",
-                                           callBackListener);
+        subscribeRestconfNotification(deviceId);
+    }
+
+    private void subscribeRestconfNotification(DeviceId deviceId) {
+
+        TeTopologyRestconfEventListener listener =
+                new TeTopologyRestconfEventListener();
+
+        listener.addCallbackFunction(TE_TOPOLOGY_LINK_NOTIFICATION,
+                                     new InternalLinkEventProcessor());
+        listener.addCallbackFunction(TE_TOPOLOGY_NODE_NOTIFICATION,
+                                     new InternalNodeEventProcessor());
+
+        if (!restconfClient.isNotificationEnabled(deviceId)) {
+            restconfClient.enableNotifications(deviceId,
+                                               IETF_NOTIFICATION_URI,
+                                               "application/json",
+                                               listener);
+        } else {
+            restconfClient.addNotificationListener(deviceId, listener);
+        }
     }
 
     private String removePrefixTagFromJson(String jsonString, String prefixTag) {
@@ -304,42 +321,15 @@
         return jsonString;
     }
 
-    private class InternalNetworkConfigListener implements NetworkConfigListener {
+    private class InternalLinkEventProcessor implements
+            RestconfNotificationEventProcessor<String> {
 
         @Override
-        public void event(NetworkConfigEvent event) {
-            executor.execute(TeTopologyRestconfProvider.this::connectDevices);
-        }
-
-        @Override
-        public boolean isRelevant(NetworkConfigEvent event) {
-            return event.configClass().equals(RestconfServerConfig.class) &&
-                    (event.type() == CONFIG_ADDED ||
-                            event.type() == CONFIG_UPDATED);
-        }
-    }
-
-    private class InternalRestconfNotificationEventListener implements
-            RestConfNotificationEventListener<String> {
-        @Override
-        public void handleNotificationEvent(DeviceId deviceId,
-                                            String eventJsonString) {
-            log.debug("New notification: {} for device: {}",
-                      eventJsonString, deviceId.toString());
-
-            String teEventString = removePrefixTagFromJson(eventJsonString,
-                                                           TE_NOTIFICATION_PREFIX);
-            if (teEventString.startsWith(TE_LINK_EVENT_PREFIX)) {
-                String linkString = removePrefixTagFromJson(teEventString,
-                                                            TE_LINK_EVENT_PREFIX);
-                log.debug("link event={}", linkString);
-                handleRestconfLinkNotification(linkString);
-            } else if (teEventString.startsWith(TE_NODE_EVENT_PREFIX)) {
-                String nodeString = removePrefixTagFromJson(teEventString,
-                                                            TE_NODE_EVENT_PREFIX);
-                log.debug("node event={}", nodeString);
-                handleRestconfNodeNotification(nodeString);
-            }
+        public void processEventPayload(String payload) {
+            String linkString = removePrefixTagFromJson(payload,
+                                                        TE_LINK_EVENT_PREFIX);
+            log.debug("link event={}", linkString);
+            handleRestconfLinkNotification(linkString);
         }
 
         private void handleRestconfLinkNotification(String linkString) {
@@ -377,29 +367,16 @@
 
             topologyProviderService.linkUpdated(linkKey, networkLink);
         }
+    }
 
-        private IetfTeTopologyEvent convertJson2IetfTeTopologyEvent(String uriString,
-                                                                    String jsonBody) {
+    private class InternalNodeEventProcessor implements
+            RestconfNotificationEventProcessor<String> {
 
-            YangCompositeEncodingImpl yce =
-                    new YangCompositeEncodingImpl(YangResourceIdentifierType.URI,
-                                                  uriString,
-                                                  jsonBody);
-            Object yo = codecHandler.decode(yce,
-                                            YangProtocolEncodingFormat.JSON,
-                                            YmsOperationType.NOTIFICATION);
-
-            if (yo == null) {
-                log.error("YMS decoder error");
-                return null;
-            }
-
-            if (!(yo instanceof IetfTeTopologyEvent)) {
-                log.error("ERROR: YO is not IetfTeTopologyEvent");
-                return null;
-            }
-
-            return (IetfTeTopologyEvent) yo;
+        @Override
+        public void processEventPayload(String payload) {
+            String nodeString = removePrefixTagFromJson(payload, TE_NODE_EVENT_PREFIX);
+            log.debug("node event={}", nodeString);
+            handleRestconfNodeNotification(nodeString);
         }
 
         private void handleRestconfNodeNotification(String nodeString) {
@@ -437,4 +414,43 @@
             topologyProviderService.nodeUpdated(nodeKey, networkNode);
         }
     }
+
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            executor.execute(TeTopologyRestconfProvider.this::connectDevices);
+        }
+
+        @Override
+        public boolean isRelevant(NetworkConfigEvent event) {
+            return event.configClass().equals(RestconfServerConfig.class) &&
+                    (event.type() == CONFIG_ADDED ||
+                            event.type() == CONFIG_UPDATED);
+        }
+    }
+
+    private IetfTeTopologyEvent convertJson2IetfTeTopologyEvent(String uriString,
+                                                                String jsonBody) {
+
+        YangCompositeEncodingImpl yce =
+                new YangCompositeEncodingImpl(YangResourceIdentifierType.URI,
+                                              uriString,
+                                              jsonBody);
+        Object yo = codecHandler.decode(yce,
+                                        YangProtocolEncodingFormat.JSON,
+                                        YmsOperationType.NOTIFICATION);
+
+        if (yo == null) {
+            log.error("YMS decoder error");
+            return null;
+        }
+
+        if (!(yo instanceof IetfTeTopologyEvent)) {
+            log.error("ERROR: YO is not IetfTeTopologyEvent");
+            return null;
+        }
+
+        return (IetfTeTopologyEvent) yo;
+    }
 }
diff --git a/providers/ietfte/tunnel/src/main/java/org/onosproject/provider/te/tunnel/TeTunnelRestconfProvider.java b/providers/ietfte/tunnel/src/main/java/org/onosproject/provider/te/tunnel/TeTunnelRestconfProvider.java
index 3a081ce..1d9468d 100644
--- a/providers/ietfte/tunnel/src/main/java/org/onosproject/provider/te/tunnel/TeTunnelRestconfProvider.java
+++ b/providers/ietfte/tunnel/src/main/java/org/onosproject/provider/te/tunnel/TeTunnelRestconfProvider.java
@@ -33,8 +33,8 @@
 import org.onosproject.net.Path;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
-import org.onosproject.protocol.restconf.RestConfNotificationEventListener;
 import org.onosproject.protocol.restconf.RestConfSBController;
+import org.onosproject.protocol.restconf.RestconfNotificationEventListener;
 import org.onosproject.provider.te.utils.DefaultJsonCodec;
 import org.onosproject.provider.te.utils.YangCompositeEncodingImpl;
 import org.onosproject.tetopology.management.api.TeTopology;
@@ -89,6 +89,7 @@
     private static final int DEFAULT_INDEX = 1;
     private static final String TUNNELS = "tunnels";
     private static final String TUNNELS_URL = IETF + ":" + TE + "/" + TUNNELS;
+    private static final String IETF_NOTIFICATION_URI = "netconf";
     private static final String MEDIA_TYPE_JSON = "json";
 
     private static final String SHOULD_IN_ONE = "Tunnel should be setup in one topo";
@@ -96,8 +97,8 @@
     private static final String RESTCONF_ROOT = "/onos/restconf";
     private static final String TE_TUNNEL_KEY = "TeTunnelKey";
 
-    private final RestConfNotificationEventListener listener =
-            new InternalTunnelNotificationListener();
+    //private final RestconfNotificationEventListener listener =
+    //        new InternalTunnelNotificationListener();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected RestConfSBController controller;
@@ -165,9 +166,14 @@
     private void subscribe() {
         for (DeviceId deviceId : controller.getDevices().keySet()) {
             try {
-                controller.enableNotifications(deviceId, TUNNELS_URL,
-                                               MEDIA_TYPE_JSON,
-                                               listener);
+                if (!controller.isNotificationEnabled(deviceId)) {
+                    controller.enableNotifications(deviceId, IETF_NOTIFICATION_URI,
+                                                   "application/json",
+                                                   new InternalTunnelNotificationListener());
+                } else {
+                    controller.addNotificationListener(deviceId,
+                                                       new InternalTunnelNotificationListener());
+                }
             } catch (Exception e) {
                 log.error("Failed to subscribe for {} : {}", deviceId,
                           e.getMessage());
@@ -179,7 +185,8 @@
         controller.getDevices()
                 .keySet()
                 .forEach(deviceId -> controller
-                        .removeNotificationListener(deviceId));
+                        .removeNotificationListener(deviceId,
+                                                    new InternalTunnelNotificationListener()));
     }
 
     @Override
@@ -343,8 +350,9 @@
         return deviceId;
     }
 
+
     private class InternalTunnelNotificationListener implements
-            RestConfNotificationEventListener {
+            RestconfNotificationEventListener {
 
         @Override
         public void handleNotificationEvent(DeviceId deviceId, Object eventJsonString) {
diff --git a/providers/ietfte/utils/BUCK b/providers/ietfte/utils/BUCK
index 7817d31..0964039 100644
--- a/providers/ietfte/utils/BUCK
+++ b/providers/ietfte/utils/BUCK
@@ -2,6 +2,7 @@
     '//lib:CORE_DEPS',
     '//apps/yms/api:onos-apps-yms-api',
     '//protocols/restconf/server/utils:onos-protocols-restconf-server-utils',
+    '//protocols/restconf/client/api:onos-protocols-restconf-client-api',
 ]
 
 TEST_DEPS = [
diff --git a/providers/ietfte/utils/pom.xml b/providers/ietfte/utils/pom.xml
index 35c727e..3174a8b 100644
--- a/providers/ietfte/utils/pom.xml
+++ b/providers/ietfte/utils/pom.xml
@@ -37,10 +37,13 @@
         </dependency>
         <dependency>
             <groupId>org.onosproject</groupId>
+            <artifactId>onos-restconf-client-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
             <artifactId>onos-restconf-server-utils</artifactId>
             <version>${project.version}</version>
         </dependency>
     </dependencies>
-
-
-</project>
\ No newline at end of file
+</project>
diff --git a/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/RestconfNotificationEventProcessor.java b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/RestconfNotificationEventProcessor.java
new file mode 100644
index 0000000..7f6ea2e
--- /dev/null
+++ b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/RestconfNotificationEventProcessor.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.provider.te.utils;
+
+/**
+ * Abstraction of the RESTCONF notification callback function. The callback
+ * is invoked by the notification listener when it receives an event.
+ */
+public interface RestconfNotificationEventProcessor<T> {
+    void processEventPayload(T event);
+}
diff --git a/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventListener.java b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventListener.java
new file mode 100644
index 0000000..668fec6
--- /dev/null
+++ b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventListener.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.provider.te.utils;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.protocol.restconf.RestconfNotificationEventListener;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the RESTCONF notification event
+ * listener for TE Topology.
+ */
+public class TeTopologyRestconfEventListener implements
+        RestconfNotificationEventListener<String> {
+    private static final String TE_TOPOLOGY_NOTIFICATION_PREFIX =
+            "{\"ietf-te-topology:ietf-te-topology\":";
+    private static final String TE_LINK_EVENT_PREFIX =
+            "{\"ietf-te-topology:te-link-event\":";
+    private static final String TE_NODE_EVENT_PREFIX =
+            "{\"ietf-te-topology:te-node-event\":";
+
+    private final Logger log = getLogger(getClass());
+
+    private Map<TeTopologyRestconfEventType, RestconfNotificationEventProcessor>
+            eventCallbackFunctionMap = new ConcurrentHashMap<>();
+
+    @Override
+    public void handleNotificationEvent(DeviceId deviceId,
+                                        String eventJsonString) {
+        log.debug("New notification: {} for device: {}",
+                  eventJsonString, deviceId.toString());
+
+        if (!eventJsonString.startsWith(TE_TOPOLOGY_NOTIFICATION_PREFIX)) {
+            // This is not a TE topology event.
+            return;
+        }
+
+        String teEventString = removePrefixTagFromJson(eventJsonString,
+                                                       TE_TOPOLOGY_NOTIFICATION_PREFIX);
+
+        TeTopologyRestconfEventType eventType = getEventType(teEventString);
+
+        if (eventType == TeTopologyRestconfEventType.TE_UNKNOWN_EVENT) {
+            log.error("handleNotificationEvent: unknown event: {}", eventJsonString);
+            return;
+        }
+
+        RestconfNotificationEventProcessor eventProcessor =
+                eventCallbackFunctionMap.get(eventType);
+
+        if (eventProcessor != null) {
+            eventProcessor.processEventPayload(teEventString);
+        } else {
+            log.info("Event callback not installed for event type: {}", eventType);
+        }
+    }
+
+    /**
+     * Registers an notification event callback function which is called by
+     * the listener when it receives an event.
+     *
+     * @param eventType      notification event type corresponding to the
+     *                       callback function
+     * @param eventProcessor callback function
+     */
+    public void addCallbackFunction(TeTopologyRestconfEventType eventType,
+                                    RestconfNotificationEventProcessor eventProcessor) {
+        if (eventCallbackFunctionMap.containsKey(eventType)) {
+            removeCallbackFunction(eventType);
+        }
+
+        eventCallbackFunctionMap.put(eventType, eventProcessor);
+    }
+
+    /**
+     * Removes the callback function associated with the given event type.
+     *
+     * @param eventType notification event type
+     */
+    public void removeCallbackFunction(TeTopologyRestconfEventType eventType) {
+        eventCallbackFunctionMap.remove(eventType);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        TeTopologyRestconfEventListener that = (TeTopologyRestconfEventListener) o;
+
+        return eventCallbackFunctionMap != null ?
+                eventCallbackFunctionMap.equals(that.eventCallbackFunctionMap) :
+                that.eventCallbackFunctionMap == null;
+    }
+
+    @Override
+    public int hashCode() {
+        return eventCallbackFunctionMap != null ? eventCallbackFunctionMap.hashCode() : 0;
+    }
+
+    private String removePrefixTagFromJson(String jsonString, String prefixTag) {
+        if (jsonString.startsWith(prefixTag)) {
+            return jsonString.substring(prefixTag.length(), jsonString.length() - 1);
+        }
+        return jsonString;
+    }
+
+    private TeTopologyRestconfEventType getEventType(String teEventString) {
+        if (teEventString.startsWith(TE_LINK_EVENT_PREFIX)) {
+            return TeTopologyRestconfEventType.TE_TOPOLOGY_LINK_NOTIFICATION;
+        }
+
+        if (teEventString.startsWith(TE_NODE_EVENT_PREFIX)) {
+            return TeTopologyRestconfEventType.TE_TOPOLOGY_NODE_NOTIFICATION;
+        }
+
+        return TeTopologyRestconfEventType.TE_UNKNOWN_EVENT;
+    }
+}
+
diff --git a/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventType.java b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventType.java
new file mode 100644
index 0000000..6806023
--- /dev/null
+++ b/providers/ietfte/utils/src/main/java/org/onosproject/provider/te/utils/TeTopologyRestconfEventType.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.provider.te.utils;
+
+/**
+ * Types of TE Topology RESTCONF notification events.
+ */
+public enum TeTopologyRestconfEventType {
+    /**
+     * TE topology link event type.
+     */
+    TE_TOPOLOGY_LINK_NOTIFICATION,
+
+    /**
+     * TE topology node event type.
+     */
+    TE_TOPOLOGY_NODE_NOTIFICATION,
+
+    /**
+     * Unknown or unsupported event type.
+     */
+    TE_UNKNOWN_EVENT,
+}