BUG-2673: Introduced new more low-level DOM Data Change APIs
[controller.git] / opendaylight / md-sal / sal-inmemory-datastore / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / tree / ListenerTree.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.md.sal.dom.store.impl.tree;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12
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;
18 import java.util.Map;
19 import java.util.concurrent.locks.Lock;
20 import java.util.concurrent.locks.ReadWriteLock;
21 import java.util.concurrent.locks.ReentrantReadWriteLock;
22
23 import javax.annotation.concurrent.GuardedBy;
24
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;
36
37 /**
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.
40  */
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);
45
46     private ListenerTree() {
47         // Private to disallow direct instantiation
48     }
49
50     /**
51      * Create a new empty instance of the listener tree.
52      *
53      * @return An empty instance.
54      */
55     public static ListenerTree create() {
56         return new ListenerTree();
57     }
58
59     /**
60      * Registers listener on this node.
61      *
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
66      */
67     public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final YangInstanceIdentifier path,
68             final L listener, final DataChangeScope scope) {
69
70         // Take the write lock
71         rwLock.writeLock().lock();
72
73         try {
74             Node walkNode = rootNode;
75             for (final PathArgument arg : path.getPathArguments()) {
76                 walkNode = walkNode.ensureChild(arg);
77             }
78
79             final Node node = walkNode;
80             DataChangeListenerRegistration<L> reg = new DataChangeListenerRegistrationImpl<L>(listener) {
81                 @Override
82                 public DataChangeScope getScope() {
83                     return scope;
84                 }
85
86                 @Override
87                 public YangInstanceIdentifier getPath() {
88                     return path;
89                 }
90
91                 @Override
92                 protected void removeRegistration() {
93                     /*
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.
99                      *
100                      *       While this does not directly violate the ListenerRegistration
101                      *       contract, it is probably not going to be liked by the users.
102                      */
103
104                     // Take the write lock
105                     ListenerTree.this.rwLock.writeLock().lock();
106                     try {
107                         node.removeListener(this);
108                     } finally {
109                         // Always release the lock
110                         ListenerTree.this.rwLock.writeLock().unlock();
111                     }
112                 }
113             };
114
115             node.addListener(reg);
116             return reg;
117         } finally {
118             // Always release the lock
119             rwLock.writeLock().unlock();
120         }
121     }
122
123     /**
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
127      * the listener tree.
128      *
129      * @return A walker instance.
130      */
131     public Walker getWalker() {
132         /*
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.
139          */
140         final Walker ret = new Walker(rwLock.readLock(), rootNode);
141         rwLock.readLock().lock();
142         return ret;
143     }
144
145     /**
146      * A walking context, pretty much equivalent to an iterator, but it
147      * exposes the underlying tree structure.
148      */
149     /*
150      * FIXME: BUG-1511: split this class out as ListenerWalker.
151      */
152     public static final class Walker implements AutoCloseable {
153         private final Lock lock;
154         private final Node node;
155
156         @GuardedBy("this")
157         private boolean valid = true;
158
159         private Walker(final Lock lock, final Node node) {
160             this.lock = Preconditions.checkNotNull(lock);
161             this.node = Preconditions.checkNotNull(node);
162         }
163
164         public Node getRootNode() {
165             return node;
166         }
167
168         @Override
169         public synchronized void close() {
170             if (valid) {
171                 lock.unlock();
172                 valid = false;
173             }
174         }
175     }
176
177     /**
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
181      * unclosed.
182      */
183     /*
184      * FIXME: BUG-1511: split this class out as ListenerNode.
185      */
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;
191
192         private Node(final Node parent, final PathArgument identifier) {
193             this.parent = new WeakReference<>(parent);
194             this.identifier = identifier;
195         }
196
197         @Override
198         public PathArgument getIdentifier() {
199             return identifier;
200         }
201
202         @Override
203         public Optional<Node> getChild(final PathArgument child) {
204             return Optional.fromNullable(children.get(child));
205         }
206
207         /**
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.
211          *
212          * @return the list of current listeners
213          */
214         public Collection<DataChangeListenerRegistration<?>> getListeners() {
215             return listeners;
216         }
217
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);
223             }
224             return potential;
225         }
226
227         private void addListener(final DataChangeListenerRegistration<?> listener) {
228             listeners.add(listener);
229             LOG.debug("Listener {} registered", listener);
230         }
231
232         private void removeListener(final DataChangeListenerRegistrationImpl<?> listener) {
233             listeners.remove(listener);
234             LOG.debug("Listener {} unregistered", listener);
235
236             // We have been called with the write-lock held, so we can perform some cleanup.
237             removeThisIfUnused();
238         }
239
240         private void removeThisIfUnused() {
241             final Node p = parent.get();
242             if (p != null && listeners.isEmpty() && children.isEmpty()) {
243                 p.removeChild(identifier);
244             }
245         }
246
247         private void removeChild(final PathArgument arg) {
248             children.remove(arg);
249             removeThisIfUnused();
250         }
251
252         @Override
253         public String toString() {
254             return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]";
255         }
256
257
258     }
259
260     private abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
261     implements DataChangeListenerRegistration<T> {
262         public DataChangeListenerRegistrationImpl(final T listener) {
263             super(listener);
264         }
265     }
266 }