BUG-650: speed up ResolveDataChangeState.needsProcessing()
[controller.git] / opendaylight / md-sal / sal-inmemory-datastore / src / main / java / org / opendaylight / controller / md / sal / dom / store / impl / ResolveDataChangeState.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;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.Iterables;
13 import com.google.common.collect.Multimap;
14
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22
23 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
24 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
25 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Recursion state used in {@link ResolveDataChangeEventsTask}. Instances of this
37  * method track which listeners are affected by a particular change node. It takes
38  * care of properly inheriting SUB/ONE listeners and also provides a means to
39  * understand when actual processing need not occur.
40  */
41 final class ResolveDataChangeState {
42     private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeState.class);
43     /**
44      * Inherited from all parents
45      */
46     private final Iterable<Builder> inheritedSub;
47     /**
48      * Inherited from immediate parent
49      */
50     private final Iterable<Builder> inheritedOne;
51     private final YangInstanceIdentifier nodeId;
52     private final Collection<Node> nodes;
53
54     private final Map<DataChangeListenerRegistration<?>, Builder> subBuilders = new HashMap<>();
55     private final Map<DataChangeListenerRegistration<?>, Builder> oneBuilders = new HashMap<>();
56     private final Map<DataChangeListenerRegistration<?>, Builder> baseBuilders = new HashMap<>();
57
58     private ResolveDataChangeState(final YangInstanceIdentifier nodeId,
59             final Iterable<Builder> inheritedSub, final Iterable<Builder> inheritedOne,
60             final Collection<Node> nodes) {
61         this.nodeId = Preconditions.checkNotNull(nodeId);
62         this.nodes = Preconditions.checkNotNull(nodes);
63         this.inheritedSub = Preconditions.checkNotNull(inheritedSub);
64         this.inheritedOne = Preconditions.checkNotNull(inheritedOne);
65
66         /*
67          * Collect the nodes which need to be propagated from us to the child.
68          */
69         for (Node n : nodes) {
70             for (DataChangeListenerRegistration<?> l : n.getListeners()) {
71                 final Builder b = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE);
72                 switch (l.getScope()) {
73                 case BASE:
74                     baseBuilders.put(l, b);
75                     break;
76                 case ONE:
77                     oneBuilders.put(l, b);
78                     break;
79                 case SUBTREE:
80                     subBuilders.put(l, b);
81                     break;
82                 }
83             }
84         }
85     }
86
87     /**
88      * Create an initial state handle at a particular root node.
89      *
90      * @param rootId root instance identifier
91      * @param root root node
92      * @return
93      */
94     public static ResolveDataChangeState initial(final YangInstanceIdentifier rootId, final Node root) {
95         return new ResolveDataChangeState(rootId, Collections.<Builder>emptyList(),
96             Collections.<Builder>emptyList(), Collections.singletonList(root));
97     }
98
99     /**
100      * Create a state handle for iterating over a particular child.
101      *
102      * @param childId ID of the child
103      * @return State handle
104      */
105     public ResolveDataChangeState child(final PathArgument childId) {
106         /*
107          * We instantiate a concatenation only when needed, otherwise
108          * we reuse the collection. This speeds up Iterables.isEmpty()
109          * in needsProcessing().
110          */
111         final Iterable<Builder> sb;
112         if (subBuilders.isEmpty()) {
113             sb = inheritedSub;
114         } else {
115             sb = Iterables.concat(inheritedSub, subBuilders.values());
116         }
117
118         return new ResolveDataChangeState(nodeId.node(childId), sb,
119             oneBuilders.values(), getListenerChildrenWildcarded(nodes, childId));
120     }
121
122     /**
123      * Get the current path
124      *
125      * @return Current path.
126      */
127     public YangInstanceIdentifier getPath() {
128         return nodeId;
129     }
130
131     /**
132      * Check if this child needs processing.
133      *
134      * @return True if processing needs to occur, false otherwise.
135      */
136     public boolean needsProcessing() {
137         // May have underlying listeners, so we need to process
138         if (!nodes.isEmpty()) {
139             return true;
140         }
141         // Have SUBTREE listeners
142         if (!Iterables.isEmpty(inheritedSub)) {
143             return true;
144         }
145         // Have ONE listeners
146         if (!Iterables.isEmpty(inheritedOne)) {
147             return true;
148         }
149
150         return false;
151     }
152
153     /**
154      * Add an event to all current listeners.
155      *
156      * @param event
157      */
158     public void addEvent(final DOMImmutableDataChangeEvent event) {
159         // Subtree builders get always notified
160         for (Builder b : subBuilders.values()) {
161             b.merge(event);
162         }
163         for (Builder b : inheritedSub) {
164             b.merge(event);
165         }
166
167         if (event.getScope() == DataChangeScope.ONE || event.getScope() == DataChangeScope.BASE) {
168             for (Builder b : oneBuilders.values()) {
169                 b.merge(event);
170             }
171         }
172
173         if (event.getScope() == DataChangeScope.BASE) {
174             for (Builder b : inheritedOne) {
175                 b.merge(event);
176             }
177             for (Builder b : baseBuilders.values()) {
178                 b.merge(event);
179             }
180         }
181     }
182
183     /**
184      * Gather all non-empty events into the provided map.
185      *
186      * @param before before-image
187      * @param after after-image
188      * @param map target map
189      */
190     public void collectEvents(final NormalizedNode<?, ?> before, final NormalizedNode<?, ?> after,
191             final Multimap<DataChangeListenerRegistration<?>, DOMImmutableDataChangeEvent> map) {
192         for (Entry<DataChangeListenerRegistration<?>, Builder> e : baseBuilders.entrySet()) {
193             final Builder b = e.getValue();
194             if (!b.isEmpty()) {
195                 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
196             }
197         }
198         for (Entry<DataChangeListenerRegistration<?>, Builder> e : oneBuilders.entrySet()) {
199             final Builder b = e.getValue();
200             if (!b.isEmpty()) {
201                 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
202             }
203         }
204         for (Entry<DataChangeListenerRegistration<?>, Builder> e : subBuilders.entrySet()) {
205             final Builder b = e.getValue();
206             if (!b.isEmpty()) {
207                 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
208             }
209         }
210
211         LOG.trace("Collected events {}", map);
212     }
213
214     private static Collection<Node> getListenerChildrenWildcarded(final Collection<Node> parentNodes,
215             final PathArgument child) {
216         if (parentNodes.isEmpty()) {
217             return Collections.emptyList();
218         }
219
220         final List<Node> result = new ArrayList<>();
221         if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
222             NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
223             addChildNodes(result, parentNodes, wildcardedIdentifier);
224         }
225         addChildNodes(result, parentNodes, child);
226         return result;
227     }
228
229     private static void addChildNodes(final List<Node> result, final Collection<Node> parentNodes, final PathArgument childIdentifier) {
230         for (Node node : parentNodes) {
231             Optional<Node> child = node.getChild(childIdentifier);
232             if (child.isPresent()) {
233                 result.add(child.get());
234             }
235         }
236     }
237 }