Filtering Dynamic Config Events

Change-Id: I8c6a250e5cb98d721b9708d905ec1bc8f549822c
diff --git a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
index f993550..802e682 100755
--- a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
+++ b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
@@ -39,6 +39,7 @@
     public static final String VAL_CHK = "\\@";
     public static final String KEY_CHK = "\\$";
     public static final String NM_CHK = "\\#";
+    public static final String EL_CHK = "\\|";
 
 
     private ResourceIdParser() {
diff --git a/apps/netconf/client/src/main/java/org/onosproject/netconf/client/EventData.java b/apps/netconf/client/src/main/java/org/onosproject/netconf/client/EventData.java
new file mode 100644
index 0000000..18c1db0
--- /dev/null
+++ b/apps/netconf/client/src/main/java/org/onosproject/netconf/client/EventData.java
@@ -0,0 +1,57 @@
+/*
+ * 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.netconf.client;
+
+import org.onosproject.config.DynamicConfigEvent;
+import org.onosproject.net.DeviceId;
+import org.onosproject.yang.model.ResourceId;
+
+
+/**
+ * Event details.
+ */
+public class EventData {
+
+    private DeviceId devId;
+    private ResourceId key;
+    private DynamicConfigEvent.Type type;
+
+    /**
+     * Creates an instance of EventData.
+     *
+     * @param devId device id
+     * @param key device key
+     * @param type event type
+     */
+    public EventData(DeviceId devId, ResourceId key, DynamicConfigEvent.Type type) {
+        devId = devId;
+        key = key;
+        type = type;
+    }
+
+    public DeviceId getDevId() {
+        return devId;
+    }
+
+    public ResourceId getKey() {
+        return key;
+    }
+
+    public DynamicConfigEvent.Type getType() {
+        return type;
+    }
+}
\ No newline at end of file
diff --git a/apps/netconf/client/src/main/java/org/onosproject/netconf/client/impl/NetconfActiveComponent.java b/apps/netconf/client/src/main/java/org/onosproject/netconf/client/impl/NetconfActiveComponent.java
index b1b2f33..acf97ba 100644
--- a/apps/netconf/client/src/main/java/org/onosproject/netconf/client/impl/NetconfActiveComponent.java
+++ b/apps/netconf/client/src/main/java/org/onosproject/netconf/client/impl/NetconfActiveComponent.java
@@ -25,6 +25,7 @@
 import org.onosproject.config.DynamicConfigEvent;
 import org.onosproject.config.DynamicConfigListener;
 import org.onosproject.config.DynamicConfigService;
+import org.onosproject.config.ResourceIdParser;
 import org.onosproject.config.Filter;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.DeviceId;
@@ -34,7 +35,6 @@
 import org.onosproject.netconf.client.NetconfTranslator.OperationType;
 import org.onosproject.yang.model.DataNode;
 import org.onosproject.yang.model.InnerNode;
-import org.onosproject.yang.model.KeyLeaf;
 import org.onosproject.yang.model.LeafNode;
 import org.onosproject.yang.model.ListKey;
 import org.onosproject.yang.model.NodeKey;
@@ -48,7 +48,9 @@
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Timer;
@@ -84,12 +86,19 @@
     private static final int MAX_BATCH_MS = 5000;
     private static final int MAX_IDLE_MS = 1000;
 
-    private ResourceId resId = new ResourceId.Builder()
+    private ResourceId defParent = new ResourceId.Builder()
             .addBranchPointSchema(DEVICES, DEVNMSPACE)
             .addBranchPointSchema(DEVICE, DEVNMSPACE)
-            .addKeyLeaf(DEVICE_ID, DEVNMSPACE, "netconf:172.16.5.11:22")
+            .addBranchPointSchema(DEVICE_ID, DEVNMSPACE)
             .build();
 
+    //TODO remove this hack after store ordering is fixed
+    private static final String EXCPATH = "root|devices#ne-l3vpn-api|" +
+            "device#ne-l3vpn-api$deviceid#ne-l3vpn-api#netconf:172.16.5.11:22|" +
+            "l3vpn#ne-l3vpn-api|l3vpncomm#ne-l3vpn-api|l3vpnInstances#ne-l3vpn-api|" +
+            "l3vpnInstance#ne-l3vpn-api$vrfName#ne-l3vpn-api#vrf2|l3vpnIfs#ne-l3vpn-api";
+    //end of hack
+
     @Activate
     protected void activate() {
         cfgService.addListener(this);
@@ -104,34 +113,9 @@
 
     @Override
     public boolean isRelevant(DynamicConfigEvent event) {
-        return checkIfRelevant(event.subject());
-    }
-
-    /**
-     * Checks if this event is relevant for active component.
-     *
-     * @param id resource id
-     * @return true if relevant
-     */
-    private boolean checkIfRelevant(ResourceId id) {
-        checkNotNull(id, "resource id can't be null");
-        List<NodeKey> nodeKeys = id.nodeKeys();
-        if (nodeKeys != null && !nodeKeys.isEmpty() && nodeKeys.size() == 2) {
-            NodeKey key = nodeKeys.get(0);
-            if (key.schemaId().name().equals(DEVICES)) {
-                key = nodeKeys.get(1);
-                if (key.schemaId().name().equals(DEVICE)) {
-                    ListKey listKey = (ListKey) key;
-                    List<KeyLeaf> keyLeaves = listKey.keyLeafs();
-                    if (keyLeaves != null && !keyLeaves.isEmpty()) {
-                        return keyLeaves.get(0).leafSchema().name()
-                                .equals(DEVICE_ID);
-                    }
-                }
-            }
-        }
-        log.debug("not a relevant event for netconf active component");
-        return false;
+        String resId = ResourceIdParser.parseResId(event.subject());
+        String refId = ResourceIdParser.parseResId(defParent);
+        return (resId.substring(0, (refId.length() - 1)).compareTo(refId) == 0);
     }
 
     public boolean isMaster(DeviceId deviceId) {
@@ -152,7 +136,7 @@
      * @param resourceId the resourceId of the root of the subtree to be edited
      * @return true if the update succeeds false otherwise
      */
-    private boolean configDelete(DataNode node, DeviceId deviceId, ResourceId resourceId) {
+    private boolean    configDelete(DataNode node, DeviceId deviceId, ResourceId resourceId) {
         return parseAndEdit(node, deviceId, resourceId, OperationType.DELETE);
     }
 
@@ -182,15 +166,17 @@
     private boolean parseAndEdit(DataNode node, DeviceId deviceId,
                                  ResourceId resourceId,
                                  NetconfTranslator.OperationType operationType) {
-
-        //add all low level nodes of devices
-        Iterator<Map.Entry<NodeKey, DataNode>> it = ((InnerNode) node)
-                .childNodes().entrySet().iterator();
+        //FIXME separate edit and delete, delete can proceed with a null node
         DefaultResourceData.Builder builder = DefaultResourceData.builder();
-        while (it.hasNext()) {
-            DataNode n = it.next().getValue();
-            if (!n.key().schemaId().name().equals("deviceid")) {
-                builder.addDataNode(n);
+        if (node != null) {
+            //add all low level nodes of devices
+            Iterator<Map.Entry<NodeKey, DataNode>> it = ((InnerNode) node)
+                    .childNodes().entrySet().iterator();
+            while (it.hasNext()) {
+                DataNode n = it.next().getValue();
+                if (!n.key().schemaId().name().equals("deviceid")) {
+                    builder.addDataNode(n);
+                }
             }
         }
         //add resouce id //TODO: check if it is correct
@@ -256,6 +242,81 @@
         }
     }
 
+    /**
+     * Retrieves device key from Resource id.
+     *
+     * @param path associated with the event
+     * @return the deviceId of the effected device
+     */
+    @Beta
+    public ResourceId getDeviceKey(ResourceId path) {
+        String resId = ResourceIdParser.parseResId(path);
+        String[] el = resId.split(ResourceIdParser.EL_CHK);
+        if (el.length < 2) {
+            throw new RuntimeException(new NetconfException("Invalid resource id, cannot apply"));
+        }
+        if (!el[1].contains((ResourceIdParser.KEY_SEP))) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        String[] keys = el[1].split(ResourceIdParser.KEY_CHK);
+        if (keys.length < 2) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        String[] parts = keys[1].split(ResourceIdParser.NM_CHK);
+        if (parts.length < 3) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        if (parts[2].split("\\:").length != 3) {
+            throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
+        }
+        return (new ResourceId.Builder()
+                .addBranchPointSchema(el[0].split(ResourceIdParser.NM_CHK)[0],
+                        el[0].split(ResourceIdParser.NM_CHK)[1])
+                .addBranchPointSchema(keys[0].split(ResourceIdParser.NM_CHK)[0],
+                        keys[0].split(ResourceIdParser.NM_CHK)[1])
+                .addKeyLeaf(parts[0], parts[1], parts[2])
+                .build());
+
+    }
+
+    /**
+     * Retrieves device id from Resource id.
+     *
+     * @param path associated with the event
+     * @return the deviceId of the effected device
+     */
+    @Beta
+    public DeviceId getDeviceId(ResourceId path) {
+        String resId = ResourceIdParser.parseResId(path);
+        String[] el = resId.split(ResourceIdParser.EL_CHK);
+        if (el.length < 2) {
+            throw new RuntimeException(new NetconfException("Invalid resource id, cannot apply"));
+        }
+        if (!el[2].contains((ResourceIdParser.KEY_SEP))) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        String[] keys = el[2].split(ResourceIdParser.KEY_CHK);
+        if (keys.length < 2) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        String[] parts = keys[1].split(ResourceIdParser.NM_CHK);
+        if (parts.length < 3) {
+            throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
+        }
+        String[] temp = parts[2].split("\\:");
+        String ip, port;
+        if (temp.length != 3) {
+            throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
+        }
+        ip = temp[1];
+        port = temp[2];
+        try {
+            return DeviceId.deviceId(new URI("netconf", ip + ":" + port, (String) null));
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException("Unable to build deviceID for device " + ip + ":" + port, ex);
+        }
+    }
+
     /* Accumulates events to allow processing after a desired number of events were accumulated.
     */
     private class InternalEventAccummulator extends AbstractAccumulator<DynamicConfigEvent> {
@@ -264,35 +325,72 @@
         }
         @Override
         public void processItems(List<DynamicConfigEvent> events) {
-            DynamicConfigEvent event = null;
+            Map<ResourceId, List<DynamicConfigEvent>> evtmap = new LinkedHashMap<>();
+            Boolean handleEx = false; //hack
+            ResourceId exId = null; //hack
             for (DynamicConfigEvent e : events) {
-                //checkIfRelevant already filters only the relevant events
-                event = e;
-                checkNotNull(event, "process:Event cannot be null");
-                switch (event.type()) {
-                    case NODE_ADDED:
-                    case NODE_UPDATED:
-                    case NODE_REPLACED:
-                        Filter filt = new Filter();
-                        DataNode node = cfgService.readNode(event.subject(), filt);
-                        DeviceId deviceId = getDeviceId(node);
-                        if (!isMaster(deviceId)) {
-                            log.info("NetConfListener: not master, ignoring config for {}", event.type());
-                            return;
-                        }
-                        initiateConnection(deviceId);
-                        configUpdate(node, deviceId, event.subject());
-                        break;
-                    case NODE_DELETED:
-                        log.info("NetConfListener: RXD DELETE EVT for {}", event.type());
-                        //configDelete(null, null, event.subject());
-                        break;
-                    case UNKNOWN_OPRN:
-                    default:
-                        log.warn("NetConfListener: unknown event: {}", event.type());
-                        break;
+                checkNotNull(e, "process:Event cannot be null");
+                ResourceId cur = e.subject();
+                //TODO remove this hack after store ordering is fixed
+                if (ResourceIdParser.parseResId(cur).compareTo(EXCPATH) == 0) {
+                    if (e.type() == DynamicConfigEvent.Type.NODE_ADDED) {
+                        log.info("Received an event for the xempted path ADD {}", e.type());
+                        handleEx = true;
+                        exId = cur;
+                    } else {
+                        log.warn("Received an event for the xempted path {}, NOT handled!!", e.type());
+                    }
+                } else { //actual code
+                    ResourceId key = getDeviceKey(e.subject());
+                    List<DynamicConfigEvent> el = evtmap.get(key);
+                    if (el == null) {
+                        el = new ArrayList<>();
+                    }
+                    el.add(e);
+                    evtmap.put(key, el);
                 }
             }
+            evtmap.forEach((k, v) -> {
+                log.info("Current deviceKey {}", k);
+                DeviceId curDevice = getDeviceId(k);
+                if (!isMaster(curDevice)) {
+                    log.info("NetConfListener: not master, ignoring config for device {}", k);
+                    return;
+                }
+                initiateConnection(curDevice);
+                for (DynamicConfigEvent curEvt : v) {
+                    switch (curEvt.type()) {
+                        case NODE_ADDED:
+                        case NODE_UPDATED:
+                        case NODE_REPLACED:
+                            Filter filt = new Filter();
+                            DataNode node = cfgService.readNode(k, filt);
+                            configUpdate(node, curDevice, k);
+                            break;
+                        case NODE_DELETED:
+                            log.info("NetConfListener: RXD DELETE EVT for {}", k);
+                            configDelete(null, curDevice, k); //TODO curEvt.subject());
+                            break;
+                        case UNKNOWN_OPRN:
+                        default:
+                            log.warn("NetConfListener: unknown event: {}", curEvt.type());
+                            break;
+                    }
+                }
+            });
+            //TODO remove this hack after store ordering is fixed
+            if (handleEx) {
+                log.info("Handling exception path {}", exId);
+                DeviceId exDevice = getDeviceId(exId);
+                if (!isMaster(exDevice)) {
+                    log.info("NetConfListener: not master, ignoring config for expath {}", exId);
+                    return;
+                }
+                initiateConnection(exDevice);
+                Filter filt = new Filter();
+                DataNode exnode = cfgService.readNode(exId, filt);
+                configUpdate(exnode, exDevice, exId);
+            } //end of hack
         }
     }
 }
\ No newline at end of file