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