ONOS-5960 Dynamic Config Svc: Datachange Notifications

Change-Id: I46b999530d985b5f9d2bf611f685c1397353997b
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
index 8b6a305..9d1b387 100755
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
@@ -50,7 +50,12 @@
         /**
          * Signifies that dynamic configuration instance was removed.
          */
-        NODE_DELETED
+        NODE_DELETED,
+
+        /**
+         * Signifies an unknown and hence invalid store opeartion.
+         */
+        UNKNOWN_OPRN
     }
 
     /**
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigStore.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigStore.java
index 9ea34f2..25ddf55 100644
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigStore.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigStore.java
@@ -20,6 +20,7 @@
 import org.onosproject.yang.model.ResourceId;
 import org.onosproject.store.Store;
 
+import java.util.Collection;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -136,4 +137,32 @@
      * {@code FailedException} if the delete request failed
      */
     CompletableFuture<Boolean> deleteNodeRecursive(ResourceId path);
+
+    /**
+     * Adds a listener to be notified when a leaf or subtree rooted at the
+     * specified path is modified.
+     *
+     * @param path data structure with absolute path to the node being listened to
+     * @param listener listener to be notified
+     * @throws FailedException if the listener could not be added
+     */
+    void addConfigListener(ResourceId path, DynamicConfigListener listener);
+
+    /**
+     * Removes a previously added listener.
+     *
+     * @param path data structure with absolute path to the node being listened to
+     * @param listener listener to unregister
+     * @throws FailedException if the listener could not be removed
+     */
+    void removeConfigListener(ResourceId path, DynamicConfigListener listener);
+
+    /**
+     * Returns a collection of previously added listeners.
+     *
+     * @param path data structure with absolute path to the node being listened to
+     * @return  a collection of previously added listeners
+     */
+    Collection<? extends DynamicConfigListener> getConfigListener(ResourceId path);
+    //DynamicConfigListener getConfigListener(ResourceId path);
 }
\ No newline at end of file
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 7b7f5bd..23c107d 100755
--- a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
+++ b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
@@ -16,35 +16,199 @@
 package org.onosproject.config;
 
 import java.util.Iterator;
