2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.md.sal.dom.store.impl;
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.Iterables;
12 import com.google.common.collect.Multimap;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.Map.Entry;
20 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
21 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
22 import org.opendaylight.controller.sal.core.compat.DataChangeListenerRegistration;
23 import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * Recursion state used in {@link ResolveDataChangeEventsTask}. Instances of this
35 * method track which listeners are affected by a particular change node. It takes
36 * care of properly inheriting SUB/ONE listeners and also provides a means to
37 * understand when actual processing need not occur.
39 final class ResolveDataChangeState {
40 private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeState.class);
43 * Inherited from all parents.
45 private final Iterable<Builder> inheritedSub;
48 * Inherited from immediate parent.
50 private final Collection<Builder> inheritedOne;
51 private final YangInstanceIdentifier nodeId;
52 private final Collection<RegistrationTreeNode<DataChangeListenerRegistration<?>>> nodes;
54 private final Map<DataChangeListenerRegistration<?>, Builder> subBuilders;
55 private final Map<DataChangeListenerRegistration<?>, Builder> oneBuilders;
56 private final Map<DataChangeListenerRegistration<?>, Builder> baseBuilders;
58 private ResolveDataChangeState(final YangInstanceIdentifier nodeId,
59 final Iterable<Builder> inheritedSub, final Collection<Builder> inheritedOne,
60 final Collection<RegistrationTreeNode<DataChangeListenerRegistration<?>>> 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);
67 * Collect the nodes which need to be propagated from us to the child.
69 final Map<DataChangeListenerRegistration<?>, Builder> sub = new HashMap<>();
70 final Map<DataChangeListenerRegistration<?>, Builder> one = new HashMap<>();
71 final Map<DataChangeListenerRegistration<?>, Builder> base = new HashMap<>();
72 for (RegistrationTreeNode<DataChangeListenerRegistration<?>> n : nodes) {
73 for (DataChangeListenerRegistration<?> l : n.getRegistrations()) {
74 final Builder b = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE);
75 switch (l.getScope()) {
91 baseBuilders = maybeEmpty(base);
92 oneBuilders = maybeEmpty(one);
93 subBuilders = maybeEmpty(sub);
96 private static <K, V> Map<K, V> maybeEmpty(final Map<K, V> map) {
98 return Collections.emptyMap();
104 * Create an initial state handle at a particular root node.
106 * @param rootId root instance identifier
107 * @param registrationTreeNode root node
109 public static ResolveDataChangeState initial(final YangInstanceIdentifier rootId,
110 final RegistrationTreeNode<DataChangeListenerRegistration<?>> registrationTreeNode) {
111 return new ResolveDataChangeState(rootId, Collections.<Builder>emptyList(),
112 Collections.<Builder>emptyList(), Collections.singletonList(registrationTreeNode));
116 * Create a state handle for iterating over a particular child.
118 * @param childId ID of the child
119 * @return State handle
121 public ResolveDataChangeState child(final PathArgument childId) {
123 * We instantiate a concatenation only when needed:
125 * 1) If our collection is empty, we reuse the parent's. This is typically the case
126 * for intermediate node, which should be the vast majority.
127 * 2) If the parent's iterable is a Collection and it is empty, reuse our collection.
128 * This is the case for the first node which defines a subtree listener in a
129 * particular subtree.
130 * 3) Concatenate the two collections. This happens when we already have some
131 * subtree listeners and we encounter a node which adds a few more.
133 * This allows us to lower number of objects allocated and also
134 * speeds up Iterables.isEmpty() in needsProcessing().
136 * Note that the check for Collection in 2) relies on precisely this logic, which
137 * ensures that we simply cannot see an empty concatenation, but rather start off with
138 * an empty collection, then switch to a non-empty collection and finally switch to
139 * a concatenation. This saves us from instantiating iterators, which a trivial
140 * Iterables.isEmpty() would do as soon as we cross case 3).
142 final Iterable<Builder> sb;
143 if (!subBuilders.isEmpty()) {
144 if (inheritedSub instanceof Collection && ((Collection<?>) inheritedSub).isEmpty()) {
145 sb = subBuilders.values();
147 sb = Iterables.concat(inheritedSub, subBuilders.values());
153 return new ResolveDataChangeState(nodeId.node(childId), sb,
154 oneBuilders.values(), getListenerChildrenWildcarded(nodes, childId));
158 * Get the current path.
160 * @return Current path.
162 public YangInstanceIdentifier getPath() {
167 * Check if this child needs processing.
169 * @return True if processing needs to occur, false otherwise.
171 public boolean needsProcessing() {
172 // May have underlying listeners, so we need to process
173 if (!nodes.isEmpty()) {
176 // Have ONE listeners
177 if (!inheritedOne.isEmpty()) {
182 * Have SUBTREE listeners
184 * This is slightly magical replacement for !Iterables.isEmpty(inheritedSub).
185 * It relies on the logic in child(), which gives us the guarantee that when
186 * inheritedSub is not a Collection, it is guaranteed to be non-empty (which
187 * means we need to process). If it is a collection, we still need to check
190 * Unlike Iterables.isEmpty() this code does not instantiate any temporary
191 * objects and is thus more efficient.
193 if (inheritedSub instanceof Collection) {
194 return !((Collection<?>) inheritedSub).isEmpty();
197 // Non-Collection => non-empty => have to process
202 * Add an event to all current listeners.
204 public void addEvent(final DOMImmutableDataChangeEvent event) {
205 // Subtree builders get always notified
206 for (Builder b : subBuilders.values()) {
209 for (Builder b : inheritedSub) {
213 if (event.getScope() == DataChangeScope.ONE || event.getScope() == DataChangeScope.BASE) {
214 for (Builder b : oneBuilders.values()) {
219 if (event.getScope() == DataChangeScope.BASE) {
220 for (Builder b : inheritedOne) {
223 for (Builder b : baseBuilders.values()) {
230 * Gather all non-empty events into the provided map.
232 * @param before before-image
233 * @param after after-image
234 * @param map target map
236 public void collectEvents(final NormalizedNode<?, ?> before, final NormalizedNode<?, ?> after,
237 final Multimap<DataChangeListenerRegistration<?>, DOMImmutableDataChangeEvent> map) {
238 for (Entry<DataChangeListenerRegistration<?>, Builder> e : baseBuilders.entrySet()) {
239 final Builder b = e.getValue();
241 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
244 for (Entry<DataChangeListenerRegistration<?>, Builder> e : oneBuilders.entrySet()) {
245 final Builder b = e.getValue();
247 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
250 for (Entry<DataChangeListenerRegistration<?>, Builder> e : subBuilders.entrySet()) {
251 final Builder b = e.getValue();
253 map.put(e.getKey(), b.setBefore(before).setAfter(after).build());
257 LOG.trace("Collected events {}", map);
260 private static Collection<RegistrationTreeNode<DataChangeListenerRegistration<?>>> getListenerChildrenWildcarded(
261 final Collection<RegistrationTreeNode<DataChangeListenerRegistration<?>>> parentNodes,
262 final PathArgument child) {
263 if (parentNodes.isEmpty()) {
264 return Collections.emptyList();
267 final List<RegistrationTreeNode<DataChangeListenerRegistration<?>>> result = new ArrayList<>();
268 if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
269 NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
270 addChildNodes(result, parentNodes, wildcardedIdentifier);
272 addChildNodes(result, parentNodes, child);
276 private static void addChildNodes(final List<RegistrationTreeNode<DataChangeListenerRegistration<?>>> result,
277 final Collection<RegistrationTreeNode<DataChangeListenerRegistration<?>>> parentNodes,
278 final PathArgument childIdentifier) {
279 for (RegistrationTreeNode<DataChangeListenerRegistration<?>> node : parentNodes) {
280 RegistrationTreeNode<DataChangeListenerRegistration<?>> child = node.getExactChild(childIdentifier);