58a0c69bb06a94e59d97f9dc88aee0ea71cef4ca
[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.Collection;
13 import java.util.HashMap;
14 import java.util.HashSet;
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.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;
31
32 import com.google.common.base.Optional;
33 import com.google.common.base.Preconditions;
34
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);
39
40     private ListenerTree() {
41
42     }
43
44     public static ListenerTree create() {
45         return new ListenerTree();
46     }
47
48     /**
49      * Registers listener on this node.
50      *
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
55      */
56     public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
57             final L listener, final DataChangeScope scope) {
58
59         // Take the write lock
60         rwLock.writeLock().lock();
61
62         try {
63             Node walkNode = rootNode;
64             for(PathArgument arg : path.getPath()) {
65                 walkNode = walkNode.ensureChild(arg);
66             }
67
68             final Node node = walkNode;
69             DataChangeListenerRegistration<L> listenerReg = new DataChangeListenerRegistration<L>(listener) {
70                 @Override
71                 public DataChangeScope getScope() {
72                     return scope;
73                 }
74
75                 @Override
76                 public InstanceIdentifier getPath() {
77                     return path;
78                 }
79
80                 @Override
81                 protected void removeRegistration() {
82                     /*
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.
88                      *
89                      *       While this does not directly violate the ListenerRegistration
90                      *       contract, it is probably not going to be liked by the users.
91                      */
92
93                     // Take the write lock
94                     ListenerTree.this.rwLock.writeLock().lock();
95                     try {
96                         node.removeListener(this);
97                     } finally {
98                         // Always release the lock
99                         ListenerTree.this.rwLock.writeLock().unlock();
100                     }
101                 }
102             };
103
104             node.addListener(listenerReg);
105             return listenerReg;
106         } finally {
107             // Always release the lock
108             rwLock.writeLock().unlock();
109         }
110     }
111
112     public Walker getWalker() {
113         /*
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.
120          */
121         final Walker ret = new Walker(rwLock.readLock(), rootNode);
122         rwLock.readLock().lock();
123         return ret;
124     }
125
126     public static final class Walker implements AutoCloseable {
127         private final Lock lock;
128         private final Node node;
129
130         @GuardedBy("this")
131         private boolean valid = true;
132
133         private Walker(final Lock lock, final Node node) {
134             this.lock = Preconditions.checkNotNull(lock);
135             this.node = Preconditions.checkNotNull(node);
136         }
137
138         public Node getRootNode() {
139             return node;
140         }
141
142         @Override
143         public synchronized void close() {
144             if (valid) {
145                 lock.unlock();
146                 valid = false;
147             }
148         }
149     }
150
151     /**
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
155      * unclosed.
156      */
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;
162
163         private Node(final Node parent, final PathArgument identifier) {
164             this.parent = new WeakReference<>(parent);
165             this.identifier = identifier;
166         }
167
168         @Override
169         public PathArgument getIdentifier() {
170             return identifier;
171         }
172
173         @Override
174         public Optional<Node> getChild(final PathArgument child) {
175             return Optional.fromNullable(children.get(child));
176         }
177
178         /**
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.
182          *
183          * @return the list of current listeners
184          */
185         @SuppressWarnings({ "rawtypes", "unchecked" })
186         public Collection<org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<?>> getListeners() {
187             return (Collection) listeners;
188         }
189
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);
195             }
196             return potential;
197         }
198
199         private void addListener(final DataChangeListenerRegistration<?> listener) {
200             listeners.add(listener);
201             LOG.debug("Listener {} registered", listener);
202         }
203
204         private void removeListener(final DataChangeListenerRegistration<?> listener) {
205             listeners.remove(listener);
206             LOG.debug("Listener {} unregistered", listener);
207
208             // We have been called with the write-lock held, so we can perform some cleanup.
209             removeThisIfUnused();
210         }
211
212         private void removeThisIfUnused() {
213             final Node p = parent.get();
214             if (p != null && listeners.isEmpty() && children.isEmpty()) {
215                 p.removeChild(identifier);
216             }
217         }
218
219         private void removeChild(final PathArgument arg) {
220             children.remove(arg);
221             removeThisIfUnused();
222         }
223     }
224
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> {
227
228         public DataChangeListenerRegistration(final T listener) {
229             super(listener);
230         }
231     }
232
233 }