+
+import java.util.List;
+
+import org.onosproject.yang.model.KeyLeaf;
+import org.onosproject.yang.model.LeafListKey;
+import org.onosproject.yang.model.ListKey;
 import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
 
 /**
- * Representation of an entity which identifies a resource in the logical tree
- * data store. It is a list of node keys to identify the branch point
- * hierarchy to reach a resource in the instance tree.
+ * Utilities to work on the ResourceId.
  */
 
 public final class ResourceIdParser {
 
+    public static final String ROOT = "root";
+    public static final String NM_SEP = "#";
+    public static final String VAL_SEP = "@";
+    public static final String KEY_SEP = "$";
+    public static final String EL_SEP = ".";
+
+
+
     private ResourceIdParser() {
 
     }
-    public static String asString(ResourceId path) {
+
+    public static ResourceId getParent(ResourceId path) {
+        int last = path.nodeKeys().size();
+        path.nodeKeys().remove(last - 1);
+        return path;
+    }
+
+    public static NodeKey getInstanceKey(ResourceId path) {
+        int last = path.nodeKeys().size();
+        NodeKey ret = path.nodeKeys().get(last - 1);
+        if (ret instanceof NodeKey) {
+            return ret;
+        } else {
+            return null;
+        }
+    }
+
+    public static NodeKey getMultiInstanceKey(ResourceId path) {
+        int last = path.nodeKeys().size();
+        NodeKey ret = path.nodeKeys().get(last - 1);
+        if (ret instanceof ListKey) {
+            return ret;
+        } else {
+            return null;
+        }
+    }
+
+    public static String appendMultiInstKey(String path, String leaf) {
+        return (path + leaf.substring(leaf.indexOf(KEY_SEP)));
+    }
+
+    public static String appendKeyLeaf(String path, String key) {
+        return (path + EL_SEP + key);
+    }
+
+    //DONE
+    public static String appendKeyLeaf(String path, KeyLeaf key) {
         StringBuilder bldr = new StringBuilder();
-            bldr.append("root.");
-            Iterator<NodeKey> iter = path.nodeKeys().iterator();
-            NodeKey key;
-            while (iter.hasNext()) {
-                key = iter.next();
-                //log.info("Iter: key {}", key.toString());
-                bldr.append(key.schemaId().name());
-                bldr.append("#");
-                bldr.append(key.schemaId().namespace());
-                if (iter.hasNext()) {
-                    bldr.append(".");
-                }
-            }
+        bldr.append(key.leafSchema().name());
+        bldr.append(NM_SEP);
+        bldr.append(key.leafSchema().namespace());
+        bldr.append(NM_SEP);
+        bldr.append(key.leafValue().toString());
+        return (path + EL_SEP + bldr.toString());
+    }
+
+    public static String appendNodeKey(String path, NodeKey key) {
+        return (path + EL_SEP + key.schemaId().name() + NM_SEP + key.schemaId().namespace());
+    }
+
+    public static String appendNodeKey(String path, String name, String nmSpc) {
+        return (path + EL_SEP + name + NM_SEP + nmSpc);
+    }
+
+    public static String appendLeafList(String path, LeafListKey key) {
+        return (path + NM_SEP + key.asString());
+    }
+
+    public static String appendLeafList(String path, String val) {
+        return (path + NM_SEP + val);
+    }
+
+    public static String appendKeyList(String path, ListKey key) {
+        StringBuilder bldr = new StringBuilder();
+        for (KeyLeaf keyLeaf : key.keyLeafs()) {
+            bldr.append(KEY_SEP);
+            bldr.append(keyLeaf.leafSchema().name());
+            bldr.append(NM_SEP);
+            bldr.append(keyLeaf.leafSchema().namespace());
+            bldr.append(NM_SEP);
+            bldr.append(keyLeaf.leafValue().toString());
+        }
+        return (path + bldr.toString());
+    }
+
+    public static String parseNodeKey(NodeKey key) {
+        if (key == null) {
+            return null;
+        }
+        StringBuilder bldr = new StringBuilder();
+        if (key instanceof LeafListKey) {
+            parseLeafList((LeafListKey) key, bldr);
+        } else if (key instanceof ListKey) {
+            parseKeyList((ListKey) key, bldr);
+        } else {
+            parseNodeKey(key, bldr);
+        }
         return bldr.toString();
     }
+
+    public static String parseResId(ResourceId path) {
+        StringBuilder bldr = new StringBuilder();
+        bldr.append(ROOT);
+        if (path == null) {
+            return bldr.toString();
+        }
+        List<NodeKey> nodeKeyList = path.nodeKeys();
+        for (NodeKey key : nodeKeyList) {
+            bldr.append(EL_SEP);
+            if (key instanceof LeafListKey) {
+                parseLeafList((LeafListKey) key, bldr);
+            } else if (key instanceof ListKey) {
+                parseKeyList((ListKey) key, bldr);
+            } else {
+                parseNodeKey(key, bldr);
+            }
+        }
+        return bldr.toString();
+    }
+
+    private static void parseLeafList(LeafListKey key, StringBuilder bldr) {
+        bldr.append(key.schemaId().name());
+        bldr.append(NM_SEP);
+        bldr.append(key.schemaId().namespace());
+        bldr.append(NM_SEP);
+        bldr.append(key.asString());
+    }
+
+    private static void parseKeyList(ListKey key, StringBuilder bldr) {
+        bldr.append(key.schemaId().name());
+        bldr.append(NM_SEP);
+        bldr.append(key.schemaId().namespace());
+        bldr.append(NM_SEP);
+        Iterator<KeyLeaf> iter = key.keyLeafs().iterator();
+        KeyLeaf next;
+        while (iter.hasNext()) {
+            next = iter.next();
+            bldr.append(KEY_SEP);
+            bldr.append(next.leafSchema().name());
+            bldr.append(NM_SEP);
+            bldr.append(next.leafSchema().namespace());
+            bldr.append(NM_SEP);
+            bldr.append(next.leafValue().toString());
+        }
+    }
+
+    private static void parseNodeKey(NodeKey key, StringBuilder bldr) {
+        bldr.append(key.schemaId().name());
+        bldr.append(NM_SEP);
+        bldr.append(key.schemaId().namespace());
+    }
+
+    public static ResourceId getResId(List<String> dpath) {
+        ResourceId.Builder resBldr = new ResourceId.Builder();
+        Iterator<String> itr = dpath.iterator();
+        itr.next();
+        while (itr.hasNext()) {
+            String name = itr.next();
+            if (name.contains(VAL_SEP)) {
+                resBldr.addLeafListBranchPoint(name.substring(0, name.indexOf(NM_SEP)),
+                        name.substring(name.indexOf(NM_SEP) + 1, name.indexOf(VAL_SEP)),
+                        name.substring(name.indexOf(VAL_SEP) + 1));
+            } else if (name.contains(KEY_SEP)) {
+                resBldr.addBranchPointSchema(name.substring(0, name.indexOf(NM_SEP)),
+                        name.substring(name.indexOf(NM_SEP) + 1, name.indexOf(KEY_SEP)));
+                String[] keys = name.split(KEY_SEP);
+                for (int i = 1; i < keys.length; i++) {
+                    String key = keys[i];
+                    resBldr.addKeyLeaf(key.substring(0, key.indexOf(NM_SEP)),
+                            key.substring(key.indexOf(NM_SEP) + 1, key.lastIndexOf(NM_SEP)),
+                            key.substring(name.lastIndexOf(NM_SEP) + 1));
+                }
+            } else {
+                resBldr.addBranchPointSchema(name.substring(0, name.indexOf(NM_SEP)),
+                        name.substring(name.indexOf(NM_SEP) + 1));
+            }
+        }
+        return resBldr.build();
+    }
 }
\ No newline at end of file
diff --git a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
index 741ae97..1c63258 100644
--- a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
+++ b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
@@ -24,38 +24,50 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.config.DynamicConfigEvent;
+import org.onosproject.config.DynamicConfigListener;
 import org.onosproject.config.DynamicConfigStore;
 import org.onosproject.config.DynamicConfigStoreDelegate;
+import org.onosproject.config.ResourceIdParser;
 import org.onosproject.config.FailedException;
 import org.onosproject.config.Filter;
-import org.onosproject.config.ResourceIdParser;
-import org.onosproject.store.service.IllegalDocumentModificationException;
-import org.onosproject.store.service.NoSuchDocumentPathException;
-import org.onosproject.yang.model.DataNode;
-import org.onosproject.yang.model.InnerNode;
-import org.onosproject.yang.model.LeafNode;
-import org.onosproject.yang.model.NodeKey;
-import org.onosproject.yang.model.ResourceId;
-import org.onosproject.yang.model.SchemaId;
+//import org.onosproject.config.cfgreceiver.CfgReceiver;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.AsyncDocumentTree;
 import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.ConsistentMapException;
+import org.onosproject.store.service.ConsistentMultimap;
 import org.onosproject.store.service.DocumentPath;
 import org.onosproject.store.service.DocumentTreeEvent;
 import org.onosproject.store.service.DocumentTreeListener;
+import org.onosproject.store.service.IllegalDocumentModificationException;
 import org.onosproject.store.service.MapEvent;
 import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.NoSuchDocumentPathException;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.Versioned;
+import org.onosproject.yang.model.DataNode;
+import org.onosproject.yang.model.InnerNode;
+import org.onosproject.yang.model.KeyLeaf;
+import org.onosproject.yang.model.LeafListKey;
+import org.onosproject.yang.model.LeafNode;
+import org.onosproject.yang.model.ListKey;
+import org.onosproject.yang.model.NodeKey;
+import org.onosproject.yang.model.ResourceId;
+import org.onosproject.yang.model.SchemaId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
+import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
+import static org.onosproject.config.DynamicConfigEvent.Type.NODE_ADDED;
+import static org.onosproject.config.DynamicConfigEvent.Type.NODE_UPDATED;
+import static org.onosproject.config.DynamicConfigEvent.Type.NODE_DELETED;
+import static org.onosproject.config.DynamicConfigEvent.Type.UNKNOWN_OPRN;
+
 /**
  * Implementation of the dynamic config store.
  */
@@ -70,6 +82,7 @@
     protected StorageService storageService;
     private AsyncDocumentTree<DataNode.Type> keystore;
     private ConsistentMap<String, LeafNode> objectStore;
+    private ConsistentMultimap<String, DynamicConfigListener> lstnrStore;
     private final DocumentTreeListener<DataNode.Type> klistener = new InternalDocTreeListener();
     private final MapEventListener<String, LeafNode> olistener = new InternalMapListener();
 
@@ -77,7 +90,7 @@
     public void activateStore() {
         KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
                 .register(KryoNamespaces.BASIC)
-                .register(String.class)
+                //.register(String.class)
                 .register(java.lang.Class.class)
                 .register(DataNode.Type.class)
                 .register(LeafNode.class)
@@ -86,6 +99,7 @@
                 .register(NodeKey.class)
                 .register(SchemaId.class)
                 .register(java.util.LinkedHashMap.class);
+                //.register(CfgReceiver.InternalDynamicConfigListener.class);
         keystore = storageService.<DataNode.Type>documentTreeBuilder()
                 .withSerializer(Serializer.using(kryoBuilder.build()))
                 .withName("config-key-store")
@@ -96,6 +110,11 @@
                 .withName("config-object-store")
                 .withRelaxedReadConsistency()
                 .build();
+        lstnrStore = storageService.<String, DynamicConfigListener>consistentMultimapBuilder()
+                .withSerializer(Serializer.using(kryoBuilder.build()))
+                .withName("config-listener-registry")
+                .withRelaxedReadConsistency()
+                .build();
         keystore.addListener(klistener);
         objectStore.addListener(olistener);
         log.info("DyanmicConfig Store Active");
@@ -115,86 +134,214 @@
     }
 
     @Override
+    public CompletableFuture<Boolean>
+    addRecursive(ResourceId complete, DataNode node) {
+        CompletableFuture<Boolean> eventFuture = CompletableFuture.completedFuture(true);
+        ResourceId path = ResourceIdParser.getParent(complete);
+        String spath = ResourceIdParser.parseResId(path);
+        if (spath == null) {
+            throw new FailedException("Invalid RsourceId, cannot create Node");
+        }
+        /*if (keystore.get(DocumentPath.from(spath)).join() == null) {
+            ////TODO is recursively creating missing parents required?
+            throw new FailedException("Some of the parents in the path " +
+                    "are not present, creation not supported currently");
+        }*/
+        spath = ResourceIdParser.appendNodeKey(spath, node.key());
+        parseNode(spath, node);
+        return eventFuture;
+    }
+
+    private void parseNode(String path, DataNode node) {
+        if (keystore.get(DocumentPath.from(path)).join() != null) {
+            throw new FailedException("Requested node already present in the" +
+                    " store, please use an update method");
+        }
+        if (node.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
+            addLeaf(path, (LeafNode) node);
+        } else if (node.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE) {
+            path = ResourceIdParser.appendLeafList(path, (LeafListKey) node.key());
+            if (keystore.get(DocumentPath.from(path)).join() != null) {
+                throw new FailedException("Requested node already present in the" +
+                        " store, please use an update method");
+            }
+            addLeaf(path, (LeafNode) node);
+        } else if (node.type() == DataNode.Type.SINGLE_INSTANCE_NODE) {
+            traverseInner(path, (InnerNode) node);
+        } else if (node.type() == DataNode.Type.MULTI_INSTANCE_NODE) {
+            path = ResourceIdParser.appendKeyList(path, (ListKey) node.key());
+            if (keystore.get(DocumentPath.from(path)).join() != null) {
+                throw new FailedException("Requested node already present in the" +
+                        " store, please use an update method");
+            }
+            traverseInner(path, (InnerNode) node);
+        } else {
+            throw new FailedException("Invalid node type");
+        }
+    }
+
+    private void traverseInner(String path, InnerNode node) {
+        addKey(path, node.type());
+        Map<NodeKey, DataNode> entries = node.childNodes();
+        if (entries.size() == 0) {
+            throw new FailedException("Inner node cannot have empty children map");
+        }
+        entries.forEach((k, v) -> {
+            String tempPath;
+            tempPath = ResourceIdParser.appendNodeKey(path, v.key());
+            if (v.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
+                addLeaf(tempPath, (LeafNode) v);
+            } else if (v.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE) {
+                tempPath = ResourceIdParser.appendLeafList(tempPath, (LeafListKey) v.key());
+                addLeaf(tempPath, (LeafNode) v);
+            } else if (v.type() == DataNode.Type.SINGLE_INSTANCE_NODE) {
+                traverseInner(tempPath, (InnerNode) v);
+            } else if (v.type() == DataNode.Type.MULTI_INSTANCE_NODE) {
+                tempPath = ResourceIdParser.appendKeyList(tempPath, (ListKey) v.key());
+                traverseInner(path, (InnerNode) v);
+            } else {
+                throw new FailedException("Invalid node type");
+            }
+        });
+    }
+
+    private Boolean addLeaf(String path, LeafNode node) {
+        objectStore.put(path, node);
+        return addKey(path, node.type());
+    }
+
+    private Boolean addKey(String path, DataNode.Type type) {
+        Boolean stat = false;
+        CompletableFuture<Boolean> ret = keystore.create(DocumentPath.from(path), type);
+        return complete(ret);
+    }
+
+    @Override
     public CompletableFuture<DataNode> readNode(ResourceId path, Filter filter) {
         CompletableFuture<DataNode> eventFuture = CompletableFuture.completedFuture(null);
-        DocumentPath dpath = DocumentPath.from(ResourceIdParser.asString(path));
-        DataNode.Type type;
-        type = keystore.get(dpath).join().value();
+        String spath = ResourceIdParser.parseResId(path);
+        DocumentPath dpath = DocumentPath.from(spath);
+        DataNode.Type type = null;
+        CompletableFuture<Versioned<DataNode.Type>> ret = keystore.get(dpath);
+        type = completeVersioned(ret);
         if (type == null) {
             throw new FailedException("Requested node or some of the parents" +
-                                              "are not present in the requested path");
+                    "are not present in the requested path");
         }
         DataNode retVal = null;
-        //TODO handle single and multi instances differently
-        if ((type == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) ||
-                (type == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE)) {
-            retVal = readLeaf(path);
-        } else {
-            int last = path.nodeKeys().size();
-            NodeKey key = path.nodeKeys().get(last - 1);
-            DataNode.Builder superBldr = InnerNode.builder(key.schemaId().name(),
-                                          key.schemaId().namespace()).type(type);
-            readInner(superBldr, path);
+        if (type == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
+            retVal = readLeaf(spath);
+        } else if (type == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE) {
+            retVal = readLeaf(spath);
+        } else if (type == DataNode.Type.SINGLE_INSTANCE_NODE) {
+            NodeKey key = ResourceIdParser.getInstanceKey(path);
+            if (key == null) {
+                throw new FailedException("Key type did not match node type");
+            }
+            DataNode.Builder superBldr = InnerNode
+                    .builder(key.schemaId().name(), key.schemaId().namespace())
+                    .type(type);
+            readInner(superBldr, spath);
             retVal = superBldr.build();
+        } else if (type == DataNode.Type.MULTI_INSTANCE_NODE) {
+            NodeKey key = ResourceIdParser.getMultiInstanceKey(path);
+            if (key == null) {
+                throw new FailedException("Key type did not match node type");
+            }
+            DataNode.Builder superBldr = InnerNode
+                    .builder(key.schemaId().name(), key.schemaId().namespace())
+                    .type(type);
+            for (KeyLeaf keyLeaf : ((ListKey) key).keyLeafs()) {
+                String tempPath = ResourceIdParser.appendKeyLeaf(spath, keyLeaf);
+                LeafNode lfnd = readLeaf(tempPath);
+                superBldr.addKeyLeaf(keyLeaf.leafSchema().name(),
+                        keyLeaf.leafSchema().namespace(), lfnd.value());
+            }
+            readInner(superBldr, spath);
+            retVal = superBldr.build();
+        } else {
+            throw new FailedException("Invalid node type");
         }
         if (retVal != null) {
             eventFuture = CompletableFuture.completedFuture(retVal);
         } else {
-            log.info("STORE: FAILED to READ node @@@@");
+            log.info("STORE: FAILED to READ node");
         }
         return eventFuture;
     }
 
-  @Override
-  public CompletableFuture<Boolean>
-  addRecursive(ResourceId path, DataNode node) {
-      CompletableFuture<Boolean> eventFuture = CompletableFuture.completedFuture(false);
-      Boolean stat = false;
-      DocumentPath dpath  = DocumentPath.from(ResourceIdParser.asString(path));
-      if (keystore.get(dpath).join() == null) {
-          //recursivley craete all missing aprents
-      }
-      if (keystore.get(dpath).join() != null) {
-          throw new FailedException("Requested node already present " +
-                                            "in the store, please use an update method");
-      }
-      //TODO single instance and multi instance need to be handled differently
-      if ((node.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) ||
-              (node.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE)) {
-          stat = addLeaf(path, (LeafNode) node);
-      } else {
-          stat = (traverseInner(path, (InnerNode) node));
-      }
-      if (stat) {
-          eventFuture = CompletableFuture.completedFuture(true);
-      } else {
-          log.info("STORE: FAILED to create node @@@@");
-      }
-      return eventFuture;
-  }
+    private void readInner(DataNode.Builder superBldr, String spath) {
+        CompletableFuture<Map<String, Versioned<DataNode.Type>>> ret = keystore.getChildren(
+                DocumentPath.from(spath));
+        Map<String, Versioned<DataNode.Type>> entries = null;
+        entries = complete(ret);
+        if ((entries == null) || (entries.size() == 0)) {
+            throw new FailedException("Inner node cannot have empty children map");
+        }
+        entries.forEach((k, v) -> {
+            String[] names = k.split(ResourceIdParser.NM_SEP);
+            String name = names[0];
+            String nmSpc = names[1];
+            DataNode.Type type = v.value();
+            String tempPath = ResourceIdParser.appendNodeKey(spath, name, nmSpc);
+            if (type == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
+                superBldr.createChildBuilder(name, nmSpc, readLeaf(tempPath).value())
+                        .type(type)
+                        .exitNode();
+            } else if (type == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE) {
+                String mlpath = ResourceIdParser.appendLeafList(tempPath, names[2]);
+                LeafNode lfnode = readLeaf(mlpath);
+                superBldr.createChildBuilder(name, nmSpc, lfnode.value())
+                        .type(type)
+                        .addLeafListValue(lfnode.value())
+                        .exitNode();
+                //TODO this alone should be sufficient and take the nm, nmspc too
+            } else if (type == DataNode.Type.SINGLE_INSTANCE_NODE) {
+                DataNode.Builder tempBldr = superBldr.createChildBuilder(name, nmSpc)
+                        .type(type);
+                readInner(tempBldr, tempPath);
+            } else if (type == DataNode.Type.MULTI_INSTANCE_NODE) {
+                DataNode.Builder tempBldr = superBldr.createChildBuilder(name, nmSpc)
+                        .type(type);
+                tempPath = ResourceIdParser.appendMultiInstKey(tempPath, k);
+                String[] keys = k.split(ResourceIdParser.KEY_SEP);
+                for (int i = 1; i < keys.length; i++) {
+                    String curKey = ResourceIdParser.appendKeyLeaf(tempPath, keys[i]);
+                    LeafNode lfnd = readLeaf(curKey);
+                    String[] keydata = keys[i].split(ResourceIdParser.NM_SEP);
+                    superBldr.addKeyLeaf(keydata[0], keydata[1], lfnd.value());
+                }
+                readInner(tempBldr, tempPath);
+            } else {
+                throw new FailedException("Node type should either be LEAF or INNERNODE");
+            }
+        });
+        superBldr.exitNode();
+    }
+
+    private LeafNode readLeaf(String path) {
+        return objectStore.get(path).value();
+    }
     @Override
     public CompletableFuture<Boolean> updateNode(ResourceId path, DataNode node) {
         throw new FailedException("Not yet implemented");
     }
     @Override
-    public CompletableFuture<Boolean>
-    updateNodeRecursive(ResourceId path, DataNode node) {
+    public CompletableFuture<Boolean> updateNodeRecursive(ResourceId path, DataNode node) {
         throw new FailedException("Not yet implemented");
     }
     @Override
-    public CompletableFuture<Boolean>
-    replaceNode(ResourceId path, DataNode node) {
+    public CompletableFuture<Boolean> replaceNode(ResourceId path, DataNode node) {
         throw new FailedException("Not yet implemented");
     }
     @Override
-    public CompletableFuture<Boolean>
-    deleteNode(ResourceId path) {
+    public CompletableFuture<Boolean> deleteNode(ResourceId path) {
         throw new FailedException("Not yet implemented");
     }
+
     @Override
-    public CompletableFuture<Boolean>
-    deleteNodeRecursive(ResourceId path) {
-        String spath = ResourceIdParser.asString(path);
+    public CompletableFuture<Boolean> deleteNodeRecursive(ResourceId path) {
+        String spath = ResourceIdParser.parseResId(path);
         DocumentPath dpath = DocumentPath.from(spath);
         DataNode.Type type = null;
         CompletableFuture<Versioned<DataNode.Type>> vtype = keystore.removeNode(dpath);
@@ -208,123 +355,71 @@
         } else {
             return CompletableFuture.completedFuture(true);
         }
-
     }
 
-    private Boolean addLeaf(ResourceId path, LeafNode node) {
-        objectStore.put(ResourceIdParser.asString(path), node);
-        return (keystore.create(DocumentPath.from(ResourceIdParser.asString(path)), node.type()).join());
-    }
-
-    private Boolean addKey(ResourceId path, DataNode.Type type) {
-        return (keystore.create(DocumentPath.from(ResourceIdParser.asString(path)), type).join());
-    }
-
-    private Boolean checkNode(ResourceId path, DataNode node) {
-        //TODO single instance and multi instance need to be handled differently
-        if ((node.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) ||
-                (node.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE)) {
-            return (addLeaf(path, (LeafNode) node));
-        } else if ((node.type() == DataNode.Type.SINGLE_INSTANCE_NODE) ||
-                (node.type() == DataNode.Type.MULTI_INSTANCE_NODE)) {
-            addKey(path, node.type());
-            return (traverseInner(path, (InnerNode) node));
-        } else {
-            throw new FailedException("Node type should either be LEAF or INNERNODE");
+    @Override
+    public void addConfigListener(ResourceId path, DynamicConfigListener listener) {
+        String lpath = ResourceIdParser.parseResId(path);
+        try {
+            lstnrStore.put(lpath, listener);
+        } catch (ConsistentMapException e) {
+            throw new FailedException(e.getCause().getMessage());
         }
     }
 
-    private LeafNode readLeaf(ResourceId path) {
-        return objectStore.get(ResourceIdParser.asString(path)).value();
+    @Override
+    public void removeConfigListener(ResourceId path, DynamicConfigListener listener) {
+        String lpath = ResourceIdParser.parseResId(path);
+        try {
+            lstnrStore.remove(lpath, listener);
+        } catch (ConsistentMapException e) {
+            throw new FailedException(e.getCause().getMessage());
+        }
     }
 
-    private Boolean traverseInner(ResourceId path, InnerNode node) {
-        addKey(path, node.type());
-        Map<NodeKey, DataNode> entries = node.childNodes();
-        if (entries.size() == 0) {
-            throw new FailedException("Inner node cannot have empty children map");
-        }
-        entries.forEach((k, v) -> {
-            ResourceId tempPath;
-            try {
-                tempPath = path.copyBuilder()
-                        .addBranchPointSchema(k.schemaId().name(),
-                                              k.schemaId().namespace())
-                        .build();
-            } catch (CloneNotSupportedException e) {
-                throw new FailedException("ResourceId could not be cloned@@@@");
-            }
-            //TODO single instance and multi instance need to be handled differently
-            if ((v.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) ||
-                    (v.type() == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE)) {
-                addLeaf(tempPath, (LeafNode) v);
-            } else if ((v.type() == DataNode.Type.SINGLE_INSTANCE_NODE) ||
-                    (v.type() == DataNode.Type.MULTI_INSTANCE_NODE)) {
-                traverseInner(tempPath, (InnerNode) v);
+    @Override
+    public Collection<? extends DynamicConfigListener> getConfigListener(ResourceId path) {
+        String lpath = ResourceIdParser.parseResId(path);
+        try {
+            Versioned<Collection<? extends DynamicConfigListener>> ls = lstnrStore.get(lpath);
+            if (ls != null) {
+                return ls.value();
             } else {
-                throw new FailedException("Node type should either be LEAF or INNERNODE");
+                log.info("STORE: no Listeners!!");
+                return null;
             }
-        });
-        return true;
-    }
-
-    private void readInner(DataNode.Builder superBldr, ResourceId path) {
-        Map<String, Versioned<DataNode.Type>> entries = keystore.getChildren(
-                DocumentPath.from(ResourceIdParser.asString(path))).join();
-        if (entries.size() == 0) {
-            throw new FailedException("Inner node cannot have empty children map");
+        } catch (ConsistentMapException e) {
+            //throw new FailedException(e.getCause().getMessage());
+            throw new FailedException("getConfigListener failed");
+        } catch (NullPointerException e) {
+            throw new FailedException(e.getCause().getMessage());
         }
-        entries.forEach((k, v) -> {
-            ResourceId tempPath;
-            String[] names = k.split("#");
-            String name = names[0];
-            String nmSpc = names[1];
-            DataNode.Type type = v.value();
-            try {
-                tempPath = path.copyBuilder()
-                        .addBranchPointSchema(name, nmSpc)
-                        .build();
-            } catch (CloneNotSupportedException e) {
-                throw new FailedException("ResourceId could not be cloned@@@@");
-            }
-            //TODO single instance and multi instance need to be handled differently
-            if ((type == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) ||
-                    (type == DataNode.Type.MULTI_INSTANCE_LEAF_VALUE_NODE)) {
-                superBldr.createChildBuilder(name, nmSpc, readLeaf(tempPath))
-                        .type(type)
-                        .exitNode();
-            } else if ((type == DataNode.Type.SINGLE_INSTANCE_NODE) ||
-                    (type == DataNode.Type.MULTI_INSTANCE_NODE)) {
-                DataNode.Builder tempBldr = superBldr.createChildBuilder(name, nmSpc)
-                        .type(type);
-                readInner(tempBldr, tempPath);
-            } else {
-                throw new FailedException("Node type should either be LEAF or INNERNODE");
-            }
-        });
-        superBldr.exitNode();
     }
 
     public class InternalDocTreeListener implements DocumentTreeListener<DataNode.Type> {
         @Override
         public void event(DocumentTreeEvent<DataNode.Type> event) {
             DynamicConfigEvent.Type type;
-            DataNode node;
             ResourceId path;
             switch (event.type()) {
                 case CREATED:
                     log.info("NODE created in store");
+                    type = NODE_ADDED;
                     break;
                 case UPDATED:
                     log.info("NODE updated in store");
+                    type = NODE_UPDATED;
                     break;
                 case DELETED:
                     log.info("NODE deleted in store");
+                    type = NODE_DELETED;
                     break;
-
                 default:
+                    log.info("UNKNOWN operation in store");
+                    type = UNKNOWN_OPRN;
             }
-            //notify
+            path = ResourceIdParser.getResId(event.path().pathElements());
+            notifyDelegate(new DynamicConfigEvent(type, path));
         }
     }
 
@@ -343,7 +438,6 @@
                     //log.info("NODE removed in store");
                     break;
             }
-            //notify
         }
     }
 
diff --git a/apps/config/src/main/java/org/onosproject/config/impl/DynamicConfigManager.java b/apps/config/src/main/java/org/onosproject/config/impl/DynamicConfigManager.java
index 154d014..c79d42a 100644
--- a/apps/config/src/main/java/org/onosproject/config/impl/DynamicConfigManager.java
+++ b/apps/config/src/main/java/org/onosproject/config/impl/DynamicConfigManager.java
@@ -40,6 +40,8 @@
 import org.onosproject.event.EventDeliveryService;
 import org.slf4j.Logger;
 
+import java.util.Collection;
+
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -85,10 +87,6 @@
         return store.readNode(path, filter).join();
     }
 
-    public Integer getNumberOfChildren(ResourceId path, Filter filter) {
-        throw new FailedException("Not yet implemented");
-    }
-
     public void updateNode(ResourceId path, DataNode node) {
         throw new FailedException("Not yet implemented");
     }
@@ -108,13 +106,15 @@
     public void replaceNode(ResourceId path, DataNode node) {
         throw new FailedException("Not yet implemented");
     }
-
-    public void addConfigListener(ResourceId path, DynamicConfigListener listener) {
+    public Integer getNumberOfChildren(ResourceId path, Filter filter) {
         throw new FailedException("Not yet implemented");
     }
+    public void addConfigListener(ResourceId path, DynamicConfigListener listener) {
+        store.addConfigListener(path, listener);
+    }
 
     public void removeConfigListener(ResourceId path, DynamicConfigListener listener) {
-        throw new FailedException("Not yet implemented");
+        store.removeConfigListener(path, listener);
     }
 
     public void registerHandler(RpcHandler handler, RpcCommand command) {
@@ -134,13 +134,19 @@
         throw new FailedException("Not yet implemented");
     }
     /**
-     * Auxiliary store delegate to receive notification about changes in
-     * the prop configuration store state - by the store itself.
+     * Auxiliary store delegate to receive notification about changes in the store.
      */
     private class InternalStoreDelegate implements DynamicConfigStoreDelegate {
         public void notify(DynamicConfigEvent event) {
-            // TODO
-            // post(event);
+            ResourceId path = event.subject();
+            Collection<? extends DynamicConfigListener> lstnrs = store.getConfigListener(path);
+            if (lstnrs != null) {
+                for (DynamicConfigListener l : lstnrs) {
+                    l.event(event);
+                }
+            } else {
+                log.info("InternalStoreDelegate: no Listeners");
+            }
         }
     }
 }
\ No newline at end of file