Merge "BUG 1211 - deletion non existing target returns 500"
[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 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
13 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
14 import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
15 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
16 import org.opendaylight.yangtools.concepts.Identifiable;
17 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
18 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
19 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 import javax.annotation.concurrent.GuardedBy;
25 import java.lang.ref.Reference;
26 import java.lang.ref.WeakReference;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.concurrent.locks.Lock;
32 import java.util.concurrent.locks.ReadWriteLock;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34
35 /**
36  * A set of listeners organized as a tree by node to which they listen. This class
37  * allows for efficient lookup of listeners when we walk the DataTreeCandidate.
38  */
39 public final class ListenerTree  {
40     private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class);
41     private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
42     private final Node rootNode = new Node(null, null);
43
44     private ListenerTree() {
45         // Private to disallow direct instantiation
46     }
47
48     /**
49      * Create a new empty instance of the listener tree.
50      *
51      * @return An empty instance.
52      */
53     public static ListenerTree create() {
54         return new ListenerTree();
55     }
56
57     /**
58      * Registers listener on this node.
59      *
60      * @param path Full path on which listener is registered.
61      * @param listener Listener
62      * @param scope Scope of triggering event.
63      * @return Listener registration
64      */
65     public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
66             final L listener, final DataChangeScope scope) {
67
68         // Take the write lock
69         rwLock.writeLock().lock();
70
71         try {
72             Node walkNode = rootNode;
73             for (final PathArgument arg : path.getPath()) {
74                 walkNode = walkNode.ensureChild(arg);
75             }
76
77             final Node node = walkNode;
78             DataChangeListenerRegistration<L> reg = new DataChangeListenerRegistrationImpl<L>(listener) {
79                 @Override
80                 public DataChangeScope getScope() {
81                     return scope;
82                 }
83
84                 @Override
85                 public InstanceIdentifier getPath() {
86                     return path;
87                 }
88
89                 @Override
90                 protected void removeRegistration() {
91                     /*
92                      * TODO: Here's an interesting problem. The way the datastore works, it
93                      *       enqueues requests towards the listener, so the listener will be
94                      *       notified at some point in the future. Now if the registration is
95                      *       closed, we will prevent any new events from being delivered, but
96                      *       we have no way to purge that queue.
97                      *
98                      *       While this does not directly violate the ListenerRegistration
99                      *       contract, it is probably not going to be liked by the users.
100                      */
101
102                     // Take the write lock
103                     ListenerTree.this.rwLock.writeLock().lock();
104                     try {
105                         node.removeListener(this);
106                     } finally {
107                         // Always release the lock
108                         ListenerTree.this.rwLock.writeLock().unlock();
109                     }
110                 }
111             };
112
113             node.addListener(reg);
114             return reg;
115         } finally {
116             // Always release the lock
117             rwLock.writeLock().unlock();
118         }
119     }
120
121     /**
122      * Obtain a tree walking context. This context ensures a consistent view of
123      * the listener registrations. The context should be closed as soon as it
124      * is not required, because each unclosed instance blocks modification of
125      * the listener tree.
126      *
127      * @return A walker instance.
128      */
129     public Walker getWalker() {
130         /*
131          * TODO: The only current user of this method is local to the datastore.
132          *       Since this class represents a read-lock, losing a reference to
133          *       it is a _major_ problem, as the registration process will get
134          *       wedged, eventually grinding the system to a halt. Should an
135          *       external user exist, make the Walker a phantom reference, which
136          *       will cleanup the lock if not told to do so.
137          */
138         final Walker ret = new Walker(rwLock.readLock(), rootNode);
139         rwLock.readLock().lock();
140         return ret;
141     }
142
143     /**
144      * A walking context, pretty much equivalent to an iterator, but it
145      * exposes the undelying tree structure.
146      */
147     public static final class Walker implements AutoCloseable {
148         private final Lock lock;
149         private final Node node;
150
151         @GuardedBy("this")
152         private boolean valid = true;
153
154         private Walker(final Lock lock, final Node node) {
155             this.lock = Preconditions.checkNotNull(lock);
156             this.node = Preconditions.checkNotNull(node);
157         }
158
159         public Node getRootNode() {
160             return node;
161         }
162
163         @Override
164         public synchronized void close() {
165             if (valid) {
166                 lock.unlock();
167                 valid = false;
168             }
169         }
170     }
171
172     /**
173      * This is a single node within the listener tree. Note that the data returned from
174      * and instance of this class is guaranteed to have any relevance or consistency
175      * only as long as the {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker} instance through which it is reached remains
176      * unclosed.
177      */
178     public static final class Node implements StoreTreeNode<Node>, Identifiable<PathArgument> {
179         private final Collection<DataChangeListenerRegistration<?>> listeners = new ArrayList<>();
180         private final Map<PathArgument, Node> children = new HashMap<>();
181         private final PathArgument identifier;
182         private final Reference<Node> parent;
183
184         private Node(final Node parent, final PathArgument identifier) {
185             this.parent = new WeakReference<>(parent);
186             this.identifier = identifier;
187         }
188
189         @Override
190         public PathArgument getIdentifier() {
191             return identifier;
192         }
193
194         @Override
195         public Optional<Node> getChild(final PathArgument child) {
196             return Optional.fromNullable(children.get(child));
197         }
198
199         /**
200          * Return the list of current listeners. This collection is guaranteed
201          * to be immutable only while the walker, through which this node is
202          * reachable remains unclosed.
203          *
204          * @return the list of current listeners
205          */
206         public Collection<DataChangeListenerRegistration<?>> getListeners() {
207             return listeners;
208         }
209
210         private Node ensureChild(final PathArgument child) {
211             Node potential = children.get(child);
212             if (potential == null) {
213                 potential = new Node(this, child);
214                 children.put(child, potential);
215             }
216             return potential;
217         }
218
219         private void addListener(final DataChangeListenerRegistration<?> listener) {
220             listeners.add(listener);
221             LOG.debug("Listener {} registered", listener);
222         }
223
224         private void removeListener(final DataChangeListenerRegistrationImpl<?> listener) {
225             listeners.remove(listener);
226             LOG.debug("Listener {} unregistered", listener);
227
228             // We have been called with the write-lock held, so we can perform some cleanup.
229             removeThisIfUnused();
230         }
231
232         private void removeThisIfUnused() {
233             final Node p = parent.get();
234             if (p != null && listeners.isEmpty() && children.isEmpty()) {
235                 p.removeChild(identifier);
236             }
237         }
238
239         private void removeChild(final PathArgument arg) {
240             children.remove(arg);
241             removeThisIfUnused();
242         }
243
244         @Override
245         public String toString() {
246             return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]";
247         }
248
249
250     }
251
252     private abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
253     implements DataChangeListenerRegistration<T> {
254         public DataChangeListenerRegistrationImpl(final T listener) {
255             super(listener);
256         }
257     }
258 }