Merge "BUG-592: Fix InstanceIdentifier instantiation"
authorTony Tkacik <ttkacik@cisco.com>
Fri, 11 Apr 2014 14:45:56 +0000 (14:45 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 11 Apr 2014 14:45:56 +0000 (14:45 +0000)
opendaylight/commons/opendaylight/pom.xml
opendaylight/forwardingrulesmanager/api/pom.xml
opendaylight/forwardingrulesmanager/api/src/main/java/org/opendaylight/controller/forwardingrulesmanager/IForwardingRulesManager.java
opendaylight/forwardingrulesmanager/implementation/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManager.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataChangeEventResolver.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerRegistrationNode.java [deleted file]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java [new file with mode: 0644]

index e638ba68a7655529ab34d52957b6b883cc5d42ef..1e8d3d26b584fd7b91bfbf340cdda3b3e7296e27 100644 (file)
@@ -95,7 +95,7 @@
     <commons.io.version>2.4</commons.io.version>
     <bundlescanner.version>0.4.2-SNAPSHOT</bundlescanner.version>
     <usermanager.version>0.4.2-SNAPSHOT</usermanager.version>
-    <forwardingrulesmanager.version>0.5.1-SNAPSHOT</forwardingrulesmanager.version>
+    <forwardingrulesmanager.version>0.6.0-SNAPSHOT</forwardingrulesmanager.version>
     <statisticsmanager.version>0.5.1-SNAPSHOT</statisticsmanager.version>
     <clustering.services.version>0.5.1-SNAPSHOT</clustering.services.version>
     <configuration.version>0.4.3-SNAPSHOT</configuration.version>
index 3bd1af3a102ac9f16300606aad216913c510c10c..304a3151deceae104375836931cad2424befb439 100644 (file)
@@ -15,7 +15,7 @@
   </scm>
 
   <artifactId>forwardingrulesmanager</artifactId>
-  <version>0.5.1-SNAPSHOT</version>
+  <version>0.6.0-SNAPSHOT</version>
   <packaging>bundle</packaging>
 
   <build>
index 9d68f84a3ae62dff4e968acba87cb8a4ec5e9b76..070e8c499800c39068bbb16d2a879a4eb353497b 100644 (file)
@@ -369,6 +369,16 @@ public interface IForwardingRulesManager {
      */
     public Status addStaticFlow(FlowConfig config);
 
+    /**
+     * Add a flow specified by the {@code FlowConfig} object on the current
+     * container, through an asynchronous call.
+     *
+     * @param config
+     *            the {@code FlowConfig} object representing the static flow
+     * @return the {@code Status} object indicating the result of this action.
+     */
+    public Status addStaticFlowAsync(FlowConfig config);
+
     /**
      * Remove a flow specified by the {@code FlowConfig} object on the current
      * container
@@ -379,6 +389,16 @@ public interface IForwardingRulesManager {
      */
     public Status removeStaticFlow(FlowConfig config);
 
+    /**
+     * Remove a flow specified by the {@code FlowConfig} object on the current
+     * container, through an asynchronous call.
+     *
+     * @param config
+     *            the {@code FlowConfig} object representing the static flow
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status removeStaticFlowAsync(FlowConfig config);
+
     /**
      * Replace the flow identified by the {@code FlowConfig.name} name for the
      * {@code FlowConfig.node} network node with the new flow specified by
@@ -386,7 +406,7 @@ public interface IForwardingRulesManager {
      *
      * @param config
      *            the {@code FlowConfig} object
-     * @returnthe {@code Status} object indicating the result of this action
+     * @return the {@code Status} object indicating the result of this action
      */
     public Status modifyStaticFlow(FlowConfig config);
 
@@ -401,6 +421,18 @@ public interface IForwardingRulesManager {
      */
     public Status removeStaticFlow(String name, Node node);
 
+    /**
+     * Remove the flow specified by name on the passed network node via an
+     * asynchronous call
+     *
+     * @param name
+     *            for the static flow
+     * @param node
+     *            on which the flow is attached
+     * @return the {@code Status} object indicating the result of this action
+     */
+    public Status removeStaticFlowAsync(String name, Node node);
+
     /**
      * Toggle the installation status of the specified configured flow If the
      * flow configuration status is active, this call will change the flow
index 614c39e0608fe1a7d384b854588d143c0ad5761e..f7b647dd721a1f1f9117d95bc3723867574e5064 100644 (file)
@@ -1590,6 +1590,10 @@ public class ForwardingRulesManager implements
 
     @Override
     public Status addStaticFlow(FlowConfig config) {
+        return addStaticFlow(config, false);
+    }
+
+    private Status addStaticFlow(FlowConfig config, boolean async) {
         // Configuration object validation
         Status status = config.validate();
         if (!status.isSuccess()) {
@@ -1598,7 +1602,13 @@ public class ForwardingRulesManager implements
             config.setStatus(error);
             return new Status(StatusCode.BADREQUEST, error);
         }
-        return addStaticFlowInternal(config, false);
+        return addStaticFlowInternal(config, async, false);
+    }
+
+
+    @Override
+    public Status addStaticFlowAsync(FlowConfig config) {
+        return addStaticFlow(config, true);
     }
 
     /**
@@ -1616,7 +1626,7 @@ public class ForwardingRulesManager implements
      *            installation on the network node was successful
      * @return The status of this request
      */
-    private Status addStaticFlowInternal(FlowConfig config, boolean restore) {
+    private Status addStaticFlowInternal(FlowConfig config, boolean async, boolean restore) {
         boolean multipleFlowPush = false;
         String error;
         Status status;
@@ -1653,7 +1663,7 @@ public class ForwardingRulesManager implements
             // Program hw
             if (config.installInHw()) {
                 FlowEntry entry = config.getFlowEntry();
-                status = this.installFlowEntry(entry);
+                status = async ? this.installFlowEntryAsync(entry) : this.installFlowEntry(entry);
                 if (!status.isSuccess()) {
                     config.setStatus(status.getDescription());
                     if (!restore) {
@@ -1765,6 +1775,15 @@ public class ForwardingRulesManager implements
 
     @Override
     public Status removeStaticFlow(FlowConfig config) {
+        return removeStaticFlow(config, false);
+    }
+
+    @Override
+    public Status removeStaticFlowAsync(FlowConfig config) {
+        return removeStaticFlow(config, true);
+    }
+
+    private Status removeStaticFlow(FlowConfig config, boolean async) {
         /*
          * No config.isInternal() check as NB does not take this path and GUI
          * cannot issue a delete on an internal generated flow. We need this
@@ -1788,7 +1807,8 @@ public class ForwardingRulesManager implements
         }
 
         // Program the network node
-        Status status = this.uninstallFlowEntry(config.getFlowEntry());
+        Status status = async ? this.uninstallFlowEntryAsync(config.getFlowEntry()) : this.uninstallFlowEntry(config
+                .getFlowEntry());
 
         // Update configuration database if programming was successful
         if (status.isSuccess()) {
@@ -1800,6 +1820,15 @@ public class ForwardingRulesManager implements
 
     @Override
     public Status removeStaticFlow(String name, Node node) {
+       return removeStaticFlow(name, node, false);
+    }
+
+    @Override
+    public Status removeStaticFlowAsync(String name, Node node) {
+        return removeStaticFlow(name, node, true);
+    }
+
+    private Status removeStaticFlow(String name, Node node, boolean async) {
         // Look for the target configuration entry
         Integer key = 0;
         FlowConfig target = null;
@@ -1830,7 +1859,7 @@ public class ForwardingRulesManager implements
         }
 
         // Program the network node
-        Status status = this.removeEntry(target.getFlowEntry(), false);
+        Status status = this.removeEntry(target.getFlowEntry(), async);
 
         // Update configuration database if programming was successful
         if (status.isSuccess()) {
@@ -2081,7 +2110,7 @@ public class ForwardingRulesManager implements
         }
 
         for (ConfigurationObject conf : configurationService.retrieveConfiguration(this, STATIC_FLOWS_FILE_NAME)) {
-            addStaticFlowInternal((FlowConfig) conf, true);
+            addStaticFlowInternal((FlowConfig) conf, false, true);
         }
     }
 
@@ -2193,7 +2222,7 @@ public class ForwardingRulesManager implements
                     // check if the frm really needs to act on the notification.
                     // this is to check against duplicate notifications
                     if(programInternalFlow(proactive, fc)) {
-                        Status status = (proactive) ? addStaticFlowInternal(fc, false) : removeStaticFlow(fc);
+                        Status status = (proactive) ? addStaticFlowInternal(fc, false, false) : removeStaticFlow(fc);
                         if (status.isSuccess()) {
                             log.trace("{} Proactive Static flow: {}", (proactive ? "Installed" : "Removed"), fc.getName());
                         } else {
@@ -2376,7 +2405,7 @@ public class ForwardingRulesManager implements
             if ((staticFlow.getNode().equals(node)) && (staticFlow.getPortGroup().equals(config.getName()))) {
                 for (Short port : data.getPorts()) {
                     FlowConfig derivedFlow = getDerivedFlowConfig(staticFlow, config.getName(), port);
-                    addStaticFlowInternal(derivedFlow, false);
+                    addStaticFlowInternal(derivedFlow, false, false);
                 }
             }
         }
@@ -3240,4 +3269,5 @@ public class ForwardingRulesManager implements
         }
         return list;
     }
+
 }
index f231bb5c39e32c3cfb6b998873d0fe3d748ecdc3..df2725d0206f1bb91716e8505b61214de6a16d65 100644 (file)
@@ -10,7 +10,8 @@ import java.util.Set;
 
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerRegistrationNode;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
@@ -28,7 +29,7 @@ public class DataChangeEventResolver {
     private static final DOMImmutableDataChangeEvent NO_CHANGE = builder().build();
     private final ImmutableList.Builder<ChangeListenerNotifyTask> tasks = ImmutableList.builder();
     private InstanceIdentifier rootPath;
-    private ListenerRegistrationNode listenerRoot;
+    private ListenerTree listenerRoot;
     private NodeModification modificationRoot;
     private Optional<StoreMetadataNode> beforeRoot;
     private Optional<StoreMetadataNode> afterRoot;
@@ -42,11 +43,11 @@ public class DataChangeEventResolver {
         return this;
     }
 
-    protected ListenerRegistrationNode getListenerRoot() {
+    protected ListenerTree getListenerRoot() {
         return listenerRoot;
     }
 
-    protected DataChangeEventResolver setListenerRoot(final ListenerRegistrationNode listenerRoot) {
+    protected DataChangeEventResolver setListenerRoot(final ListenerTree listenerRoot) {
         this.listenerRoot = listenerRoot;
         return this;
     }
@@ -79,13 +80,16 @@ public class DataChangeEventResolver {
     }
 
     public Iterable<ChangeListenerNotifyTask> resolve() {
-        LOG.trace("Resolving events for {}" ,modificationRoot);
-        resolveAnyChangeEvent(rootPath, Optional.of(listenerRoot), modificationRoot, beforeRoot, afterRoot);
-        return tasks.build();
+        LOG.trace("Resolving events for {}", modificationRoot);
+
+        try (final Walker w = listenerRoot.getWalker()) {
+            resolveAnyChangeEvent(rootPath, Optional.of(w.getRootNode()), modificationRoot, beforeRoot, afterRoot);
+            return tasks.build();
+        }
     }
 
     private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
-            final Optional<ListenerRegistrationNode> listeners, final NodeModification modification,
+            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
             final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
         // No listeners are present in listener registration subtree
         // no before and after state is present
@@ -119,13 +123,13 @@ public class DataChangeEventResolver {
      * @return
      */
     private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
-            final Optional<ListenerRegistrationNode> listeners, final StoreMetadataNode afterState) {
+            final Optional<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
         final NormalizedNode<?, ?> node = afterState.getData();
         Builder builder = builder().setAfter(node).addCreated(path, node);
 
         for (StoreMetadataNode child : afterState.getChildren()) {
             PathArgument childId = child.getIdentifier();
-            Optional<ListenerRegistrationNode> childListeners = getChild(listeners, childId);
+            Optional<ListenerTree.Node> childListeners = getChild(listeners, childId);
 
             InstanceIdentifier childPath = StoreUtils.append(path, childId);
             builder.merge(resolveCreateEvent(childPath, childListeners, child));
@@ -135,13 +139,13 @@ public class DataChangeEventResolver {
     }
 
     private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
-            final Optional<ListenerRegistrationNode> listeners, final StoreMetadataNode beforeState) {
+            final Optional<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
         final NormalizedNode<?, ?> node = beforeState.getData();
         Builder builder = builder().setBefore(node).addRemoved(path, node);
 
         for (StoreMetadataNode child : beforeState.getChildren()) {
             PathArgument childId = child.getIdentifier();
-            Optional<ListenerRegistrationNode> childListeners = getChild(listeners, childId);
+            Optional<ListenerTree.Node> childListeners = getChild(listeners, childId);
             InstanceIdentifier childPath = StoreUtils.append(path, childId);
             builder.merge(resolveDeleteEvent(childPath, childListeners, child));
         }
@@ -149,7 +153,7 @@ public class DataChangeEventResolver {
     }
 
     private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
-            final Optional<ListenerRegistrationNode> listeners, final NodeModification modification,
+            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
             final StoreMetadataNode before, final StoreMetadataNode after) {
 
         Builder one = builder().setBefore(before.getData()).setAfter(after.getData());
@@ -159,7 +163,7 @@ public class DataChangeEventResolver {
         for (NodeModification childMod : modification.getModifications()) {
             PathArgument childId = childMod.getIdentifier();
             InstanceIdentifier childPath = append(path, childId);
-            Optional<ListenerRegistrationNode> childListen = getChild(listeners, childId);
+            Optional<ListenerTree.Node> childListen = getChild(listeners, childId);
 
             Optional<StoreMetadataNode> childBefore = before.getChild(childId);
             Optional<StoreMetadataNode> childAfter = after.getChild(childId);
@@ -189,13 +193,13 @@ public class DataChangeEventResolver {
     }
 
     private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
-            final Optional<ListenerRegistrationNode> listeners, final NodeModification modification,
+            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
             final StoreMetadataNode before, final StoreMetadataNode after) {
         // FIXME Add task
         return builder().build();
     }
 
-    private DOMImmutableDataChangeEvent addNotifyTask(final Optional<ListenerRegistrationNode> listeners, final DOMImmutableDataChangeEvent event) {
+    private DOMImmutableDataChangeEvent addNotifyTask(final Optional<ListenerTree.Node> listeners, final DOMImmutableDataChangeEvent event) {
         if (listeners.isPresent()) {
             final Collection<DataChangeListenerRegistration<?>> l = listeners.get().getListeners();
             if (!l.isEmpty()) {
@@ -206,7 +210,7 @@ public class DataChangeEventResolver {
         return event;
     }
 
-    private void addNotifyTask(final ListenerRegistrationNode listenerRegistrationNode, final DataChangeScope scope,
+    private void addNotifyTask(final ListenerTree.Node listenerRegistrationNode, final DataChangeScope scope,
             final DOMImmutableDataChangeEvent event) {
         Collection<DataChangeListenerRegistration<?>> potential = listenerRegistrationNode.getListeners();
         if(!potential.isEmpty()) {
index de0c1463923ebe0e2600a79191e590cf7552376f..a854c4806b4a61f8f3d52716e23f978503465a16 100644 (file)
@@ -18,8 +18,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerRegistrationNode;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerRegistrationNode.DataChangeListenerRegistration;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ModificationType;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
@@ -59,7 +58,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
     private final ListeningExecutorService executor;
     private final String name;
     private final AtomicLong txCounter = new AtomicLong(0);
-    private final ListenerRegistrationNode listenerTree;
+    private final ListenerTree listenerTree;
     private final AtomicReference<DataAndMetadataSnapshot> snapshot;
 
     private ModificationApplyOperation operationTree;
@@ -69,7 +68,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
     public InMemoryDOMDataStore(final String name, final ListeningExecutorService executor) {
         this.name = Preconditions.checkNotNull(name);
         this.executor = Preconditions.checkNotNull(executor);
-        this.listenerTree = ListenerRegistrationNode.createRoot();
+        this.listenerTree = ListenerTree.create();
         this.snapshot = new AtomicReference<DataAndMetadataSnapshot>(DataAndMetadataSnapshot.createEmpty());
         this.operationTree = new AlwaysFailOperation();
     }
@@ -114,12 +113,8 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
         final DataChangeListenerRegistration<L> reg;
         synchronized (this) {
             LOG.debug("{}: Registering data change listener {} for {}",name,listener,path);
-            ListenerRegistrationNode listenerNode = listenerTree;
-            for(PathArgument arg : path.getPath()) {
-                listenerNode = listenerNode.ensureChild(arg);
-            }
 
-            reg = listenerNode.registerDataChangeListener(path, listener, scope);
+            reg = listenerTree.registerDataChangeListener(path, listener, scope);
 
             Optional<StoreMetadataNode> currentState = snapshot.get().read(path);
             if (currentState.isPresent()) {
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerRegistrationNode.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerRegistrationNode.java
deleted file mode 100644 (file)
index 854c125..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.opendaylight.controller.md.sal.dom.store.impl.tree;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
-import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
-import org.opendaylight.yangtools.concepts.Identifiable;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Optional;
-
-public class ListenerRegistrationNode implements StoreTreeNode<ListenerRegistrationNode>, Identifiable<PathArgument> {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ListenerRegistrationNode.class);
-
-    private final ListenerRegistrationNode parent;
-    private final Map<PathArgument, ListenerRegistrationNode> children;
-    private final PathArgument identifier;
-    private final HashSet<DataChangeListenerRegistration<?>> listeners;
-
-    private ListenerRegistrationNode(final PathArgument identifier) {
-        this(null, identifier);
-    }
-
-    private ListenerRegistrationNode(final ListenerRegistrationNode parent, final PathArgument identifier) {
-        this.parent = parent;
-        this.identifier = identifier;
-        children = new HashMap<>();
-        listeners = new HashSet<>();
-    }
-
-    public final static ListenerRegistrationNode createRoot() {
-        return new ListenerRegistrationNode(null);
-    }
-
-    @Override
-    public PathArgument getIdentifier() {
-        return identifier;
-    }
-
-    /**
-     * Return the list of current listeners. Any caller wishing to use this method
-     * has to make sure the collection remains unchanged while it's executing. This
-     * means the caller has to synchronize externally both the registration and
-     * unregistration process.
-     *
-     * @return the list of current listeners
-     */
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    public Collection<org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<?>> getListeners() {
-        return (Collection) listeners;
-    }
-
-    @Override
-    public synchronized Optional<ListenerRegistrationNode> getChild(final PathArgument child) {
-        return Optional.fromNullable(children.get(child));
-    }
-
-    public synchronized ListenerRegistrationNode ensureChild(final PathArgument child) {
-        ListenerRegistrationNode potential = (children.get(child));
-        if (potential == null) {
-            potential = new ListenerRegistrationNode(this, child);
-            children.put(child, potential);
-        }
-        return potential;
-    }
-
-    /**
-     * Registers listener on this node.
-     *
-     * @param path Full path on which listener is registered.
-     * @param listener Listener
-     * @param scope Scope of triggering event.
-     * @return
-     */
-    public synchronized <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
-            final L listener, final DataChangeScope scope) {
-
-        DataChangeListenerRegistration<L> listenerReg = new DataChangeListenerRegistration<L>(path,listener, scope, this);
-        listeners.add(listenerReg);
-        LOG.debug("Listener {} registered", listener);
-        return listenerReg;
-    }
-
-    private synchronized void removeListener(final DataChangeListenerRegistration<?> listener) {
-        listeners.remove(listener);
-        LOG.debug("Listener {} unregistered", listener);
-        removeThisIfUnused();
-    }
-
-    private void removeThisIfUnused() {
-        if (parent != null && listeners.isEmpty() && children.isEmpty()) {
-            parent.removeChildIfUnused(this);
-        }
-    }
-
-    public boolean isUnused() {
-        return (listeners.isEmpty() && children.isEmpty()) || areChildrenUnused();
-    }
-
-    private boolean areChildrenUnused() {
-        for (ListenerRegistrationNode child : children.values()) {
-            if (!child.isUnused()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void removeChildIfUnused(final ListenerRegistrationNode listenerRegistrationNode) {
-        // FIXME Remove unnecessary
-    }
-
-    public static class DataChangeListenerRegistration<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>>
-            extends AbstractObjectRegistration<T> implements
-            org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<T> {
-
-        private final DataChangeScope scope;
-        private ListenerRegistrationNode node;
-        private final InstanceIdentifier path;
-
-        public DataChangeListenerRegistration(final InstanceIdentifier path,final T listener, final DataChangeScope scope,
-                final ListenerRegistrationNode node) {
-            super(listener);
-            this.path = path;
-            this.scope = scope;
-            this.node = node;
-        }
-
-        @Override
-        public DataChangeScope getScope() {
-            return scope;
-        }
-
-        @Override
-        protected void removeRegistration() {
-            node.removeListener(this);
-            node = null;
-        }
-
-        @Override
-        public InstanceIdentifier getPath() {
-            return path;
-        }
-    }
-}
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java
new file mode 100644 (file)
index 0000000..83cfcac
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2014 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.md.sal.dom.store.impl.tree;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.annotation.concurrent.GuardedBy;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
+import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+
+public final class ListenerTree {
+    private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class);
+    private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
+    private final Node rootNode = new Node(null, null);
+
+    private ListenerTree() {
+
+    }
+
+    public static ListenerTree create() {
+        return new ListenerTree();
+    }
+
+    /**
+     * Registers listener on this node.
+     *
+     * @param path Full path on which listener is registered.
+     * @param listener Listener
+     * @param scope Scope of triggering event.
+     * @return Listener registration
+     */
+    public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
+            final L listener, final DataChangeScope scope) {
+
+        // Take the write lock
+        rwLock.writeLock().lock();
+
+        try {
+            Node walkNode = rootNode;
+            for (final PathArgument arg : path.getPath()) {
+                walkNode = walkNode.ensureChild(arg);
+            }
+
+            final Node node = walkNode;
+            DataChangeListenerRegistration<L> reg = new DataChangeListenerRegistrationImpl<L>(listener) {
+                @Override
+                public DataChangeScope getScope() {
+                    return scope;
+                }
+
+                @Override
+                public InstanceIdentifier getPath() {
+                    return path;
+                }
+
+                @Override
+                protected void removeRegistration() {
+                    /*
+                     * TODO: Here's an interesting problem. The way the datastore works, it
+                     *       enqueues requests towards the listener, so the listener will be
+                     *       notified at some point in the future. Now if the registration is
+                     *       closed, we will prevent any new events from being delivered, but
+                     *       we have no way to purge that queue.
+                     *
+                     *       While this does not directly violate the ListenerRegistration
+                     *       contract, it is probably not going to be liked by the users.
+                     */
+
+                    // Take the write lock
+                    ListenerTree.this.rwLock.writeLock().lock();
+                    try {
+                        node.removeListener(this);
+                    } finally {
+                        // Always release the lock
+                        ListenerTree.this.rwLock.writeLock().unlock();
+                    }
+                }
+            };
+
+            node.addListener(reg);
+            return reg;
+        } finally {
+            // Always release the lock
+            rwLock.writeLock().unlock();
+        }
+    }
+
+    public Walker getWalker() {
+        /*
+         * TODO: The only current user of this method is local to the datastore.
+         *       Since this class represents a read-lock, losing a reference to
+         *       it is a _major_ problem, as the registration process will get
+         *       wedged, eventually grinding the system to a halt. Should an
+         *       external user exist, make the Walker a phantom reference, which
+         *       will cleanup the lock if not told to do so.
+         */
+        final Walker ret = new Walker(rwLock.readLock(), rootNode);
+        rwLock.readLock().lock();
+        return ret;
+    }
+
+    public static final class Walker implements AutoCloseable {
+        private final Lock lock;
+        private final Node node;
+
+        @GuardedBy("this")
+        private boolean valid = true;
+
+        private Walker(final Lock lock, final Node node) {
+            this.lock = Preconditions.checkNotNull(lock);
+            this.node = Preconditions.checkNotNull(node);
+        }
+
+        public Node getRootNode() {
+            return node;
+        }
+
+        @Override
+        public synchronized void close() {
+            if (valid) {
+                lock.unlock();
+                valid = false;
+            }
+        }
+    }
+
+    /**
+     * This is a single node within the listener tree. Note that the data returned from
+     * and instance of this class is guaranteed to have any relevance or consistency
+     * only as long as the {@link Walker} instance through which it is reached remains
+     * unclosed.
+     */
+    public static final class Node implements StoreTreeNode<Node>, Identifiable<PathArgument> {
+        private final Collection<DataChangeListenerRegistration<?>> listeners = new ArrayList<>();
+        private final Map<PathArgument, Node> children = new HashMap<>();
+        private final PathArgument identifier;
+        private final Reference<Node> parent;
+
+        private Node(final Node parent, final PathArgument identifier) {
+            this.parent = new WeakReference<>(parent);
+            this.identifier = identifier;
+        }
+
+        @Override
+        public PathArgument getIdentifier() {
+            return identifier;
+        }
+
+        @Override
+        public Optional<Node> getChild(final PathArgument child) {
+            return Optional.fromNullable(children.get(child));
+        }
+
+        /**
+         * Return the list of current listeners. This collection is guaranteed
+         * to be immutable only while the walker, through which this node is
+         * reachable remains unclosed.
+         *
+         * @return the list of current listeners
+         */
+        public Collection<DataChangeListenerRegistration<?>> getListeners() {
+            return listeners;
+        }
+
+        private Node ensureChild(final PathArgument child) {
+            Node potential = children.get(child);
+            if (potential == null) {
+                potential = new Node(this, child);
+                children.put(child, potential);
+            }
+            return potential;
+        }
+
+        private void addListener(final DataChangeListenerRegistration<?> listener) {
+            listeners.add(listener);
+            LOG.debug("Listener {} registered", listener);
+        }
+
+        private void removeListener(final DataChangeListenerRegistrationImpl<?> listener) {
+            listeners.remove(listener);
+            LOG.debug("Listener {} unregistered", listener);
+
+            // We have been called with the write-lock held, so we can perform some cleanup.
+            removeThisIfUnused();
+        }
+
+        private void removeThisIfUnused() {
+            final Node p = parent.get();
+            if (p != null && listeners.isEmpty() && children.isEmpty()) {
+                p.removeChild(identifier);
+            }
+        }
+
+        private void removeChild(final PathArgument arg) {
+            children.remove(arg);
+            removeThisIfUnused();
+        }
+    }
+
+    private abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
+    implements DataChangeListenerRegistration<T> {
+        public DataChangeListenerRegistrationImpl(final T listener) {
+            super(listener);
+        }
+    }
+}