BUG-509: Fix thread safety of listener registration
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / tree / ListenerRegistrationNode.java
1 package org.opendaylight.controller.md.sal.dom.store.impl.tree;
2
3 import java.util.Collection;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Map;
7
8 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
9 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
10 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
11 import org.opendaylight.yangtools.concepts.Identifiable;
12 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
13 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
14 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17
18 import com.google.common.base.Optional;
19
20 public class ListenerRegistrationNode implements StoreTreeNode<ListenerRegistrationNode>, Identifiable<PathArgument> {
21
22     private static final Logger LOG = LoggerFactory.getLogger(ListenerRegistrationNode.class);
23
24     private final ListenerRegistrationNode parent;
25     private final Map<PathArgument, ListenerRegistrationNode> children;
26     private final PathArgument identifier;
27     private final HashSet<DataChangeListenerRegistration<?>> listeners;
28
29     private ListenerRegistrationNode(final PathArgument identifier) {
30         this(null, identifier);
31     }
32
33     private ListenerRegistrationNode(final ListenerRegistrationNode parent, final PathArgument identifier) {
34         this.parent = parent;
35         this.identifier = identifier;
36         children = new HashMap<>();
37         listeners = new HashSet<>();
38     }
39
40     public final static ListenerRegistrationNode createRoot() {
41         return new ListenerRegistrationNode(null);
42     }
43
44     @Override
45     public PathArgument getIdentifier() {
46         return identifier;
47     }
48
49     /**
50      * Return the list of current listeners. Any caller wishing to use this method
51      * has to make sure the collection remains unchanged while it's executing. This
52      * means the caller has to synchronize externally both the registration and
53      * unregistration process.
54      *
55      * @return the list of current listeners
56      */
57     @SuppressWarnings({ "rawtypes", "unchecked" })
58     public Collection<org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<?>> getListeners() {
59         return (Collection) listeners;
60     }
61
62     @Override
63     public synchronized Optional<ListenerRegistrationNode> getChild(final PathArgument child) {
64         return Optional.fromNullable(children.get(child));
65     }
66
67     public synchronized ListenerRegistrationNode ensureChild(final PathArgument child) {
68         ListenerRegistrationNode potential = (children.get(child));
69         if (potential == null) {
70             potential = new ListenerRegistrationNode(this, child);
71             children.put(child, potential);
72         }
73         return potential;
74     }
75
76     /**
77      * Registers listener on this node.
78      *
79      * @param path Full path on which listener is registered.
80      * @param listener Listener
81      * @param scope Scope of triggering event.
82      * @return
83      */
84     public synchronized <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> DataChangeListenerRegistration<L> registerDataChangeListener(final InstanceIdentifier path,
85             final L listener, final DataChangeScope scope) {
86
87         DataChangeListenerRegistration<L> listenerReg = new DataChangeListenerRegistration<L>(path,listener, scope, this);
88         listeners.add(listenerReg);
89         LOG.debug("Listener {} registered", listener);
90         return listenerReg;
91     }
92
93     private synchronized void removeListener(final DataChangeListenerRegistration<?> listener) {
94         listeners.remove(listener);
95         LOG.debug("Listener {} unregistered", listener);
96         removeThisIfUnused();
97     }
98
99     private void removeThisIfUnused() {
100         if (parent != null && listeners.isEmpty() && children.isEmpty()) {
101             parent.removeChildIfUnused(this);
102         }
103     }
104
105     public boolean isUnused() {
106         return (listeners.isEmpty() && children.isEmpty()) || areChildrenUnused();
107     }
108
109     private boolean areChildrenUnused() {
110         for (ListenerRegistrationNode child : children.values()) {
111             if (!child.isUnused()) {
112                 return false;
113             }
114         }
115         return true;
116     }
117
118     private void removeChildIfUnused(final ListenerRegistrationNode listenerRegistrationNode) {
119         // FIXME Remove unnecessary
120     }
121
122     public static class DataChangeListenerRegistration<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>>
123             extends AbstractObjectRegistration<T> implements
124             org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration<T> {
125
126         private final DataChangeScope scope;
127         private ListenerRegistrationNode node;
128         private final InstanceIdentifier path;
129
130         public DataChangeListenerRegistration(final InstanceIdentifier path,final T listener, final DataChangeScope scope,
131                 final ListenerRegistrationNode node) {
132             super(listener);
133             this.path = path;
134             this.scope = scope;
135             this.node = node;
136         }
137
138         @Override
139         public DataChangeScope getScope() {
140             return scope;
141         }
142
143         @Override
144         protected void removeRegistration() {
145             node.removeListener(this);
146             node = null;
147         }
148
149         @Override
150         public InstanceIdentifier getPath() {
151             return path;
152         }
153     }
154 }