2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.md.sal.dom.store.impl.tree;
10 import java.lang.ref.Reference;
11 import java.lang.ref.WeakReference;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.HashSet;
16 import java.util.concurrent.locks.Lock;
17 import java.util.concurrent.locks.ReadWriteLock;
18 import java.util.concurrent.locks.ReentrantReadWriteLock;
20 import javax.annotation.concurrent.GuardedBy;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
24 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
25 import org.opendaylight.yangtools.concepts.Identifiable;
26 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 import com.google.common.base.Optional;
33 import com.google.common.base.Preconditions;
35 public final class ListenerTree {
36 private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class);
37 private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
38 private final Node rootNode = new Node(null, null);
40 private ListenerTree() {
44 public static ListenerTree create() {
45 return new ListenerTree();
49 * Registers listener on this node.
51 * @param path Full path on which listener is registered.
52 * @param listener Listener
53 * @param scope Scope of triggering event.
54 * @return Listener registration
56 public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
57 final L listener, final DataChangeScope scope) {
59 // Take the write lock
60 rwLock.writeLock().lock();
63 Node walkNode = rootNode;
64 for(PathArgument arg : path.getPath()) {
65 walkNode = walkNode.ensureChild(arg);
68 final Node node = walkNode;
69 DataChangeListenerRegistration<L> listenerReg = new DataChangeListenerRegistration<L>(listener) {
71 public DataChangeScope getScope() {
76 public InstanceIdentifier getPath() {
81 protected void removeRegistration() {
83 * TODO: Here's an interesting problem. The way the datastore works, it
84 * enqueues requests towards the listener, so the listener will be
85 * notified at some point in the future. Now if the registration is
86 * closed, we will prevent any new events from being delivered, but
87 * we have no way to purge that queue.
89 * While this does not directly violate the ListenerRegistration
90 * contract, it is probably not going to be liked by the users.
93 // Take the write lock
94 ListenerTree.this.rwLock.writeLock().lock();
96 node.removeListener(this);
98 // Always release the lock
99 ListenerTree.this.rwLock.writeLock().unlock();
104 node.addListener(listenerReg);
107 // Always release the lock
108 rwLock.writeLock().unlock();
112 public Walker getWalker() {
114 * TODO: The only current user of this method is local to the datastore.
115 * Since this class represents a read-lock, losing a reference to
116 * it is a _major_ problem, as the registration process will get
117 * wedged, eventually grinding the system to a halt. Should an
118 * external user exist, make the Walker a phantom reference, which
119 * will cleanup the lock if not told to do so.
121 final Walker ret = new Walker(rwLock.readLock(), rootNode);
122 rwLock.readLock().lock();
126 public static final class Walker implements AutoCloseable {
127 private final Lock lock;
128 private final Node node;
131 private boolean valid = true;
133 private Walker(final Lock lock, final Node node) {
134 this.lock = Preconditions.checkNotNull(lock);
135 this.node = Preconditions.checkNotNull(node);
138 public Node getRootNode() {
143 public synchronized void close() {
152 * This is a single node within the listener tree. Note that the data returned from
153 * and instance of this class is guaranteed to have any relevance or consistency
154 * only as long as the {@link Walker} instance through which it is reached remains
157 public static final class Node implements StoreTreeNode<Node>, Identifiable<PathArgument> {
158 private final HashSet<DataChangeListenerRegistration<?>> listeners = new HashSet<>();
159 private final Map<PathArgument, Node> children = new HashMap<>();
160 private final PathArgument identifier;
161 private final Reference<Node> parent;
163 private Node(final Node parent, final PathArgument identifier) {
164 this.parent = new WeakReference<>(parent);
165 this.identifier = identifier;
169 public PathArgument getIdentifier() {
174 public Optional<Node> getChild(final PathArgument child) {
175 return Optional.fromNullable(children.get(child));
179 * Return the list of current listeners. This collection is guaranteed
180 * to be immutable only while the walker, through which this node is
181 * reachable remains unclosed.
183 * @return the list of current listeners
185 @SuppressWarnings({ "rawtypes", "unchecked" })
186 public Collection<org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<?>> getListeners() {
187 return (Collection) listeners;
190 private Node ensureChild(final PathArgument child) {
191 Node potential = (children.get(child));
192 if (potential == null) {
193 potential = new Node(this, child);
194 children.put(child, potential);
199 private void addListener(final DataChangeListenerRegistration<?> listener) {
200 listeners.add(listener);
201 LOG.debug("Listener {} registered", listener);
204 private void removeListener(final DataChangeListenerRegistration<?> listener) {
205 listeners.remove(listener);
206 LOG.debug("Listener {} unregistered", listener);
208 // We have been called with the write-lock held, so we can perform some cleanup.
209 removeThisIfUnused();
212 private void removeThisIfUnused() {
213 final Node p = parent.get();
214 if (p != null && listeners.isEmpty() && children.isEmpty()) {
215 p.removeChild(identifier);
219 private void removeChild(final PathArgument arg) {
220 children.remove(arg);
221 removeThisIfUnused();
225 private abstract static class DataChangeListenerRegistration<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> implements
226 org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<T> {
228 public DataChangeListenerRegistration(final T listener) {