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 com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
13 import java.lang.ref.Reference;
14 import java.lang.ref.WeakReference;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.HashMap;
19 import java.util.concurrent.locks.Lock;
20 import java.util.concurrent.locks.ReadWriteLock;
21 import java.util.concurrent.locks.ReentrantReadWriteLock;
23 import javax.annotation.concurrent.GuardedBy;
25 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
26 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
27 import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
28 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
29 import org.opendaylight.yangtools.concepts.Identifiable;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * A set of listeners organized as a tree by node to which they listen. This class
39 * allows for efficient lookup of listeners when we walk the DataTreeCandidate.
41 public final class ListenerTree {
42 private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class);
43 private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
44 private final Node rootNode = new Node(null, null);
46 private ListenerTree() {
47 // Private to disallow direct instantiation
51 * Create a new empty instance of the listener tree.
53 * @return An empty instance.
55 public static ListenerTree create() {
56 return new ListenerTree();
60 * Registers listener on this node.
62 * @param path Full path on which listener is registered.
63 * @param listener Listener
64 * @param scope Scope of triggering event.
65 * @return Listener registration
67 public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final YangInstanceIdentifier path,
68 final L listener, final DataChangeScope scope) {
70 // Take the write lock
71 rwLock.writeLock().lock();
74 Node walkNode = rootNode;
75 for (final PathArgument arg : path.getPathArguments()) {
76 walkNode = walkNode.ensureChild(arg);
79 final Node node = walkNode;
80 DataChangeListenerRegistration<L> reg = new DataChangeListenerRegistrationImpl<L>(listener) {
82 public DataChangeScope getScope() {
87 public YangInstanceIdentifier getPath() {
92 protected void removeRegistration() {
94 * TODO: Here's an interesting problem. The way the datastore works, it
95 * enqueues requests towards the listener, so the listener will be
96 * notified at some point in the future. Now if the registration is
97 * closed, we will prevent any new events from being delivered, but
98 * we have no way to purge that queue.
100 * While this does not directly violate the ListenerRegistration
101 * contract, it is probably not going to be liked by the users.
104 // Take the write lock
105 ListenerTree.this.rwLock.writeLock().lock();
107 node.removeListener(this);
109 // Always release the lock
110 ListenerTree.this.rwLock.writeLock().unlock();
115 node.addListener(reg);
118 // Always release the lock
119 rwLock.writeLock().unlock();
124 * Obtain a tree walking context. This context ensures a consistent view of
125 * the listener registrations. The context should be closed as soon as it
126 * is not required, because each unclosed instance blocks modification of
129 * @return A walker instance.
131 public Walker getWalker() {
133 * TODO: The only current user of this method is local to the datastore.
134 * Since this class represents a read-lock, losing a reference to
135 * it is a _major_ problem, as the registration process will get
136 * wedged, eventually grinding the system to a halt. Should an
137 * external user exist, make the Walker a phantom reference, which
138 * will cleanup the lock if not told to do so.
140 final Walker ret = new Walker(rwLock.readLock(), rootNode);
141 rwLock.readLock().lock();
146 * A walking context, pretty much equivalent to an iterator, but it
147 * exposes the underlying tree structure.
150 * FIXME: BUG-1511: split this class out as ListenerWalker.
152 public static final class Walker implements AutoCloseable {
153 private final Lock lock;
154 private final Node node;
157 private boolean valid = true;
159 private Walker(final Lock lock, final Node node) {
160 this.lock = Preconditions.checkNotNull(lock);
161 this.node = Preconditions.checkNotNull(node);
164 public Node getRootNode() {
169 public synchronized void close() {
178 * This is a single node within the listener tree. Note that the data returned from
179 * and instance of this class is guaranteed to have any relevance or consistency
180 * only as long as the {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker} instance through which it is reached remains
184 * FIXME: BUG-1511: split this class out as ListenerNode.
186 public static final class Node implements StoreTreeNode<Node>, Identifiable<PathArgument> {
187 private final Collection<DataChangeListenerRegistration<?>> listeners = new ArrayList<>();
188 private final Map<PathArgument, Node> children = new HashMap<>();
189 private final PathArgument identifier;
190 private final Reference<Node> parent;
192 private Node(final Node parent, final PathArgument identifier) {
193 this.parent = new WeakReference<>(parent);
194 this.identifier = identifier;
198 public PathArgument getIdentifier() {
203 public Optional<Node> getChild(final PathArgument child) {
204 return Optional.fromNullable(children.get(child));
208 * Return the list of current listeners. This collection is guaranteed
209 * to be immutable only while the walker, through which this node is
210 * reachable remains unclosed.
212 * @return the list of current listeners
214 public Collection<DataChangeListenerRegistration<?>> getListeners() {
218 private Node ensureChild(final PathArgument child) {
219 Node potential = children.get(child);
220 if (potential == null) {
221 potential = new Node(this, child);
222 children.put(child, potential);
227 private void addListener(final DataChangeListenerRegistration<?> listener) {
228 listeners.add(listener);
229 LOG.debug("Listener {} registered", listener);
232 private void removeListener(final DataChangeListenerRegistrationImpl<?> listener) {
233 listeners.remove(listener);
234 LOG.debug("Listener {} unregistered", listener);
236 // We have been called with the write-lock held, so we can perform some cleanup.
237 removeThisIfUnused();
240 private void removeThisIfUnused() {
241 final Node p = parent.get();
242 if (p != null && listeners.isEmpty() && children.isEmpty()) {
243 p.removeChild(identifier);
247 private void removeChild(final PathArgument arg) {
248 children.remove(arg);
249 removeThisIfUnused();
253 public String toString() {
254 return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]";
260 private abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
261 implements DataChangeListenerRegistration<T> {
262 public DataChangeListenerRegistrationImpl(final T listener) {